سفارشی سازی ASP.NET Core Identity - قسمت سوم - نرمال سازها و اعتبارسنج‌ها
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هشت دقیقه

چندی قبل مطلب «نرمال سازی اطلاعات کاربران در حین ثبت نام» را در سایت جاری مطالعه کردید. پیاده سازی یک چنین قابلیتی به صورت توکار در ASP.NET Core Identity پیش بینی شده‌است. همچنین تمام اعتبارسنج‌های نام‌های کاربران، کلمات عبور آن‌ها، ایمیل‌های آن‌ها و غیره را نیز می‌توان سفارشی سازی کرد و بجای سرویس‌های پیش‌فرض آن‌ها معرفی و جایگزین نمود.


سفارشی سازی نرمال سازها

اگر به طراحی جداول ASP.NET Core Identity دقت کنید، تعدادی فیلد اضافی حاوی کلمه‌ی Normalized را هم مشاهده خواهید کرد. برای مثال:


در جدول کاربران، فیلدهای Email و UserName به همراه دو فیلد اضافه‌ی NormalizedEmail و NormalizedUserName وجود دارند.
مقدار دهی و مدیریت این فیلدهای ویژه به صورت خودکار توسط کلاسی به نام UpperInvariantLookupNormalizer صورت می‌گیرد:
 public class UpperInvariantLookupNormalizer : ILookupNormalizer
این کلاس اینترفیس ILookupNormalizer را پیاده سازی کرده و تنها کاری را که انجام می‌دهد، تبدیل نام کاربر، نام نقش‌ها و یا ایمیل کاربر به حالت upper case آن است. اما هدف اصلی از آن چیست؟
همانطور که در مطلب «نرمال سازی اطلاعات کاربران در حین ثبت نام» نیز عنوان شد، برای مثال ایمیل‌های جی‌میل را می‌توان با چندین حالت مختلف ثبت کرد و یک کاربر به این صورت می‌تواند شرط یکتا بودن آدرس ایمیل‌های تنظیم شده‌ی در کلاس IdentityServicesRegistry را دور بزند:
 identityOptionsUser.RequireUniqueEmail = true;
به همین جهت برای سفارشی سازی آن کلاس CustomNormalizer با سفارشی سازی UpperInvariantLookupNormalizer پیاده سازی شده‌است.
چون تنها یک اینترفیس ILookupNormalizer وجود دارد، باید بر اساس محتوای کلیدی که به آن ارسال می‌شود:
   public override string Normalize(string key)
تصمیم‌گیری کرد که آیا ایمیل است یا خیر. چون از این نرمال کننده هم برای ایمیل‌ها و هم برای نام‌ها استفاده می‌شود. سپس می‌توان منطق‌های سفارشی خود مانند حذف نقطه‌های اضافی ایمیل‌ها و یا حذف کاراکترهای اضافی اعمالی به نام‌های کاربری را اعمال کرد.
پس از تدارک کلاس CustomNormalizer، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
services.AddScoped<ILookupNormalizer, CustomNormalizer>();
services.AddScoped<UpperInvariantLookupNormalizer, CustomNormalizer>();
یکبار CustomNormalizer را به عنوان پیاده سازی کننده‌ی ILookupNormalizer معرفی کرده‌ایم. همچنین یکبار هم سرویس توکار UpperInvariantLookupNormalizer را به سرویس سفارشی خودمان هدایت کرده‌ایم. به این ترتیب مطمئن خواهیم شد که همواره از CustomNormalizer ما استفاده خواهد شد.
بنابراین دیگر نیازی نیست تا در حین ثبت‌نام نسبت به تمیزسازی ایمیل‌ها و یا نام‌های کاربری اقدام کنیم. سرویس ILookupNormalizer در پشت صحنه به صورت خودکار در تمام مراحل ثبت نام و به روز رسانی‌ها توسط ASP.NET Core Identity استفاده می‌شود.


سفارشی سازی UserValidator

ASP.NET Core Identity به همراه یک سرویس توکار اعتبارسنج کاربران است که با پیاده سازی اینترفیس IUserValidator ارائه شده‌است:
 public class UserValidator<TUser> : IUserValidator<TUser> where TUser : class
