در قسمت قبل کلاسهای متناظر با جداول پایهی ASP.NET Core Identity را تغییر دادیم. اما هنوز سرویسهای پایهی این فریم ورک مانند مدیریت و ذخیرهی کاربران و مدیریت و ذخیرهی نقشها، اطلاعی از وجود آنها ندارند. در ادامه این سرویسها را نیز سفارشی سازی کرده و سپس به ASP.NET Core Identity معرفی میکنیم.
سفارشی سازی RoleStore
ASP.NET Core Identity دو سرویس را جهت کار با نقشهای کاربران پیاده سازی کردهاست:
- سرویس RoleStore: کار آن دسترسی به ApplicationDbContext ایی است که در قسمت قبل سفارشی سازی کردیم و سپس ارائهی زیر ساختی به سرویس RoleManager جهت استفادهی از آن برای ذخیره سازی و یا تغییر و حذف نقشهای سیستم.
وجود Storeها از این جهت است که اگر علاقمند بودید، بتوانید از سایر ORMها و یا زیرساختهای ذخیره سازی اطلاعات برای کار با بانکهای اطلاعاتی استفاده کنید. در اینجا از همان پیاده سازی پیشفرض آن که مبتنی بر EF Core است استفاده میکنیم. به همین جهت است که وابستگی ذیل را در فایل
project.json مشاهده میکنید:
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0",
- سرویس RoleManager: این سرویس از سرویس RoleStore و تعدادی سرویس دیگر مانند اعتبارسنج نام نقشها و نرمال ساز نام نقشها، جهت ارائهی متدهایی برای یافتن، افزودن و هر نوع عملیاتی بر روی نقشها استفاده میکند.
برای سفارشی سازی سرویسهای پایهی ASP.NET Core Identity، ابتدا باید سورس این مجموعه را جهت یافتن نگارشی از سرویس مدنظر که کلاسهای موجودیت را به صورت آرگومانهای جنریک دریافت میکند، پیدا کرده و سپس از آن ارث بری کنیم:
public class ApplicationRoleStore :
RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>,
IApplicationRoleStore
تا اینجا مرحلهی اول تشکیل کلاس
ApplicationRoleStore سفارشی به پایان میرسد. نگارش جنریک RoleStore پایه را یافته و سپس موجودیتهای سفارشی سازی شدهی خود را به آن معرفی میکنیم.
این ارث بری جهت تکمیل، نیاز به بازنویسی سازندهی RoleStore پایه را نیز دارد:
public ApplicationRoleStore(
IUnitOfWork uow,
IdentityErrorDescriber describer = null)
: base((ApplicationDbContext)uow, describer)
در اینجا پارامتر اول آنرا به IUnitOfWork بجای DbContext متداول تغییر دادهایم؛ چون IUnitOfWork دقیقا از همین نوع است و همچنین امکان دسترسی به متدهای ویژهی آنرا نیز فراهم میکند.
در نگارش 1.1 این کتابخانه، بازنویسی متد CreateRoleClaim نیز اجباری است تا در آن مشخص کنیم، نحوهی تشکیل کلید خارجی و اجزای یک RoleClaim به چه نحوی است:
protected override RoleClaim CreateRoleClaim(Role role, Claim claim)
{
return new RoleClaim
{
RoleId = role.Id,
ClaimType = claim.Type,
ClaimValue = claim.Value
};
}
در نگارش 2.0 آن، این new RoleClaim به صورت خودکار توسط کتابخانه صورت خواهد گرفت و سفارشی کردن آن سادهتر میشود.
در ادامه اگر به انتهای تعریف امضای کلاس دقت کنید، یک اینترفیس
IApplicationRoleStore را نیز مشاهده میکنید. برای تشکیل آن بر روی نام کلاس سفارشی خود کلیک راست کرده و با استفاده از ابزارهای Refactoring کار Extract interface را انجام میدهیم؛ از این جهت که در سایر لایههای برنامه نمیخواهیم از تزریق مستقیم کلاس ApplicationRoleStore استفاده کنیم. بلکه میخواهیم اینترفیس IApplicationRoleStore را در موارد ضروری، به سازندههای کلاسهای تزریق نمائیم.
پس از تشکیل این اینترفیس، مرحلهی بعد، معرفی آنها به سیستم تزریق وابستگیهای ASP.NET Core است. چون تعداد تنظیمات مورد نیاز ما زیاد هستند، یک کلاس ویژه به نام
IdentityServicesRegistry تشکیل شدهاست تا به عنوان رجیستری تمام سرویسهای سفارشی سازی شدهی ما عمل کند و تنها با فراخوانی متد AddCustomIdentityServices آن
در کلاس آغازین برنامه، کار ثبت یکجای تمام سرویسهای ASP.NET Core Identity انجام شود و بیجهت کلاس آغازین برنامه شلوغ نگردد.
ثبت ApplicationDbContext طبق روش متداول آن
در کلاس آغازین برنامه انجام شدهاست. سپس معرفی سرویس IUnitOfWork را که از ApplicationDbContext تامین میشود، در کلاس IdentityServicesRegistry مشاهده میکنید.
services.AddScoped<IUnitOfWork, ApplicationDbContext>();
در ادامه RoleStore سفارشی ما نیاز به دو تنظیم جدید را خواهد داشت:
services.AddScoped<IApplicationRoleStore, ApplicationRoleStore>();
services.AddScoped<RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>, ApplicationRoleStore>();
ابتدا مشخص کردهایم که اینترفیس IApplicationRoleStore، از طریق کلاس سفارشی ApplicationRoleStore تامین میشود.
سپس RoleStore توکار ASP.NET Core Identity را نیز به ApplicationRoleStore خود هدایت کردهایم. به این ترتیب هر زمانیکه در کدهای داخلی این فریم ورک وهلهای از RoleStore جنریک آن درخواست میشود، دیگر از همان پیاده سازی پیشفرض خود استفاده نخواهد کرد و به پیاده سازی ما هدایت میشود.
این روشی است که جهت سفارشی سازی تمام سرویسهای پایهی ASP.NET Core Identity بکار میگیریم:
1) ارث بری از نگارش جنریک سرویس پایهی موجود و معرفی موجودیتهای سفارشی سازی شدهی خود به آن
2) سفارشی سازی سازندهی این کلاسها با سرویسهایی که تهیه کردهایم (بجای سرویسهای پیش فرض).
3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
5) معرفی اینترفیس و همچنین نمونهی توکار این سرویس به سیستم تزریق وابستگیهای ASP.NET Core جهت استفادهی از این سرویس جدید سفارشی سازی شده.
سفارشی سازی RoleManager
در اینجا نیز همان 5 مرحلهای را که عنوان کردیم باید جهت تشکیل کلاس جدید
ApplicationRoleManager پیگیری کنیم.
1) ارث بری از نگارش جنریک سرویس پایهی موجود و معرفی موجودیتهای سفارشی سازی شدهی خود به آن
public class ApplicationRoleManager :
RoleManager<Role>,
IApplicationRoleManager
در اینجا نگارشی از RoleManager را انتخاب کردهایم که بتواند Role سفارشی خود را به سیستم معرفی کند.
2) سفارشی سازی سازندهی این کلاس با سرویسی که تهیه کردهایم (بجای سرویس پیش فرض).
public ApplicationRoleManager(
IApplicationRoleStore store,
IEnumerable<IRoleValidator<Role>> roleValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
ILogger<ApplicationRoleManager> logger,
IHttpContextAccessor contextAccessor,
IUnitOfWork uow) :
base((RoleStore<Role, ApplicationDbContext, int, UserRole, RoleClaim>)store, roleValidators, keyNormalizer, errors, logger, contextAccessor)
در این سفارشی سازی دو مورد را تغییر دادهایم:
الف) ذکر IApplicationRoleStore بجای RoleStore آن
ب) ذکر IUnitOfWork بجای ApplicationDbContext
3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
RoleManager پایه نیازی به پیاده سازی اجباری متدی ندارد.
4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در
IApplicationRoleManager مشاهده میکنید.
5)
معرفی اینترفیس و همچنین نمونهی توکار این سرویس به سیستم تزریق وابستگیهای ASP.NET Core جهت استفادهی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationRoleManager, ApplicationRoleManager>();
services.AddScoped<RoleManager<Role>, ApplicationRoleManager>();
در کلاس IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار RoleManager را به سرویس جدید و سفارشی سازی شدهی ApplicationRoleManager خود هدایت کردهایم.
سفارشی سازی UserStore
در مورد مدیریت کاربران نیز دو سرویس Store و Manager را مشاهده میکنید. کار کلاس Store، کپسوله سازی لایهی دسترسی به دادهها است تا کتابخانههای ثالث، مانند وابستگی Microsoft.AspNetCore.Identity.EntityFrameworkCore بتوانند آنرا پیاده سازی کنند و کار کلاس Manager، استفادهی از این Store است جهت کار با بانک اطلاعاتی.
5 مرحلهای را که باید جهت تشکیل کلاس جدید
ApplicationUserStore پیگیری کنیم، به شرح زیر هستند:
1) ارث بری از نگارش جنریک سرویس پایهی موجود و معرفی موجودیتهای سفارشی سازی شدهی خود به آن
public class ApplicationUserStore :
UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>,
IApplicationUserStore
از بین نگارشهای مختلف UserStore، نگارشی را انتخاب کردهایم که بتوان در آن موجودیتهای سفارشی سازی شدهی خود را معرفی کنیم.
2) سفارشی سازی سازندهی این کلاس با سرویسی که تهیه کردهایم (بجای سرویس پیش فرض).
public ApplicationUserStore(
IUnitOfWork uow,
IdentityErrorDescriber describer = null)
: base((ApplicationDbContext)uow, describer)
در این سفارشی سازی یک مورد را تغییر دادهایم:
الف) ذکر IUnitOfWork بجای ApplicationDbContext
3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
در اینجا نیز تکمیل 4 متد از کلاس پایه UserStore جنریک انتخاب شده، جهت مشخص سازی نحوهی انتخاب کلیدهای خارجی جداول سفارشی سازی شدهی مرتبط با جدول کاربران ضروری است:
protected override UserClaim CreateUserClaim(User user, Claim claim)
{
var userClaim = new UserClaim { UserId = user.Id };
userClaim.InitializeFromClaim(claim);
return userClaim;
}
protected override UserLogin CreateUserLogin(User user, UserLoginInfo login)
{
return new UserLogin
{
UserId = user.Id,
ProviderKey = login.ProviderKey,
LoginProvider = login.LoginProvider,
ProviderDisplayName = login.ProviderDisplayName
};
}
protected override UserRole CreateUserRole(User user, Role role)
{
return new UserRole
{
UserId = user.Id,
RoleId = role.Id
};
}
protected override UserToken CreateUserToken(User user, string loginProvider, string name, string value)
{
return new UserToken
{
UserId = user.Id,
LoginProvider = loginProvider,
Name = name,
Value = value
};
}
در نگارش 2.0 آن، این newها به صورت خودکار توسط خود فریم ورک صورت خواهد گرفت و سفارشی کردن آن سادهتر میشود.
4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در
IApplicationUserStore مشاهده میکنید.
5) معرفی اینترفیس و همچنین نمونهی توکار این سرویس به سیستم تزریق وابستگیهای ASP.NET Core جهت استفادهی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationUserStore, ApplicationUserStore>();
services.AddScoped<UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>, ApplicationUserStore>();
در کلاس
IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار UserStore را به سرویس جدید و سفارشی سازی شدهی ApplicationUserStore خود هدایت کردهایم.
سفارشی سازی UserManager
5 مرحلهای را که باید جهت تشکیل کلاس جدید
ApplicationUserManager پیگیری کنیم، به شرح زیر هستند:
1) ارث بری از نگارش جنریک سرویس پایهی موجود و معرفی موجودیتهای سفارشی سازی شدهی خود به آن
public class ApplicationUserManager :
UserManager<User>,
IApplicationUserManager
از بین نگارشهای مختلف UserManager، نگارشی را انتخاب کردهایم که بتوان در آن موجودیتهای سفارشی سازی شدهی خود را معرفی کنیم.
2) سفارشی سازی سازندهی این کلاس با سرویسی که تهیه کردهایم (بجای سرویس پیش فرض).
public ApplicationUserManager(
IApplicationUserStore store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<User> passwordHasher,
IEnumerable<IUserValidator<User>> userValidators,
IEnumerable<IPasswordValidator<User>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<ApplicationUserManager> logger,
IHttpContextAccessor contextAccessor,
IUnitOfWork uow,
IUsedPasswordsService usedPasswordsService)
: base((UserStore<User, Role, ApplicationDbContext, int, UserClaim, UserRole, UserLogin, UserToken, RoleClaim>)store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
در این سفارشی سازی چند مورد را تغییر دادهایم:
الف) ذکر IUnitOfWork بجای ApplicationDbContext (البته این مورد، یک پارامتر اضافی است که بر اساس نیاز این سرویس سفارشی، اضافه شدهاست)
تمام پارامترهای پس از logger به دلیل نیاز این سرویس اضافه شدهاند و جزو پارامترهای سازندهی کلاس پایه نیستند.
ب) استفادهی از IApplicationUserStore بجای UserStore پیشفرض
3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
UserManager پایه نیازی به پیاده سازی اجباری متدی ندارد.
4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در
IApplicationUserManager مشاهده میکنید.
5) معرفی اینترفیس و همچنین نمونهی توکار این سرویس به سیستم تزریق وابستگیهای ASP.NET Core جهت استفادهی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationUserManager, ApplicationUserManager>();
services.AddScoped<UserManager<User>, ApplicationUserManager>();
در کلاس
IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار UserManager را به سرویس جدید و سفارشی سازی شدهی ApplicationUserManager خود هدایت کردهایم.
سفارشی سازی SignInManager
سرویس پایه SignInManager از سرویس UserManager جهت فراهم آوردن زیرساخت لاگین کاربران استفاده میکند.
5 مرحلهای را که باید جهت تشکیل کلاس جدید
ApplicationSignInManager پیگیری کنیم، به شرح زیر هستند:
1) ارث بری از نگارش جنریک سرویس پایهی موجود و معرفی موجودیتهای سفارشی سازی شدهی خود به آن
public class ApplicationSignInManager :
SignInManager<User>,
IApplicationSignInManager
از بین نگارشهای مختلف SignInManager، نگارشی را انتخاب کردهایم که بتوان در آن موجودیتهای سفارشی سازی شدهی خود را معرفی کنیم.
2) سفارشی سازی سازندهی این کلاس با سرویسی که تهیه کردهایم (بجای سرویس پیش فرض).
public ApplicationSignInManager(
IApplicationUserManager userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<User> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<ApplicationSignInManager> logger)
: base((UserManager<User>)userManager, contextAccessor, claimsFactory, optionsAccessor, logger)
در این سفارشی سازی یک مورد را تغییر دادهایم:
الف) استفادهی از IApplicationUserManager بجای UserManager پیشفرض
3) تکمیل متدهایی که باید به اجبار پس از این ارث بری پیاده سازی شوند.
SignInManager پایه نیازی به پیاده سازی اجباری متدی ندارد.
4) استخراج یک اینترفیس از کلاس نهایی تشکیل شده (توسط ابزارهای Refactoring).
محتوای این اینترفیس را در
IApplicationSignInManager مشاهده میکنید.
5) معرفی اینترفیس و همچنین نمونهی توکار این سرویس به سیستم تزریق وابستگیهای ASP.NET Core جهت استفادهی از این سرویس جدید سفارشی سازی شده.
services.AddScoped<IApplicationSignInManager, ApplicationSignInManager>();
services.AddScoped<SignInManager<User>, ApplicationSignInManager>();
در کلاس
IdentityServicesRegistry، یکبار اینترفیس و یکبار اصل سرویس توکار مدیریت لاگین را به سرویس جدید و سفارشی سازی شدهی ApplicationSignInManager خود هدایت کردهایم.
معرفی نهایی سرویسهای سفارشی سازی شده به ASP.NET Identity Core
تا اینجا سرویسهای پایهی این فریم ورک را جهت معرفی موجودیتهای سفارشی سازی شدهی خود سفارشی سازی کردیم و همچنین آنها را به سیستم تزریق وابستگیهای ASP.NET Core نیز معرفی نمودیم. مرحلهی آخر،
ثبت این سرویسها در رجیستری ASP.NET Core Identity است:
services.AddIdentity<User, Role>(identityOptions =>
{
}).AddUserStore<ApplicationUserStore>()
.AddUserManager<ApplicationUserManager>()
.AddRoleStore<ApplicationRoleStore>()
.AddRoleManager<ApplicationRoleManager>()
.AddSignInManager<ApplicationSignInManager>()
// You **cannot** use .AddEntityFrameworkStores() when you customize everything
//.AddEntityFrameworkStores<ApplicationDbContext, int>()
.AddDefaultTokenProviders();
اگر منابع را مطالعه کنید، تمام آنها از AddEntityFrameworkStores و سپس معرفی ApplicationDbContext به آن استفاده میکنند. با توجه به اینکه ما همه چیز را در اینجا سفارشی سازی کردهایم، فراخوانی متد افزودن سرویسهای EF این فریم ورک، تمام آنها را بازنویسی کرده و به حالت اول و پیش فرض آن بر میگرداند. بنابراین نباید از آن استفاده شود.
در اینجا متد AddIdentity یک سری تنظیمهای پیش فرضها این فریم ورک مانند اندازهی طول کلمهی عبور، نام کوکی و غیره را در اختیار ما قرار میدهد به همراه ثبت تعدادی سرویس پایه مانند نرمال ساز نامها و ایمیلها. سپس توسط متدهای AddUserStore، AddUserManager و ... ایی که مشاهده میکنید، سبب بازنویسی سرویسهای پیش فرض این فریم ورک به سرویسهای سفارشی خود خواهیم شد.
در این مرحلهاست که اگر Migration را اجرا کنید، کار میکند و خطای تبدیل نشدن کلاسها به یکدیگر را دریافت نخواهید کرد.
تشکیل مرحله استفادهی از ASP.NET Core Identity و ثبت اولین کاربر در بانک اطلاعاتی به صورت خودکار
روال متداول کار با امکانات کتابخانههای نوشته شدهی برای ASP.NET Core، ثبت سرویسهای پایهی آنها توسط متدهای Add است که نمونهی services.AddIdentity فوق دقیقا همین کار را انجام میدهد. مرحلهی بعد به app.UseIdentity میرسیم که کار ثبت میانافزارهای این فریم ورک را انجام میدهد. متد
UseCustomIdentityServices کلاس IdentityServicesRegistry اینکار را انجام میدهد که از آن
در کلاس آغازین برنامه استفاده شدهاست.
public static void UseCustomIdentityServices(this IApplicationBuilder app)
{
app.UseIdentity();
var identityDbInitialize = app.ApplicationServices.GetService<IIdentityDbInitializer>();
identityDbInitialize.Initialize();
identityDbInitialize.SeedData();
}
در اینجا یک مرحلهی استفادهی از سرویس IIdentityDbInitializer را نیز مشاهده میکنید. کلاس
IdentityDbInitializer این اهداف را برآورده میکند:
الف)
متد Initialize آن، متد context.Database.Migrate را فراخوانی میکند. به همین جهت دیگر نیاز به اعمال دستی حاصل Migrations، به بانک اطلاعاتی نخواهد بود. متد Database.Migrate هر مرحلهی اعمال نشدهای را که باقی مانده باشد، به صورت خودکار اعمال میکند.
ب)
متد SeedData آن، به نحو صحیحی یک Scope جدید را ایجاد کرده و توسط آن به ApplicationDbContext دسترسی پیدا میکند تا نقش Admin و کاربر Admin را به سیستم اضافه کند. همچنین به کاربر Admin، نقش Admin را نیز انتساب میدهد. تنظیمات این کاربران را نیز از فایل appsettings.json و مدخل
AdminUserSeed آن دریافت میکند.
کدهای کامل این سری را در مخزن کد
DNT Identity میتوانید ملاحظه کنید.