چندی قبل مطلب «نرمال سازی اطلاعات کاربران در حین ثبت نام» را در سایت جاری مطالعه کردید. پیاده سازی یک چنین قابلیتی به صورت توکار در ASP.NET Core Identity پیش بینی شدهاست. همچنین تمام اعتبارسنجهای نامهای کاربران، کلمات عبور آنها، ایمیلهای آنها و غیره را نیز میتوان سفارشی سازی کرد و بجای سرویسهای پیشفرض آنها معرفی و جایگزین نمود.
سفارشی سازی نرمال سازها
اگر به طراحی جداول ASP.NET Core Identity دقت کنید، تعدادی فیلد اضافی حاوی کلمهی Normalized را هم مشاهده خواهید کرد. برای مثال:
در جدول کاربران، فیلدهای Email و UserName به همراه دو فیلد اضافهی NormalizedEmail و NormalizedUserName وجود دارند.
مقدار دهی و مدیریت این فیلدهای ویژه به صورت خودکار توسط کلاسی به نام UpperInvariantLookupNormalizerصورت میگیرد:
این کلاس اینترفیس ILookupNormalizer را پیاده سازی کرده و تنها کاری را که انجام میدهد، تبدیل نام کاربر، نام نقشها و یا ایمیل کاربر به حالت upper case آن است. اما هدف اصلی از آن چیست؟
همانطور که در مطلب «نرمال سازی اطلاعات کاربران در حین ثبت نام» نیز عنوان شد، برای مثال ایمیلهای جیمیل را میتوان با چندین حالت مختلف ثبت کرد و یک کاربر به این صورت میتواند شرط یکتا بودن آدرس ایمیلهای تنظیم شدهی در کلاس IdentityServicesRegistryرا دور بزند:
به همین جهت برای سفارشی سازی آن کلاس CustomNormalizerبا سفارشی سازی UpperInvariantLookupNormalizer پیاده سازی شدهاست.
چون تنها یک اینترفیس ILookupNormalizer وجود دارد، باید بر اساس محتوای کلیدی که به آن ارسال میشود:
تصمیمگیری کرد که آیا ایمیل است یا خیر. چون از این نرمال کننده هم برای ایمیلها و هم برای نامها استفاده میشود. سپس میتوان منطقهای سفارشی خود مانند حذف نقطههای اضافی ایمیلها و یا حذف کاراکترهای اضافی اعمالی به نامهای کاربری را اعمال کرد.
پس از تدارک کلاس CustomNormalizer، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistryاست:
یکبار CustomNormalizer را به عنوان پیاده سازی کنندهی ILookupNormalizer معرفی کردهایم. همچنین یکبار هم سرویس توکار UpperInvariantLookupNormalizer را به سرویس سفارشی خودمان هدایت کردهایم. به این ترتیب مطمئن خواهیم شد که همواره از CustomNormalizer ما استفاده خواهد شد.
بنابراین دیگر نیازی نیست تا در حین ثبتنام نسبت به تمیزسازی ایمیلها و یا نامهای کاربری اقدام کنیم. سرویس ILookupNormalizer در پشت صحنه به صورت خودکار در تمام مراحل ثبت نام و به روز رسانیها توسط ASP.NET Core Identity استفاده میشود.
سفارشی سازی UserValidator
ASP.NET Core Identity به همراه یک سرویس توکار اعتبارسنج کاربراناست که با پیاده سازی اینترفیس IUserValidator ارائه شدهاست:
این سرویس پیشفرض و توکار، تنظیمات Options.User.RequireUniqueEmail، Options.User.AllowedUserNameCharacters و امثال آنرا در مورد نامهای کاربری و ایمیلها بررسی میکند (تنظیم شدهی در متد setUserOptionsکلاس IdentityServicesRegistry).
بنابراین اگر قصد تهیهی یک IUserValidator جدید را داشته باشیم، از تمام تنظیمات و بررسیهای پیش فرض سرویس توکار UserValidator فوق محروم میشویم. به همین جهت برای سفارشی سازی این سرویس، از خود کلاس UserValidator ارث بری کرده و سپس base.ValidateAsync آنرا فراخوانی میکنیم. با اینکار سبب خواهیم شد تا تمام اعتبارسنجیهای پیشفرض ASP.NET Core Identity اعمال شده و پس از آن منطقهای سفارشی اعتبارسنجی خود را که در کلاس CustomUserValidatorقابل مشاهده هستند، اضافه میکنیم.
در اینجا برای مثال در متد validateEmail سفارشی تهیه شده، لیست یک سری fake email provider اضافه شدهاند (مدخل EmailsBanListدر فایل appsettings.json برنامه) تا کاربران نتوانند از آنها جهت ثبتنام استفاده کنند و یا در متد validateUserName سفارشی، اگر نام کاربری برای مثال عددی وارد شده بود، یک new IdentityError بازگشت داده میشود.
پس از تدارک کلاس CustomUserValidator، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistryاست:
یکبار CustomUserValidator را به عنوان پیاده سازی کنندهی IUserValidator معرفی کردهایم. همچنین یکبار هم سرویس توکار UserValidator را به سرویس سفارشی خودمان هدایت کردهایم. به این ترتیب مطمئن خواهیم شد که همواره از CustomUserValidator ما استفاده خواهد شد (حتی اگر UserValidator اصلی از سیستم تزریق وابستگیها درخواست شود).
سفارشی سازی PasswordValidator
مراحل سفارشی سازی اعتبارسنج کلمات عبور نیز همانند تهیهی CustomUserValidator فوق است.
ASP.NET Core Identity به همراه یک سرویس توکار اعتبارسنج کلمات عبور کاربراناست که با پیاده سازی اینترفیس IPasswordValidator ارائه شدهاست:
در این کلاس، از اطلاعات متد setPasswordOptionsکلاس IdentityServicesRegistry
که از فایل appsettings.json و مدخل PasswordOptionsآن تامین میشود:
جهت اعتبارسنجی کلمات عبور وارد شدهی توسط کاربران در حین ثبت نام و یا به روز رسانی اطلاعات خود، استفاده میشود.
بنابراین در اینجا نیز ارائهی یک پیاده سازی خام از IPasswordValidator سبب خواهد شد تا تمام اعتبارسنجیهای توکار کلاس PasswordValidator اصلی را از دست بدهیم. به همین جهت کار را با ارث بری از همین کلاس توکار شروع کرده و ابتدا متد base.ValidateAsync آنرا فراخوانی میکنیم تا مطمئن شویم، مدخل PasswordOptions تنظیمات یاد شده، حتما پردازش خواهند شد. سپس منطق سفارشی خود را اعمال میکنیم.
برای مثال در کلاس CustomPasswordValidatorتهیه شده، به مدخل PasswordsBanListفایل appsettings.json مراجعه شده و کاربران را از انتخاب کلمات عبوری به شدت ساده، منع میکند.
پس از تدارک کلاس CustomPasswordValidator، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
یکبار CustomPasswordValidator را به عنوان پیاده سازی کنندهی IPasswordValidator معرفی کردهایم. همچنین یکبار هم سرویس توکار PasswordValidator را به سرویس سفارشی خودمان هدایت کردهایم. به این ترتیب مطمئن خواهیم شد که همواره از CustomPasswordValidator ما استفاده خواهد شد (حتی اگر PasswordValidator اصلی از سیستم تزریق وابستگیها درخواست شود).
پردازش نتایج اعتبارسنجها
این اعتبارسنجها در خروجیهای IdentityResult تمام متدهای ASP.NET Core Identity ظاهر میشوند. بنابراین فراخوانی سادهی UpdateUserAsync اشتباه است و حتما باید خروجی آنرا جهت پردازش IdentityResult آن بررسی کرد. به همین جهت تعدادی متد الحاقی به کلاس IdentityExtensionsاضافه شدهاند تا کارکردن با IdentityResult را سادهتر کنند.
متد AddErrorsFromResultخطاهای حاصل از عملیات ASP.NET Core Identity را به ModelState جاری اضافه میکند. به این ترتیب میتوان این خطاها را به کاربر در Viewهای برنامه و در قسمت اعتبارسنجی مدل آن نمایش داد.
و یا متد DumpErrorsتمام خطاهای موجود در IdentityResult را تبدیل به یک رشته میکند. برای مثال میتوان این رشته را در Remote validationها مورد استفاده قرار داد.
استفادهی از این متدهای الحاقی را در کنترلرهای برنامه میتوانید مشاهده کنید.
استفادهی از اعتبارسنجها جهت انجام Remote validation
اگر به RegisterControllerدقت کنید، اکشن متدهای ValidateUsername و ValidatePassword قابل مشاهده هستند:
این اکشن متدها توسط سرویسهای
تزریق شدهی به سازندهی کلاس، پیاده سازی شدهاند. در مورد تامین آنها و سفارشی سازی آنها نیز پیشتر بحث شد. این اینترفیسها دقیقا همان وهلههای CustomUserValidator و CustomPasswordValidator را در اختیار ما قرار میدهند. تنها کاری را که باید انجام دهیم، فراخوانی متد ValidateAsync آنها است. این متد یک خروجی از نوع IdentityResult را دارد. به همین جهت متد DumpErrors را برای پردازش این نتیجه تدارک دیدیم.
به این ترتیب کاربران در حین ثبت نام، راهنمای بهتری را جهت انتخاب کلمات عبور و نام کاربری مشاهده خواهند کرد و این بررسیها نیز Ajax ایی هستند و پیش از ارسال فرم نهایی به سرور اتفاق میافتند.
برای فعالسازی Remote validation، علاوه برثبت اسکریپتهای Ajax ایی، خواص کلاس RegisterViewModelنیز از ویژگی Remote استفاده میکنند:
یک نکته:برای اینکه Remote Validation را به همراه ValidateAntiForgeryToken استفاده کنیم، تنها کافی است نام فیلد مخفی آنرا به لیست AdditionalFields به نحوی که مشاهده میکنید، اضافه کنیم.
کدهای کامل این سری را در مخزن کد DNT Identityمیتوانید ملاحظه کنید.
سفارشی سازی نرمال سازها
اگر به طراحی جداول ASP.NET Core Identity دقت کنید، تعدادی فیلد اضافی حاوی کلمهی Normalized را هم مشاهده خواهید کرد. برای مثال:
در جدول کاربران، فیلدهای Email و UserName به همراه دو فیلد اضافهی NormalizedEmail و NormalizedUserName وجود دارند.
مقدار دهی و مدیریت این فیلدهای ویژه به صورت خودکار توسط کلاسی به نام UpperInvariantLookupNormalizerصورت میگیرد:
public class UpperInvariantLookupNormalizer : ILookupNormalizer
همانطور که در مطلب «نرمال سازی اطلاعات کاربران در حین ثبت نام» نیز عنوان شد، برای مثال ایمیلهای جیمیل را میتوان با چندین حالت مختلف ثبت کرد و یک کاربر به این صورت میتواند شرط یکتا بودن آدرس ایمیلهای تنظیم شدهی در کلاس IdentityServicesRegistryرا دور بزند:
identityOptionsUser.RequireUniqueEmail = true;
چون تنها یک اینترفیس ILookupNormalizer وجود دارد، باید بر اساس محتوای کلیدی که به آن ارسال میشود:
public override string Normalize(string key)
پس از تدارک کلاس CustomNormalizer، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistryاست:
services.AddScoped<ILookupNormalizer, CustomNormalizer>(); services.AddScoped<UpperInvariantLookupNormalizer, CustomNormalizer>();
بنابراین دیگر نیازی نیست تا در حین ثبتنام نسبت به تمیزسازی ایمیلها و یا نامهای کاربری اقدام کنیم. سرویس ILookupNormalizer در پشت صحنه به صورت خودکار در تمام مراحل ثبت نام و به روز رسانیها توسط ASP.NET Core Identity استفاده میشود.
سفارشی سازی UserValidator
ASP.NET Core Identity به همراه یک سرویس توکار اعتبارسنج کاربراناست که با پیاده سازی اینترفیس IUserValidator ارائه شدهاست:
public class UserValidator<TUser> : IUserValidator<TUser> where TUser : class
بنابراین اگر قصد تهیهی یک IUserValidator جدید را داشته باشیم، از تمام تنظیمات و بررسیهای پیش فرض سرویس توکار UserValidator فوق محروم میشویم. به همین جهت برای سفارشی سازی این سرویس، از خود کلاس UserValidator ارث بری کرده و سپس base.ValidateAsync آنرا فراخوانی میکنیم. با اینکار سبب خواهیم شد تا تمام اعتبارسنجیهای پیشفرض ASP.NET Core Identity اعمال شده و پس از آن منطقهای سفارشی اعتبارسنجی خود را که در کلاس CustomUserValidatorقابل مشاهده هستند، اضافه میکنیم.
public override async Task<IdentityResult> ValidateAsync(UserManager<User> manager, User user) { // First use the built-in validator var result = await base.ValidateAsync(manager, user).ConfigureAwait(false); var errors = result.Succeeded ? new List<IdentityError>() : result.Errors.ToList(); // Extending the built-in validator validateEmail(user, errors); validateUserName(user, errors); return !errors.Any() ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()); }
پس از تدارک کلاس CustomUserValidator، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistryاست:
services.AddScoped<IUserValidator<User>, CustomUserValidator>(); services.AddScoped<UserValidator<User>, CustomUserValidator>();
سفارشی سازی PasswordValidator
مراحل سفارشی سازی اعتبارسنج کلمات عبور نیز همانند تهیهی CustomUserValidator فوق است.
ASP.NET Core Identity به همراه یک سرویس توکار اعتبارسنج کلمات عبور کاربراناست که با پیاده سازی اینترفیس IPasswordValidator ارائه شدهاست:
public class PasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
private static void setPasswordOptions(PasswordOptions identityOptionsPassword, SiteSettings siteSettings) { identityOptionsPassword.RequireDigit = siteSettings.PasswordOptions.RequireDigit; identityOptionsPassword.RequireLowercase = siteSettings.PasswordOptions.RequireLowercase; identityOptionsPassword.RequireNonAlphanumeric = siteSettings.PasswordOptions.RequireNonAlphanumeric; identityOptionsPassword.RequireUppercase = siteSettings.PasswordOptions.RequireUppercase; identityOptionsPassword.RequiredLength = siteSettings.PasswordOptions.RequiredLength; }
"PasswordOptions": { "RequireDigit": false, "RequiredLength": 6, "RequireLowercase": false, "RequireNonAlphanumeric": false, "RequireUppercase": false },
بنابراین در اینجا نیز ارائهی یک پیاده سازی خام از IPasswordValidator سبب خواهد شد تا تمام اعتبارسنجیهای توکار کلاس PasswordValidator اصلی را از دست بدهیم. به همین جهت کار را با ارث بری از همین کلاس توکار شروع کرده و ابتدا متد base.ValidateAsync آنرا فراخوانی میکنیم تا مطمئن شویم، مدخل PasswordOptions تنظیمات یاد شده، حتما پردازش خواهند شد. سپس منطق سفارشی خود را اعمال میکنیم.
برای مثال در کلاس CustomPasswordValidatorتهیه شده، به مدخل PasswordsBanListفایل appsettings.json مراجعه شده و کاربران را از انتخاب کلمات عبوری به شدت ساده، منع میکند.
پس از تدارک کلاس CustomPasswordValidator، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
services.AddScoped<IPasswordValidator<User>, CustomPasswordValidator>(); services.AddScoped<PasswordValidator<User>, CustomPasswordValidator>();
پردازش نتایج اعتبارسنجها
این اعتبارسنجها در خروجیهای IdentityResult تمام متدهای ASP.NET Core Identity ظاهر میشوند. بنابراین فراخوانی سادهی UpdateUserAsync اشتباه است و حتما باید خروجی آنرا جهت پردازش IdentityResult آن بررسی کرد. به همین جهت تعدادی متد الحاقی به کلاس IdentityExtensionsاضافه شدهاند تا کارکردن با IdentityResult را سادهتر کنند.
public static void AddErrorsFromResult(this ModelStateDictionary modelStat, IdentityResult result)
public static string DumpErrors(this IdentityResult result, bool useHtmlNewLine = false)
استفادهی از این متدهای الحاقی را در کنترلرهای برنامه میتوانید مشاهده کنید.
استفادهی از اعتبارسنجها جهت انجام Remote validation
اگر به RegisterControllerدقت کنید، اکشن متدهای ValidateUsername و ValidatePassword قابل مشاهده هستند:
[AjaxOnly, HttpPost, ValidateAntiForgeryToken] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task<IActionResult> ValidateUsername(string username, string email)
[AjaxOnly, HttpPost, ValidateAntiForgeryToken] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public async Task<IActionResult> ValidatePassword(string password, string username)
IPasswordValidator<User> passwordValidator, IUserValidator<User> userValidator,
به این ترتیب کاربران در حین ثبت نام، راهنمای بهتری را جهت انتخاب کلمات عبور و نام کاربری مشاهده خواهند کرد و این بررسیها نیز Ajax ایی هستند و پیش از ارسال فرم نهایی به سرور اتفاق میافتند.
برای فعالسازی Remote validation، علاوه برثبت اسکریپتهای Ajax ایی، خواص کلاس RegisterViewModelنیز از ویژگی Remote استفاده میکنند:
[Required(ErrorMessage = "(*)")] [Display(Name = "نام کاربری")] [Remote("ValidateUsername", "Register", AdditionalFields = nameof(Email) + "," + ViewModelConstants.AntiForgeryToken, HttpMethod = "POST")] [RegularExpression("^[a-zA-Z_]*$", ErrorMessage = "لطفا تنها از حروف انگلیسی استفاده نمائید")] public string Username { get; set; }
یک نکته:برای اینکه Remote Validation را به همراه ValidateAntiForgeryToken استفاده کنیم، تنها کافی است نام فیلد مخفی آنرا به لیست AdditionalFields به نحوی که مشاهده میکنید، اضافه کنیم.
کدهای کامل این سری را در مخزن کد DNT Identityمیتوانید ملاحظه کنید.