این سرویس پیش‌فرض و توکار، تنظیمات Options.User.RequireUniqueEmail، Options.User.AllowedUserNameCharacters و امثال آن‌را در مورد نام‌های کاربری و ایمیل‌ها بررسی می‌کند (تنظیم شده‌ی در متد setUserOptions کلاس IdentityServicesRegistry).
بنابراین اگر قصد تهیه‌ی یک 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());
        }
در اینجا برای مثال در متد validateEmail سفارشی تهیه شده، لیست یک سری fake email provider اضافه شده‌اند (مدخل EmailsBanList در فایل appsettings.json برنامه) تا کاربران نتوانند از آن‌ها جهت ثبت‌نام استفاده کنند و یا در متد validateUserName سفارشی، اگر نام کاربری برای مثال عددی وارد شده بود، یک new IdentityError بازگشت داده می‌شود.

پس از تدارک کلاس CustomUserValidator، تنها کاری را که باید در جهت معرفی و جایگرینی آن انجام داد، تغییر ذیل در کلاس IdentityServicesRegistry است:
 services.AddScoped<IUserValidator<User>, CustomUserValidator>();
services.AddScoped<UserValidator<User>, CustomUserValidator>();
یکبار CustomUserValidator را به عنوان پیاده سازی کننده‌ی IUserValidator معرفی کرده‌ایم. همچنین یکبار هم سرویس توکار UserValidator را به سرویس سفارشی خودمان هدایت کرده‌ایم. به این ترتیب مطمئن خواهیم شد که همواره از CustomUserValidator ما استفاده خواهد شد (حتی اگر UserValidator اصلی از سیستم تزریق وابستگی‌ها درخواست شود).


سفارشی سازی PasswordValidator

مراحل سفارشی سازی اعتبارسنج کلمات عبور نیز همانند تهیه‌ی CustomUserValidator فوق است.
ASP.NET Core Identity به همراه یک سرویس توکار اعتبارسنج کلمات عبور کاربران است که با پیاده سازی اینترفیس IPasswordValidator ارائه شده‌است:
 public class PasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
در این کلاس، از اطلاعات متد setPasswordOptions کلاس IdentityServicesRegistry
        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;
        }
که از فایل appsettings.json و مدخل PasswordOptions آن تامین می‌شود:
"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>();
یکبار CustomPasswordValidator را به عنوان پیاده سازی کننده‌ی IPasswordValidator معرفی کرده‌ایم. همچنین یکبار هم سرویس توکار PasswordValidator را به سرویس سفارشی خودمان هدایت کرده‌ایم. به این ترتیب مطمئن خواهیم شد که همواره از CustomPasswordValidator ما استفاده خواهد شد (حتی اگر PasswordValidator اصلی از سیستم تزریق وابستگی‌ها درخواست شود).


پردازش نتایج اعتبارسنج‌ها

این اعتبارسنج‌ها در خروجی‌های IdentityResult تمام متدهای ASP.NET Core Identity ظاهر می‌شوند. بنابراین فراخوانی ساده‌ی UpdateUserAsync اشتباه است و حتما باید خروجی آن‌را جهت پردازش IdentityResult آن بررسی کرد. به همین جهت تعدادی متد الحاقی به کلاس IdentityExtensions اضافه شده‌اند تا کارکردن با IdentityResult را ساده‌تر کنند.
   public static void AddErrorsFromResult(this ModelStateDictionary modelStat, IdentityResult result)
متد AddErrorsFromResult خطاهای حاصل از عملیات ASP.NET Core Identity را به ModelState جاری اضافه می‌کند. به این ترتیب می‌توان این خطاها را به کاربر در Viewهای برنامه و در قسمت اعتبارسنجی مدل آن نمایش داد.

   public static string DumpErrors(this IdentityResult result, bool useHtmlNewLine = false)
و یا متد DumpErrors تمام خطاهای موجود در IdentityResult  را تبدیل به یک رشته می‌کند. برای مثال می‌توان این رشته را در Remote validationها مورد استفاده قرار داد.
استفاده‌ی از این متدهای الحاقی را در کنترلرهای برنامه می‌توانید مشاهده کنید.


