نظرات مطالب
نحوه ایجاد یک تصویر امنیتی (Captcha) با حروف فارسی در ASP.Net MVC
سلام دوست عزیز
بابت تاخیر بوجود آمده در پاسخ دهی عذرخواهی می‌کنم.

من همین کپچا رو دارم استفاده می‌کنم و این مشکلو هم باهاش ندارم. یک حدسی که می‌زنم اینه که شاید کوکی بار اول درست مقداردهی نشه. لطفا کل عملیات از تشکیل تصویر امنیتی، تا مقداری که درون کوکی قرار می‌گیره، و بعد رمز گزاری میشه ، تا مقداری که درون کوکی مرورگر ذخیره میشه ( برای مشاهده کوکی‌های مرورگر از این روش می‌تونید استفاده کنید) . سپس ببینید بعد از پست شدن فرم حاوی کپچا به سرور مقداری که از درون کوکی خونده میشه و می‌خواد decrypt بشه با مقداری که از اول ذخیره شده یکسان باشه.

امیدوارم مشکلتون حل شه دوست عزیز.
نظرات مطالب
EF Code First #12
سلام
عالی مثل همیشه.
مهندس شما فرمودین:
الگوی مخزنی که ارائه داده در این مثال ساده کار می‌کنه اما اگر قرار باشه با چند موجودیت کار کرد و نتیجه رو ترکیب، کارآیی خوبی نداره چون خیلی از قابلیت‌های ذاتی EF مثل کوئری‌های به تاخیر افتاده (deferred LINQ queries) در اینجا قابل پیاده سازی نیست. اگر هم بخوان این رو اضافه کنن باید به لایه مخزن خروجی IQueryable اضافه کنن که به یک طراحی نشتی دار خواهند رسید چون انتهای کار با خروجی IQueryable کاملا باز باقی می‌ماند (نمونه‌اش متد Get ایی است که طراحی کرده).

البته (البته چندین جای دیگه هم گفتین) در مورد نشتی حافظه، کاربرد IQueryable  پس توی کدام لایه از کار ما می‌تونه باشه با توجه به انعظاف پذیری که به کار ما میده؟
مطالب
کنترل نرخ ورود اطلاعات در برنامه‌های Angular
فرض کنید قصد دارید همزمان با تایپ کاربر، نتایج جستجو را به او نمایش دهید. این جستجو نیز عموما به همراه ارسال یک درخواست HTTP به سمت سرور و نمایش اطلاعات بازگشتی به کاربر است. جهت کاهش تعداد رفت و برگشت‌های به سرور، کاهش بار سرور و همچنین کاهش تعداد بار به روز رسانی رابط کاربری، کتابخانه‌ی RxJS به همراه متدهایی است که امکان کاهش نرخ ورودی کاربر را میسر می‌کنند.


کنترلر جستجوی سمت سرور و سرویس سمت کلاینت استفاده کننده‌ی از آن

در اینجا کنترلر و اکشن متدی را جهت جستجوی قسمتی از نام کشورها، مشاهده می‌کنید:
    [Route("api/[controller]")]
    public class TypeaheadController : Controller
    {
        [HttpGet("[action]")]
        public async Task<IActionResult> SearchCountries(string term)
        {
            await Task.Delay(1000); // simulating a slow operation

            var items = new[]
                {
                     "Afghanistan",
                     "Albania",
                     "Algeria",
                     "American Samoa",
                     "Andorra",
                     "Angola",
                     "Anguilla",
                     "Antarctica",
                     "Antigua and/or Barbuda"
                };
            var results = string.IsNullOrWhiteSpace(term) ? items :
                           items.Where(item => item.StartsWith(term, StringComparison.OrdinalIgnoreCase));
            return Json(results.ToArray());
        }
    }
از این کنترلر به نحو ذیل در برنامه‌ی Angular برای ارسال اطلاعات و انجام جستجو استفاده می‌شود:
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { ErrorObservable } from "rxjs/observable/ErrorObservable";
import { catchError, map } from "rxjs/operators";

@Injectable()
export class SearchService {

  constructor(private http: HttpClient) { }

  searchCountries(term: string): Observable<string[]> {
    return this.http
      .get(`/api/Typeahead/SearchCountries?term=${encodeURIComponent(term)}`)
      .pipe(
        map(response => response || {}),
        catchError((error: HttpErrorResponse) => ErrorObservable.create(error))
      );
  }
}
در اینجا از اپراتور pipe مخصوص RxJS 5.5 استفاده شده‌است.


جستجوی ورودی کاربر به ازای هربار ورود اطلاعات توسط او

صرفنظر از نوع فرمی که استفاده می‌کنید (مبتنی بر قالب‌ها و یا واکنشی)، جهت انتقال هربار فشرده شدن کلیدی به کدهای کامپوننت، می‌توان از رخ‌داد input استفاده کرد:
<label>Country: </label>
<input type="text" (input)="onSearch1Change($event.target.value)" />
<ul class="list-group">
   <li class="list-group-item" *ngFor="let country of countries1">
        {{country}}
   </li>