استفاده‌ی از اعتبارسنج‌ها جهت انجام 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,
تزریق شده‌ی به سازنده‌ی کلاس، پیاده سازی شده‌اند. در مورد تامین آن‌ها و سفارشی سازی آن‌ها نیز پیشتر بحث شد. این اینترفیس‌ها دقیقا همان وهله‌های CustomUserValidator و CustomPasswordValidator را در اختیار ما قرار می‌دهند. تنها کاری را که باید انجام دهیم، فراخوانی متد ValidateAsync آن‌ها است. این متد یک خروجی از نوع IdentityResult را دارد. به همین جهت متد DumpErrors را برای پردازش این نتیجه تدارک دیدیم.
به این ترتیب کاربران در حین ثبت نام، راهنمای بهتری را جهت انتخاب کلمات عبور و نام کاربری مشاهده خواهند کرد و این بررسی‌ها نیز 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 می‌توانید ملاحظه کنید.
  • #
    ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۲۶ تیر ۱۳۹۷، ساعت ۱۹:۵۴
    سلام؛ من توی این پروژه می‌خواستم AutoMapper  استفاده کنم. می‌خواستم بدونم که AutoMapper رو باید در کدام لایه قرار داد و از آن استفاده کرد. و برای تنظیمات که باید در Startup انجام داد باید چکاری کرد. ممنون   
  • #
    ‫۶ سال و ۱ ماه قبل، یکشنبه ۲۸ مرداد ۱۳۹۷، ساعت ۲۱:۲۷
    با سلام.
    در پروژه ای ما باید یک ثبت نام اولیه برای کاربر انجام دهیم به این شکل که کاربر را با حداقل اطلاعات عضو سیستم می‌کنیم و پس از اولین لاگین اطلاعات تکمیلی ثبت نام رو می‌گیریم.
                // Extending the built-in validator
                validateEmail(user, errors);

    بنده این تابع رو بکلی غیر فعال کردم ولی بازهم ایمیل رو اعتبار سنجی می‌کنه.
    همین مشکل رو ما در مورد پسورد داریم و باید در مرحله اول یک پسورد ۴ کاراکتری بدیم ولی در مرحله دوم باید یک پسورد ۱۰ کاراکتری داشته باشیم.
    در مورد پسورد یک راه پیدا کردیم و اونم اینه که پسورد هش شده رو در دیتابیس ذخیره کنیم ولی دنبال راه حل بهتری هستم.
    آیا کلا راهی وجود داره تا بدون نیاز به Migration و update datatbase سیستم اعتبار سنجی رو برای برخی توابع فعال و غیر فعال کرد؟
    با تشکر
    • #
      ‫۶ سال و ۱ ماه قبل، یکشنبه ۲۸ مرداد ۱۳۹۷، ساعت ۲۲:۳۵
      اعتبارسنجی‌های اولیه‌، در فراخوانی base.ValidateAsync توسط خود Identity انجام می‌شود؛ این base به منطق پیش‌فرض Identity اشاره می‌کند که در اینجا لغو نشده‌است (همان قسمت «... بنابراین اگر قصد تهیه‌ی یک IUserValidator جدید را داشته باشیم، از تمام تنظیمات و بررسی‌های پیش فرض سرویس توکار UserValidator فوق محروم می‌شویم ... »  سفارشی سازی UserValidator مطلب جاری). اگر می‌خواهید این منطق پیش‌فرض را به طور کامل بازنویسی کنید، تمام ارجاعات به base را در CustomUserValidator که جایگزین کامل اعتبارسنج اصلی است، حذف کنید.
      همچنین RequireUniqueEmail را هم مد نظر داشته باشید.
  • #
    ‫۵ سال و ۸ ماه قبل، جمعه ۱۴ دی ۱۳۹۷، ساعت ۰۳:۲۷
    سلام 
    من از کلاس CustomUserValidator استفاده کردم و تمام موارد صحیح است بجز یک حالت اگر در اینپوت Username ما نام کاربری را اشتباه وارد کنیم خطای این قسمت صحیح است منتها دو خطای دیگر صادر میشود که ایمیل معتبر نیست و ... . در واقع در زمان خطاهای سمت کلاینت وقتیکه در اینپوت Username خطا صادر شود خطاهای قسمت ایمیل هم نمایش داده میشود و به همچنین برای اینپوت Email . چطور میتونم فقط خطای مربوط به اینپوتی را که فوکوس در آن است نمایش دهم نه هر دو اینپوت ؟ممنون
    • #
      ‫۵ سال و ۸ ماه قبل، جمعه ۱۴ دی ۱۳۹۷، ساعت ۰۴:۳۹
      این مورد بیشتر به طراحى خود اعتبارسنج این فریم ورک مرتبط هست که همه را یکجا و با هم نیاز دارد. userValidator «کل» اطلاعات شىء کاربر را اعتبارسنجى می‌کند و تمام فیلدهاى آن‌‌را با هم (^ و ^). اگر این مورد مدنظر شما نیست یک remote validation جدید را برای آن طراحی کنید.
      • #
        ‫۵ سال و ۸ ماه قبل، جمعه ۱۴ دی ۱۳۹۷، ساعت ۱۴:۵۰
        ممنون؛ من بدین صورت عمل کردم که دوتا ولیدیتور جداگانه برای ایمیل و نام کاربری در نظر گرفتم. هر دو متد ValidateAsync را override می‌کنن . و در startup من هر دو کلاس تعریفی CustomUserValidator و CustomEmailValidator را تزریق کردم و برای هر کدام در کنترلر Account یک متد جداگانه برای remote در نظر گرفتم. اما فقط از متد ValidateAsync موجود در CustomEmailValidator استفاده میکنه. یعنی کلاسی که در آخر override شده است. کدوم قسمت رو درست نفهمیدم که این متد رو درست تفکیک نمیکنه؟
        • #
          ‫۵ سال و ۸ ماه قبل، جمعه ۱۴ دی ۱۳۹۷، ساعت ۱۵:۵۸
          - سیستم Identity فقط با یک نمونه‌ی از IUserValidator کار می‌کند. 
          - اگر چندین پیاده سازی یک اینترفیس را به سیستم تزریق وابستگی‌ها معرفی کنید، استفاده‌ی از آن‌ها نکات خاصی را به همراه دارند که در سری مهارت‌های تزریق وابستگی‌های NET Core. بحث خواهند شد.
          - زمانیکه قصد ندارید از IUserValidator پیش‌فرض این سیستم در remote validation خاص یک نقطه‌ی ویژه‌ی برنامه استفاده کنید، نیازی هم به تعریف یا سفارشی سازی آن ندارید. منطق سفارشی خودتان را به هر نحوی که مایل بودید تعریف کنید، چون جای دیگری استفاده نخواهد شد. یک سرویس Validator جدید خاص خودتان را تعریف کنید که دو متد بررسی تعیین اعتبار کاربر یا ایمیل را داشته باشد. سپس این سرویس را به صورت مستقل و جدای از IUserValidator سیستم Identity تزریق و استفاده کنید. دستکاری IUserValidator خود Identity، قسمت‌های دیگر سیستم شما را هم تحت تاثیر قرار خواهد داد.
          • #
            ‫۵ سال و ۸ ماه قبل، جمعه ۱۴ دی ۱۳۹۷، ساعت ۲۲:۰۰
            ممنون؛ در واقع من اگر بخام بعد از postback یکسری ولیدیشن اختصاصی برای پسورد و یوزرنیم و ایمیل داشته باشم میتونم یک کلاس که از IUserValidator و IPasswordValidator ارثبری میکنه رو بسازم و ولیدیشن‌ها رو اونجا انجام بدم . برای نمایش ولیدیشن‌های خاص سمت کلاینت در کنترلر و خاصیت Remote استفاده کنم؟
            و اینکه چه تفاوتی بین این دوتا خط کد هست ؟اینکه من بیام و فرضا کلاس CustomUserValidator رو بصورت Addscoped تزریق کنم یا بیام زمانیکه سرویس آیدنتیتی رو اضافه میکنم از AddUserValidator استفاده کنم و کلاس CustomUserValidator رو بهش معرفی کنم ؟در حالت دوم داره یک Extension Builder میسازه و خودش تزریق رو انجام میده؟
            • #
              ‫۵ سال و ۸ ماه قبل، جمعه ۱۴ دی ۱۳۹۷، ساعت ۲۲:۱۶
              - اکثر نکات مطلب «اعتبار سنجی اطلاعات ورودی در فرم‌های ASP.NET MVC » در اینجا هم کار می‌کند.
              - تفاوتی ندارند. هر دو در نهایت یک کار را انجام می‌دهند. یکی سرویسی را به سیستم اضافه می‌کند. دیگری همان سرویس موجود را با نمونه‌ی جدید سفارشی سازی شده بازنویسی می‌کند.
  • #
    ‫۵ سال و ۴ ماه قبل، یکشنبه ۸ اردیبهشت ۱۳۹۸، ساعت ۱۶:۱۰
    با سلام؛ بنده تغییراتی در ساختار پروژه دادم و متاسفانه اکنون در زمان اجرای پروژه در کلاس IdentityServicesRegistry خط زیر زمان ورود به کلاس AddCustomServicesExtensions.AddCustomServices 
    public static void AddCustomServices(this IServiceCollection services)
            {
                var siteSettings = GetSiteSettings(services);
                AddCustomServicesExtensions.AddCustomServices(services);
    به ارور
    Could not load type 'Identity.CustomNormalizer' from assembly ', Version=1.0.0.0,
     Culture=neutral, PublicKeyToken=null' because the parent type is sealed.
    بر می‌خورم. در اینترنت بررسی کردم و نام کلاس، و بیشتر کارهای پیشنهادی رو انجام دادم ولی متاسفانه ارور برطرف نشد. کلاس CustomNormalizer   تغییر داده نشده و مانند پروژه پیشفرض هست. تمامی این کد‌های در پروژه دیگه به درستی کار می‌کنه ولی با انتقال به این پروژه و تغییرات دیتابیس به این ارور می‌خوریم. لازم به ذکر هست که نسخه کور مورد استفاده، 3 پیش‌نمایش 4 می‌باشد.
    • #
      ‫۵ سال و ۴ ماه قبل، یکشنبه ۸ اردیبهشت ۱۳۹۸، ساعت ۱۶:۳۵
      علت اینجا است که کلاس UpperInvariantLookupNormalizer را اینبار sealed تعریف کردند و قابل ارث بری نیست:
      public sealed class UpperInvariantLookupNormalizer : ILookupNormalizer
      بنابراین کلاس CustomNormalizer فعلی را که از آن ارث بری می‌کند، اینبار با پیاده سازی کامل اینترفیس تغییر یافته‌ی ILookupNormalizer تکمیل کنید (با همان جزئیات پیاده سازی قبلی آن؛ فقط نام متدها تغییر می‌کنند و در اینجا نسبت به نگارش 2.2، دو متد را باید پیاده سازی کرد که پیشتر فقط یک متد Normalize عمومی بود).
      بنابراین امضای جدید کلاس قبلی به این صورت تغییر می‌کند و نیاز به پیاده سازی دو متد جدید NormalizeName و NormalizeEmail را دارد که بدنه‌ی این متدها همان متد Normalize قبلی است :
      public class CustomNormalizer : ILookupNormalizer
      • #
        ‫۵ سال و ۴ ماه قبل، یکشنبه ۸ اردیبهشت ۱۳۹۸، ساعت ۱۸:۲۶
        تغییرات مطابق موارد زیر داده شد ولی باز هم همان ارور 
        public class CustomNormalizer : ILookupNormalizer
            {
                public string NormalizeName(string key)
                {
                    key = Normalize(key);
                    key = key.ApplyCorrectYeKe()
                             .RemoveDiacritics()
                             .CleanUnderLines()
                             .RemovePunctuation();
                    key = key.Trim().Replace(" ", "");
                    return key;
                }
                public string NormalizeEmail(string key)
                {
                    key = Normalize(key);
                    key = fixGmailDots(key);
                    return key;
                }
                public string Normalize(string key)
                {
                    if (string.IsNullOrWhiteSpace(key))
                    {
                        return null;
                    }
        
                    key = key.Trim();
                    key = key.ToUpperInvariant();
                    return key;
                }
        
                private static string fixGmailDots(string email)
                {
                    email = email.ToLowerInvariant().Trim();
                    var emailParts = email.Split('@');
                    var name = emailParts[0].Replace(".", string.Empty);
        
                    var plusIndex = name.IndexOf("+", StringComparison.OrdinalIgnoreCase);
                    if (plusIndex != -1)
                    {
                        name = name.Substring(0, plusIndex);
                    }
        
                    var emailDomain = emailParts[1];
                    emailDomain = emailDomain.Replace("googlemail.com", "gmail.com");
        
                    string[] domainsAllowedDots =
                    {
                        "gmail.com",
                        "facebook.com"
                    };
        
                    var isFromDomainsAllowedDots = domainsAllowedDots.Any(domain => emailDomain.Equals(domain));
                    return !isFromDomainsAllowedDots ? email : string.Format("{0}@{1}", name, emailDomain);
                }
            }

        اینترفیس ILookupNormalizer   به شکل زیر است

        #region Assembly Microsoft.Extensions.Identity.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
        // C:\Users\kazemi\.nuget\packages\microsoft.extensions.identity.core\3.0.0-preview-18579-0056\lib\netstandard2.0\Microsoft.Extensions.Identity.Core.dll
        #endregion
        
        namespace Microsoft.AspNetCore.Identity
        {
            //
            // Summary:
            //     Provides an abstraction for normalizing keys for lookup purposes.
            public interface ILookupNormalizer
            {
                //
                // Summary:
                //     Returns a normalized representation of the specified key.
                //
                // Parameters:
                //   key:
                //     The key to normalize.
                //
                // Returns:
                //     A normalized representation of the specified key.
                string Normalize(string key);
            }
        }

        • #
          ‫۵ سال و ۴ ماه قبل، یکشنبه ۸ اردیبهشت ۱۳۹۸، ساعت ۱۹:۰۳
          این شماره‌ای که ارسال کردید «core\3.0.0-preview-18579-0056\lib» مربوط به 5 ماه قبل هست و با نگارش «3 پیش‌نمایش 4» ای که عنوان کردید سازگاری ندارد. اگر مطمئن هستید که SDK درستی را نصب کردید، نیاز است تمام اسمبلی‌های تمام پروژه‌ها را هم یک‌دست کنید (پس از اصلاح دستی تمام TargetFramework‌های موجود).
          dotnet tool update --global dotnet-outdated
          dotnet outdated -u
        • #
          ‫۵ سال و ۴ ماه قبل، دوشنبه ۹ اردیبهشت ۱۳۹۸، ساعت ۱۹:۰۵
          یک نکته‌ی تکمیلی: چگونه پس از نصب SDK 3x جدید، بتوانیم همان پروژه‌ی قبلی را بدون به روز رسانی یا هیچگونه تغییری در آن، باز هم استفاده کنیم؟

          با توجه به اینکه امضای یکسری از اینترفیس‌های نگارش 3x با 2x یکی نیست (مانند ILookupNormalizer)، پس از نصب SDK 3x، دیگر قادر به اجرای برنامه‌های 2x خود نخواهید شد؛ چون پروژه‌های NET Core. همواره از آخرین نگارش SDK نصب شده استفاده می‌کنند. برای قفل کردن شماره SDK یک Solution به نگارش 2x به صورت زیر عمل کنید:
          dotnet --list-sdks
          dotnet new globaljson --sdk-version 2.2.106
          دستور اول لیست SDKهای نصب شده را نمایش می‌دهد و دستور دوم یک فایل global.json جدید را بر اساس شماره‌ای که از طریق اجرای دستور اول یافته‌اید، تولید می‌کند. این فایل باید در ریشه‌ی Solution قرار گیرد.
  • #
    ‫۵ سال و ۱ ماه قبل، چهارشنبه ۲۳ مرداد ۱۳۹۸، ساعت ۱۶:۰۹
    ارتقاء به ASP.NET Core Identity 3.0

    امضای اینترفیس ILookupNormalizer، از حالت کلی زیر، که هم ایمیل و هم نام را شامل می‌شد:
        public interface ILookupNormalizer
        {
            string Normalize(string key);
        }
    به حالت اختصاصی‌تر زیر تغییر کرده‌است:
        public interface ILookupNormalizer
        {
            string NormalizeEmail(string email);
            string NormalizeName(string name);
        }
  • #
    ‫۴ سال و ۹ ماه قبل، یکشنبه ۳ آذر ۱۳۹۸، ساعت ۱۶:۴۴
    در این پروژه اگر بخواهیم زبان انگلیسی هم مد نظر قرار بدیم آیا امکانش هست؟ چون با دو زبانه کردن در موارد validationها عمل نمیکند مثلا در Required(*) نمیتوان بجای * عبارت مورد نظرمون براش نوشت و اگرنوشته بشه در حالت زبان فارسی فقط صفحه سفید نمایش میده.
    و اگر این پروژه فقط برای زبان فارسی سفارشی سازی شده اگر بخواهیم زبان انگلیسی هم پشتیبانی کنه چه مواردی نیاز نیست استفاده بشه؟ مثلا نرمال ساز  در حالت پیش فرض کافی است یا باید نرمال سازی سفارشی هم باید اعمال بشه؟
  • #
    ‫۴ سال و ۵ ماه قبل، چهارشنبه ۱۳ فروردین ۱۳۹۹، ساعت ۱۷:۳۸
    سلام وقت بخیر
    من یک کاربر با ایمیل abc@gmail.com درج کردم
    برای تست خواستم با a_bc@gmail.com لاگین کنم: 
    متد FindByNameAsync ، کاربر را شناسایی می‌کند، ولی متد FindByEmailAsync متوجه نشده و null برمیگرداند.
    سوال اولم اینکه چرا FindByEmailAsync  از وجود ایمیل مطلع نمی‌شود و باید چه کار کرد که متوجه بشود؟ (یعنی وقتی متد  FindByEmailAsync  با مقدار a_bc@gmail.com صدا زده شد این متد کاربر ثبت شده با ایمیل abc@gmail.com را برگرداند)
    توضیح: اینکه من ایمیل و نام کاربری رو یکی تعریف کردم.(email == username)
    سوال دوم اینکه، اگر بخواهیم نام کاربری و ایمیل یکی باشد، نیاز هست که حتما UserName و فیلد نرمال شده آن در دیتابیس تعریف شده باشند؟
  • #
    ‫۲ سال قبل، پنجشنبه ۱۳ مرداد ۱۴۰۱، ساعت ۱۴:۴۴
    من دو تا برنامه رو با این مخزن کد ایجاد کردم، هشِ رمز عبورِ پروژه اول رو در پایگاه داده دوم کپی کردم و تونستم با رمز عبور پروژه اول در پروژه دوم هم لاگین کنم.
    - آیا این یک مشکل است ؟
    - اگر مشکل است، برای رفع آن اگر در کلاس PasswordValidator، رمز عبور را با Salt خودم هش کنم مشکل حل میشود ؟
    و در نهایت ممنون میشم بگید که محل ذخیره سازی Salt باید در کد‌های سی شارپی باشه یا در یک فایل txt ؟
    • #
      ‫۲ سال قبل، پنجشنبه ۱۳ مرداد ۱۴۰۱، ساعت ۱۴:۵۳
      - خیر. به همین صورت است و این مشکلی نیست. تمام الگوریتم‌های هش به همین صورت هستند. اصلا امضای دیجیتال به همین معنا است. قرار نیست امضای دیجیتال یک محتوا از یک سیستم به سیستم دیگر متفاوت باشد. الگوریتم یاد شده هم به همراه salt هست که در مطلب لینک داده شده نحوه‌ی ذخیره سازی آن ذکر شده.
      + کسی که دسترسی کپی کردن اطلاعاتی را از یک بانک اطلاعاتی، به بانک اطلاعاتی دوم دارد، نیازی به دانستن یا دستکاری هش کلمات عبور را ندارد؛ چون از این مراحل رد شده و دسترسی کاملی را به تمام اجزای بانک اطلاعاتی که برنامه‌ی شما فقط یک پوسته‌ی دسترسی به آن است، دارد.
      + امکان تعویض الگوریتم هش کردن اطلاعات، با پیاده سازی سفارشی IPasswordHasher وجود دارد. اما چون پیاده سازی پیش‌فرض خود ASP.NET Core Identity بسیار عالی است، عموما چنین کاری انجام نمی‌شود.
  • #
    ‫۱ سال و ۱ ماه قبل، یکشنبه ۱ مرداد ۱۴۰۲، ساعت ۲۰:۲۳
    در کنترلر Register نام کاربری تکراری باشه دو بار مینویسه "نام کاربری x هم اکنون مورد استفاده است." دلیلش چیه ؟ چطور میشه فقط یکبار متن خطارو نمایش بدیم. (پروژه DntIdentity اجرا کردم و موردی رو هم تغییر ندادم). برای تست این بخش، اعتبار سنجی سمت کلاینت رو غیر فعال کنید و نام کاربری تکراری وارد کنید.
    services.AddMvc(options => options.UseYeKeModelBinder())
            .AddViewOptions(options => options.HtmlHelperOptions.ClientValidationEnabled = false);  
    foreach (var error in result.Errors)
    {
        ModelState.AddModelError(string.Empty, error.Description);
    }
    نام کاربری 'admin' هم اکنون مورد استفاده‌است.
    نام کاربری 'admin' هم اکنون مورد استفاده‌است.
    • #
      ‫۱ سال و ۱ ماه قبل، دوشنبه ۲ مرداد ۱۴۰۲، ساعت ۱۷:۱۰
      از متد DumpErrors استفاده کنید.