</ul>
و سپس متد مدیریت کننده‌ی آن در کامپوننت نیز به صورت زیر تعریف می‌شود:
onSearch1Change(value: string) {
 
}
در این حالت روش ابتدایی واکنش نشان دادن به هر ورودی، تزریق SearchService فوق به سازنده‌ی این کامپوننت
 constructor(private searchService: SearchService) { }
و سپس مشترک متد جستجوی سمت سرور آن، شدن است.
این روش ابتدایی سه مشکل را به همراه دارد:
الف) به ازای هر بار فشرده شدن کلیدی در Input box، یک درخواست به سمت سرور ارسال می‌شود. برای مثال اگر هدف اصلی کاربر، جستجوی کشورهای شروع شده‌ی با alg باشد، سه درخواست به سمت سرور ارسال می‌شوند و سه بار هم رابط کاربری به روز می‌شود.
ب) اگر در این بین، کاربر حرفی را کم و زیاد کند، درخواست‌های قبلی لغو نمی‌شوند.
ج) درخواست‌ها به صورت موازی به سرور ارسال می‌شوند. ممکن است نتیجه‌ی یکی زودتر و دیگری دیرتر دریافت شود. در این حالت آخرین نتیجه‌ی رسیده، نتایج قبلی را بازنویسی می‌کند که ممکن است الزاما نتیجه‌ای نباشد که کاربر درخواست کرده‌است.


کنترل نرخ ورود اطلاعات توسط متد debounceTime

با اعمال اپراتور debounceTime به رخ‌داد تغییرات ورودی، می‌توان نرخ ورودی کاربر و واکنش نشان دادن به آن‌را کاهش داد. برای مثال اگر این عدد به 300 میلی ثانیه تنظیم شده باشد، صرفا به اولین ورودی رسیده‌ی پس از 300 میلی ثانیه واکنش نشان داده می‌شود و از مابقی صرفنظر خواهد شد. به این ترتیب دیگر به ازای هربار فشرده شدن کلیدی توسط کاربر جستجو صورت نمی‌گیرد. همچنین با ترکیب آن با اپراتور distinctUntilChanged می‌توان تنها به تغییرات غیرتکراری واکنش نشان داد:
export class AutocompleteSampleComponent implements OnInit {

  countries1: string[] = [];
  private model1Changed: Subject<string> = new Subject<string>();
  private dueTime = 300;

  constructor(private searchService: SearchService) { }

  ngOnInit() {
    this.model1Changed
      .pipe(
        debounceTime(this.dueTime),
        distinctUntilChanged(),
        flatMap(inputValue => {
          console.log("debounced input value1", inputValue);
          return this.searchService.searchCountries(inputValue);
        })
      )
      .subscribe(countries => {
        this.countries1 = countries;
      });
  }

  onSearch1Change(value: string) {
    this.model1Changed.next(value);
  }
}
بنابراین بجای اینکه متد this.searchService.searchCountries دقیقا داخل onSearch1Change فراخوانی شود، باید بتوان تغییرات صورت گرفته‌ی نهایی را پس از اعمال debounceTime و distinctUntilChanged به آن ارسال کرد و سپس نتیجه را به کاربر نمایش داد.
برای این منظور یک Subject تعریف شده‌است تا کار مدیریت تغییرات رسیده (کلیدهای فشرده شده‌ی توسط کاربر) را انجام دهد. در این‌حالت فرصت خواهیم داشت تا انواع و اقسام اپراتورهای RxJS را با هم ترکیب و صرفا نتیجه‌ی نهایی (آخرین ورودی یکتای با تاخیر او) را به searchService ارسال کنیم.
متد onSearch1Change نیز تنها کافی است با فراخوانی متد next این Subject‌، جریان تغییرات رسیده را به آن انتقال دهد.
در اینجا برای انتقال آخرین ورودی یکتای با تاخیر به متد this.searchService.searchCountries از اپراتور flatMap استفاده شده‌است. این اپراتور، آخرین ورودی فیلتر شده را دریافت کرده و به متد searchCountries ارسال می‌کند. همچنین خروجی آن نیز یک Observable است. به همین جهت در ادامه می‌توان توسط متد subscribe، مشترک آن شد و آرایه‌ی countries دریافتی از سرور را به کاربر نمایش داد.



بهبود کارآیی جستجو با لغو درخواست‌های پیشین

تا اینجا توانستیم نرخ ورود اطلاعات کاربر را به صورت کنترل شده‌ای به متد this.searchService.searchCountries ارسال کنیم و نه اینکه به ازای هر بار ورود اطلاعات توسط آن، یکبار این متد فراخوانی شود. اما همانطور که در تصویر فوق مشاهده می‌کنید، در اینجا هدف نهایی کاربر، جستجوی نام کشورهای شروع شده‌ی با alg بوده است و در این بین چندین بار سعی و خطا انجام داده‌است تا به alg رسیده‌است. مشکل اینجا است که هیچکدام از درخواست‌های قبلی او که مدنظر نبوده‌اند، لغو نشده‌اند و تمام آن‌ها صورت گرفته و همچنین سبب به روز رسانی‌های مکرر رابط کاربری شده‌اند.
برای رفع یک چنین مشکلی و لغو خودکار درخواست‌های قبلی، اپراتور دیگری به نام switchMap وجود دارد که دقیقا یک چنین کاری را انجام می‌دهد. در اینجا برخلاف اپراتور flatMap، تمام درخواست‌های تمام نشده‌ی قبلی، لغو شده و صرفا آخرین مورد پردازش می‌شود.


برای اعمال آن نیز در کدهای فوق تنها کافی است flatMap را با switchMap جایگزین کنید. پس از آن نتیجه را در تصویر فوق ملاحظه می‌کنید. اینبار اگر هدف نهایی کاربر جستجوی alg باشد، تمام ورودی‌های قبلی او به صورت خودکار لغو می‌شوند و دیگر پردازش نخواهند شد که در نهایت سبب بالا رفتن کارآیی برنامه با کاهش تعداد بار به روز رسانی رابط کاربری خواهد شد.

همچنین در حالت استفاده‌ی از flatMap، ممکن است کاربر نتیجه‌ی اشتباهی را نیز دریافت کند. از این جهت که درخواست‌های ارسالی به سمت سرور، به صورت موازی اجرا می‌شوند. در این حالت ممکن است یکی زودتر و دیگری دیرتر به پایان برسد و کاربر نتیجه‌ای را که مشاهده می‌کند، دقیقا آن چیزی نباشد که جستجو کرده‌است (رابط کاربری آخرین درخواست پایان یافته را نمایش می‌دهد که نتیجه‌ی آن الزاما به ترتیب ورود اطلاعات کاربر نیست).
// A1: Request for `ABC`
// A2: Response for `ABC`
// B1: Request for `ABCX`
// B2: Response for `ABCX`
--A1----------A2-->
------B1--B2------>
برای نمونه فرض کنید دو درخواست A1 و B1 به همراه پاسخ‌های A2 و B2 را داریم. درخواست A1 پیش از B1 ارسال شده‌است؛ اما پاسخ B1 زودتر از پاسخ A2 از سرور دریافت شده‌است. در این حالت کاربر عبارت ABCX را وارد کرده‌است اما پاسخ عبارت ABC پیشین را در رابط کاربری مشاهده می‌کند (آخرین پاسخ رسیده در رابط کاربری (یا همان A2)، پاسخ‌های قبلی (یا همان B2) را بازنویسی می‌کند).

در حالت استفاده‌ی از flatMap‌، مشترک هر رخ‌داد رسیده خواهیم شد؛ بدون قطع اشتراک خودکار از سایر observableهای ایجاد شده‌ی پیشین. اما در حالت استفاده‌ی از switchMap‌، ابتدا کار لغو اشتراک خودکار از تمام observableهای قبلی صورت می‌گیرد و سپس یک observable جدید را ایجاد می‌کند. به همین جهت است که استفاده‌ی از switchMap‌  به همراه درخواست‌های http، سبب لغو خودکار درخواست‌های پیشین می‌شود. در این حالت نه تنها تعداد بار به روز رسانی رابط کاربری کاهش پیدا می‌کند، بلکه تضمین خواهد شد دیگر کاربر نتیجه‌ی اشتباهی را نیز مشاهده نکند.



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
- در سازنده‌ی کلاس ApplicationDbContext ، به connectionString1 اشاره شده که تعریف آن در فایل web.config برنامه موجود است. به عبارتی در این مثال چنین رشته‌ی اتصالی تعریف شده‌است:
<add name="connectionString1"
connectionString="Data Source=(local);Initial Catalog=TestDbIdentity;Integrated Security = true" 
providerName="System.Data.SqlClient" />
بنابراین بانک اطلاعاتی پیش فرض آن TestDbIdentity نام دارد (جهت اتصال به آن، برای مشاهده جداول و یا تغییر و ثبت اطلاعات). این رشته اتصالی هم مخصوص SQL Server تنظیم شده‌است که می‌توانید توسط management studio و یا سایر ابزارهای مشابه، همانند قبل به آن متصل شوید.
- «در پوشه App_Data حذف کردم» ... از مثال نهایی کامل شده استفاده کنید (^) و نیازی به تکرار این مراحل نیست تا خطای یافت نشدن dbcontext را دریافت نکنید.
- برای ReCreate فقط کافی هست که بانک اطلاعاتی TestDbIdentity را drop کنید. بعد برنامه را مجددا از نو اجرا کنید. چون مراحل migrations آن به حالت خودکار تنظیم شده‌است، بانک اطلاعاتی را به صورت خودکار ایجاد می‌کند یا تغییرات کلاس‌های دومین برنامه را به صورت خودکار به بانک اطلاعاتی اعمال خواهد کرد.
- برای آشنایی بیشتر با مباحث تعریف رشته اتصالی EF Code First و مباحث Migrations آن، سری EF Code First را در سایت یکبار مطالعه کنید.
اشتراک‌ها
انگولار 1.6

نگاهی بر تغییرات جدید در نسخه‌ی 1.6

انگولار 1.6