_categoryService.AddNewCategory(category); _uow.SaveAllChanges(); throw new InvalidOperationException(); return RedirectToAction("Index");
public interface IRunAtInit { void Execute(); } public interface IRunAfterEachRequest { void Execute(); } public interface IRunAtStartUp { void Execute(); } public interface IRunOnEachRequest { void Execute(); } public interface IRunOnError { void Execute(); }
public class TaskRegistry : StructureMap.Configuration.DSL.Registry { public TaskRegistry() { Scan(scan => { scan.Assembliy("yourAssemblyName"); scan.AddAllTypesOf<IRunAtInit>(); scan.AddAllTypesOf<IRunAtStartUp>(); scan.AddAllTypesOf<IRunOnEachRequest>(); scan.AddAllTypesOf<IRunOnError>(); scan.AddAllTypesOf<IRunAfterEachRequest>(); }); } }
ioc.AddRegistry(new TaskRegistry());
protected void Application_Start() { // other code foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAtInit>()) { task.Execute(); } } protected void Application_BeginRequest() { foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnEachRequest>()) { task.Execute(); } } protected void Application_EndRequest(object sender, EventArgs e) { try { foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAfterEachRequest>()) { task.Execute(); } } finally { HttpContextLifecycle.DisposeAndClearAll(); MiniProfiler.Stop(); } } protected void Application_Error() { foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnError>()) { task.Execute(); } }
public class TransactionPerRequest : IRunOnEachRequest, IRunOnError, IRunAfterEachRequest { private readonly IUnitOfWork _uow; private readonly HttpContextBase _httpContext; public TransactionPerRequest(IUnitOfWork uow, HttpContextBase httpContext) { _uow = uow; _httpContext = httpContext; } void IRunOnEachRequest.Execute() { _httpContext.Items["_Transaction"] = _uow.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted); } void IRunOnError.Execute() { _httpContext.Items["_Error"] = true; } void IRunAfterEachRequest.Execute() { var transaction = (DbContextTransaction) _httpContext.Items["_Transaction"]; if (_httpContext.Items["_Error"] != null) { transaction.Rollback(); } else { transaction.Commit(); } } }
_categoryService.AddNewCategory(category); _uow.SaveAllChanges(); throw new InvalidOperationException(); return RedirectToAction("Index");
طراحی لایه DataAccess
در این نوع طراحی، هر لایه تنها از طریق Interfaceها به لایه بالاتر از خودش سرویس میدهد و در مواردی که ما در نظر بگیریم، میتوانیم به لایههای دیگر نیز دسترسیهای غیر مستقیم و کنترل شدهای را بدهیم. بطور مثال هر کلاس Repository میتواند بهصورت Internal تعریف شود؛ پس تنها در لایه DataAccess در دسترس است. برای دسترسی سایر لایهها به Repositoryها، هر Repository میتواند از یک IRepository (این Interface دسترسی خواندنی نوشتنی به کلاس Repository دارد) که در لایه DataAccess بصورت public تعریف شده و تنها در لایه Business قابل دسترس است و یک IRepositoryReadOnly (این Interface دسترسی فقط خواندنی به کلاس Repository دارد) که در قسمت Common تعریف شده، ارث ببرد. به این صورت همان طور که در شکل بالا نیز میبینید، دسترسیهایی که از بیرون به لایه DataAccess صورت میگیرد، به دو قسمت تقسیم میشوند: اول دسترسی کامل به IRepository که تنها از طریق Business صورت میپذیرد و دوم دسترسی از طریق IRepositoryReadOnly که میتواند از هر جایی در سیستم که به قسمت Common دسترسی دارد صورت بپذیرد (البته استفاده از IRepositoryReadOnly به سیاستهایی که شما در نظر میگیرید بستگی دارد). با این کار مطمئن میشوید که تغییرات تنها میتوانند از طریق Business صورت بپذیرند. همچنین حتی در صورتیکه نیاز بدانید، یک دسترسی فقط خواندنی را نیز به توسعه دهندگان سایر قسمتها دادهاید.
حال با توجه به توضیحات فوق، تنظیمات مربوط به Autofac را در لایه DataAccess انجام میدهیم.
طراحی تنظیمات Autofac در لایه DataAccess
public class DataAccessSetupDependency : Autofac.Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterType<EFContext>().As<IDbContext>(); builder.RegisterType<UnitOfWork>().As<IUnitOfWork>(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(x => x.Namespace.EndsWith("Repositories")) .AsImplementedInterfaces(); } }
طراحی لایه Business
در لایه Business نیز دسترسیهای خود را تنها از طریق Interface هایی که کلاسهای Business از آنها ارث بردهاند و آنها را پیاده سازی کردهاند، به قسمت Web میدهید.
طراحی تنظیمات Autofac در لایه Business
public class BusinessSetupDependency : Autofac.Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterAssemblyModules(typeof(SqlServerDataAccess.SetupDependencies.DataAccessSetupDependency).Assembly); //builder.RegisterAssemblyModules(typeof(CassandraDataAccess.SetupDependecies.SetupDependency).Assembly); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(x => x.Namespace.EndsWith("Business.Core")) .AsImplementedInterfaces(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .Where(x => x.Namespace.EndsWith("Business.Businesses")) .AsImplementedInterfaces(); } }
طراحی لایه Web
جمع بندی تمام تنظیمات Autofac در لایه Web
public class WebSetupDependency : Autofac.Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterAssemblyModules(typeof(Business.SetupDependencies.BusinessSetupDependency).Assembly); builder.RegisterControllers(Assembly.GetExecutingAssembly()); //سایر تنظیمات var container = builder.Build(); CommonDependencyResolver.SetContainer(container); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); } } public class AutofacConfig { public static void SetupContainer() { var builder = new ContainerBuilder(); builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly()); var container = builder.Build(); CommonDependencyResolver.SetContainer(container); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); } }
همچنین کلاس AutofacConfig مسئول جمع بندی تمام ماژولها و ایجاد Container آنهاست و سپس این Container را در DependencyResolver ثبت میکند. نکتهای را که باید در اینجا در نظر بگیرید، CommonDependencyResolver است که مسئول ثبت Container در قسمت Common است. به این صورت دیگر تنها لایهای که به Container دسترسی دارد، لایه Web نیست. در واقع با ثبت Container در قسمت Common شما دسترسی کنترل شدهای را از Container به سایر لایههای سیستم دادهاید و در این حالت در صورتیکه لایههای دیگر مانند DataAccess یا Business به Container نیاز پیدا کنند، میتوانند از طریق CommonDependencyResolver این دسترسی را داشته باشند.
جمع بندی
با طراحی ماژولار تنظیمات Autofac و استفاده از Interfaceها برای ارتباط با دیگر قسمتها، دیگر نیازی نیست دسترسیهای بیموردی از یک لایه را به سایر قسمتها داد و دیگر نیازی نیست شما نگران این باشید که ممکن است یکی از توسعه دهندگان بهدلایلی مانند کم تجربگی، کاری را خارج از زیرساختی که شما یا گروه پیاده سازی زیرساخت، پیاده سازی کردهاید انجام دهد. همه چیز آنطور که شما میخواهید و برنامه ریزی کردهاید، انجام میشود.
public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, CancellationToken cancellationToken, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, CancellationToken cancellationToken, Func<TSource, CancellationToken, ValueTask> body) public static Task ForEachAsync<TSource>(IAsyncEnumerable<TSource> source, ParallelOptions parallelOptions, Func<TSource, CancellationToken, ValueTask> body)
یک مثال: در اینجا میخواهیم به صورت موازی، مشخصات کاربرانی از Github را توسط HttpClient دریافت کنیم. هر بار هم فقط میخواهیم سه وظیفه اجرا شوند و نه بیشتر
using System.Net.Http.Headers; using System.Net.Http.Json; var userHandlers = new [] { "users/VahidN", "users/shanselman", "users/jaredpar", "users/davidfowl" }; using HttpClient client = new() { BaseAddress = new Uri("https://api.github.com"), }; client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("DotNet", "6")); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 3 }; await Parallel.ForEachAsync(userHandlers, parallelOptions, async (uri, token) => { var user = await client.GetFromJsonAsync<GitHubUser>(uri, token); Console.WriteLine($"Name: {user.Name}\nBio: {user.Bio}\n"); }); public class GitHubUser { public string Name { get; set; } public string Bio { get; set; } }
متد Parallel.ForEachAsync، آرایهای را که باید بر روی آن کار کند، دریافت میکند. سپس تنظیمات اجرای موازی آنها را هم مشخص میکنیم. در ادامه آنها را در دستههای مشخصی، به صورت موازی بر اساس منطقی که مشخص میکنیم، اجرا خواهد کرد.
وضعیت امکان اجرای موازی متدهای async همزمان، تا پیش از دات نت 6
<List<T به همراه متد الحاقی ForEach است که میتواند یک <Action<T را بر روی المانهای این لیست، اجرا کند و ... عموما زمانیکه به وظایف async میرسیم، به اشتباه مورد استفاده قرار میگیرد:
customers.ForEach(c => SendEmailAsync(c));
foreach(var c in customers) { SendEmailAsync(c); // the return task is ignored }
customers.ForEach(async c => await SendEmailAsync(c));
تنها راه حل پذیرفتهی شدهی چنین عمل async ای، فراخوانی آنها به صورت متداول زیر و بدون استفاده از متد ForEach است:
foreach(var c in customers) { await SendEmailAsync(c); }
foreach(var o in orders) { await ProcessOrderAsync(o); }
var tasks = orders.Select(o => ProcessOrderAsync(o)).ToList(); await Task.WhenAll(tasks);
دات نت 6، هم کنترل MaxDegreeOfParallelism را میسر کردهاست و هم اینکه اینبار نگارش async واقعی Parallel.ForEachAsync را ارائه دادهاست تا دیگر همانند حالت قبلی Parallel.ForEach، به async voidها و مشکلات مرتبط با آنها نرسیم.
EF Code First #12
using System; using System.ComponentModel.DataAnnotations; namespace Test { [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class CompareAttribute : ValidationAttribute { public CompareAttribute(string originalProperty, string confirmProperty) { OriginalProperty = originalProperty; ConfirmProperty = confirmProperty; } public string ConfirmProperty { get; private set; } public string OriginalProperty { get; private set; } protected override ValidationResult IsValid(object value, ValidationContext ctx) { if (value == null) return new ValidationResult("لطفا فیلدها را تکمیل نمائید"); var confirmProperty = ctx.ObjectType.GetProperty(ConfirmProperty); if (confirmProperty == null) throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", ConfirmProperty)); var confirmValue = confirmProperty.GetValue(ctx.ObjectInstance, null) as string; if (string.IsNullOrWhiteSpace(confirmValue)) return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", ConfirmProperty)); var originalProperty = ctx.ObjectType.GetProperty(OriginalProperty); if (originalProperty == null) throw new InvalidOperationException(string.Format("لطفا فیلد {0} را تعریف نمائید", OriginalProperty)); var originalValue = originalProperty.GetValue(ctx.ObjectInstance, null) as string; if (string.IsNullOrWhiteSpace(originalValue)) return new ValidationResult(string.Format("لطفا فیلد {0} را تکمیل نمائید", OriginalProperty)); return originalValue == confirmValue ? ValidationResult.Success : new ValidationResult("مقادیر وارد شده یکسان نیستند"); } } }
معرفی پروژه DNTFrameworkCore
پروژه DNTFrameworkCore که قصد پشتیبانی از آن را دارم، یک زیرساخت سبک وزن و توسعه پذیر با پشتیبانی از طراحی چند مستاجری با کمترین وابستگی به کتابخانههای ثالث میباشد که با تمرکز بر کاهش زمان و افزایش کیفیت توسعه بخش منطق تجاری پروژههای تحت وب، توسعه داده شده است. به مرور زمان مطالب و مستندات آن نیز کامل خواهد شد. برای برخی از امکانات از جمله اعتبارسنجی خودکار، مدیریت تراکنش ها، شماره گذاری خودکار و ... آزمون واحد نیز در نظر گرفته شده است که در آینده نزدیک با تکمیل آزمون واحد بخشهای دیگر، انتشار آنها نیز انجام خواهد شد.
برای نصب و استفاده از بستههای نیوگت آن، دستورات زیر را اجرا کنید:
PM>Install-Package DNTFrameworkCore PM>Install-Package DNTFrameworkCore.EntityFramework PM>Install-Package DNTFrameworkCore.Web PM>Install-Package DNTFrameworkCore.Web.EntityFramework
به منظور بررسی دقیقتر امکانات آن میتوانید پروژه TestAPI موجود در مخزن گیت هاب را بررسی کنید.
نمونه API پیاده سازی شده:
[Route("api/[controller]")] public class TasksController : CrudController<ITaskService, int, TaskReadModel, TaskModel, TaskFilteredPagedQueryModel> { public TasksController(ITaskService service) : base(service) { } protected override string CreatePermissionName => PermissionNames.Tasks_Create; protected override string EditPermissionName => PermissionNames.Tasks_Edit; protected override string ViewPermissionName => PermissionNames.Tasks_View; protected override string DeletePermissionName => PermissionNames.Tasks_Delete; }
Install-Package Stimulsoft.Reports.Blazor -Version 2021.2.4
جهت دسترسی به فایل گزارش، نیاز است فایل ریپورت، تبدیل به آرایهای از بایتها شود. به همین دلیل در Web Api یک متد را ساخته و در این متد، فایل گزارش را به آرایه تبدیل میکنیم:
[HttpGet] [Route("GetReportFile/{fileName}")] public async Task<IActionResult> GetReportFile(string fileName) { var rootPath = _webHostEnverioment.WebRootFileProvider.GetDirectoryContents("/") .FirstOrDefault(x => x.Name == "Reports")?.PhysicalPath; var path = Path.Combine(rootPath!, fileName); var bytes = await System.IO.File.ReadAllBytesAsync(path); return Ok(bytes); }
@page "/" @using Stimulsoft.Base @using Stimulsoft.Report @using Stimulsoft.Report.Blazor @inject HttpClient Http <StiBlazorViewer Report="@report" /> @code { private StiReport report; protected override async Task OnInitializedAsync() { //Create empty report object report = new StiReport(); //Load report template // var reportBytes = await Http.GetByteArrayAsync("Reports/Report.mrt"); report.RegBusinessObject("MyList", GetBusinessObject()); var uri = $"/api/Report/GetReportFile/Report.mrt"; var reportFile=await Http.GetFromJsonAsync<byte[]>(uri); // var reportFile = await Http.GetByteArrayAsync("Report.mrt"); report.Load(reportFile); await report.Dictionary.SynchronizeAsync(); } public class BusinessEntity { public string Name { get; set; } public string Alias { get; set; } public BusinessEntity(string name, string alias) { Name = name; Alias = alias; } } private System.Collections.ArrayList GetBusinessObject() { var list = new System.Collections.ArrayList(); list.Add(new BusinessEntity("ali", "alias1")); list.Add(new BusinessEntity("reza", "alias2")); return list; } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorWasmReport.zip
تزریق خودکار وابستگیها در برنامههای ASP.NET MVC
اگر به هر دلیلی یک کلاس پایه کنترلر را ایجاد کردید که در آن ExecuteCore تحریف شده است، این متد چه با تزریق وابستگیها و چه بدون آن فراخوانی نخواهد شد. (روش صحیح مدیریت این مسایل در ASP.NET MVC استفاده از فیلترها است و نه ارث بری؛ چون در طراحی ASP.NET MVC مباحث AOP به صورت خودکار توسط فیلترها پیاده سازی میشوند)
public class MyBaseController : Controller { /// <summary> /// from http://forums.asp.net/t/1776480.aspx/1?ExecuteCore+in+base+class+not+fired+in+MVC+4+beta /// </summary> protected override bool DisableAsyncSupport { get { return true; } } protected override void ExecuteCore() { base.ExecuteCore(); } }
- جزئیات نحوه پیاده سازی یک Storage Provider برای ASP.NET Identity
- تشریح اینترفیس هایی که باید پیاده سازی شوند، و نحوه استفاده از آنها در ASP.NET Identity
- ایجاد یک دیتابیس MySQL روی Windows Azure
- نحوه استفاده از یک ابزار کلاینت (MySQL Workbench) برای مدیریت دیتابیس مذکور
- نحوه جایگزینی پیاده سازی سفارشی با نسخه پیش فرض در یک اپلیکیشن ASP.NET MVC
پیاده سازی یک Storage Provider سفارشی برای ASP.NET Identity
- <UserStore<TUser
- IdentityUser
- <RoleStore<TRole
- IdentityRole
Roles
در مخزن پیش فرض ASP.NET Identity EntityFramework کلاسهای بیشتری برای موجودیتها مشاهده میکنید.
- IdentityUserClaim
- IdentityUserLogin
- IdentityUserRole
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user) { ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id); return Task.FromResult<IList<Claim>>(identity.Claims.ToList()); }
public class IdentityUser : IUser { public IdentityUser(){...} public IdentityUser(string userName) (){...} public string Id { get; set; } public string UserName { get; set; } public string PasswordHash { get; set; } public string SecurityStamp { get; set; } }
public class UserStore : IUserStore<IdentityUser>, IUserClaimStore<IdentityUser>, IUserLoginStore<IdentityUser>, IUserRoleStore<IdentityUser>, IUserPasswordStore<IdentityUser> { public UserStore(){...} public Task CreateAsync(IdentityUser user){...} public Task<IdentityUser> FindByIdAsync(string userId){...} ... }
public class IdentityRole : IRole { public IdentityRole(){...} public IdentityRole(string roleName) (){...} public string Id { get; set; } public string Name { get; set; } }
public class RoleStore : IRoleStore<IdentityRole> { public RoleStore(){...} public Task CreateAsync(IdentityRole role){...} public Task<IdentityRole> FindByIdAsync(string roleId){...} .... }
- MySQLDatabase: این کلاس اتصال دیتابیس MySql و کوئریها را کپسوله میکند. کلاسهای UserStore و RoleStore توسط نمونه ای از این کلاس وهله سازی میشوند.
- RoleTable: این کلاس جدول Roles و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserClaimsTable: این کلاس جدول UserClaims و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserLoginsTable: این کلاس جدول UserLogins و عملیات CRUD مربوط به آن را کپسوله میکند.
- UserRolesTable: این کلاس جدول UserRoles و عملیات CRUD مربوطه به آن را کپسوله میکند.
- UserTable: این کلاس جدول Users و عملیات CRUD مربوط به آن را کپسوله میکند.
ایجاد یک دیتابیس MySQL روی Windows Azure
در ویزارد Choose Add-on به سمت پایین اسکرول کنید و گزینه ClearDB MySQL Database را انتخاب کنید. سپس به مرحله بعد بروید.
4. راهکار Free بصورت پیش فرض انتخاب شده، همین گزینه را انتخاب کنید و نام دیتابیس را به IdentityMySQLDatabase تغییر دهید. نزدیکترین ناحیه (region) به خود را انتخاب کنید و به مرحله بعد بروید.
5. روی علامت checkmark کلیک کنید تا دیتابیس شما ایجاد شود. پس از آنکه دیتابیس شما ساخته شد میتوانید از قسمت ADD-ONS آن را مدیریت کنید.
6. همانطور که در تصویر بالا میبینید، میتوانید اطلاعات اتصال دیتابیس (connection info) را از پایین صفحه دریافت کنید.
7. اطلاعات اتصال را با کلیک کردن روی دکمه مجاور کپی کنید تا بعدا در اپلیکیشن MVC خود از آن استفاده کنیم.
ایجاد جداول ASP.NET Identity در یک دیتابیس MySQL
CREATE TABLE `IdentityMySQLDatabase`.`users` ( `Id` VARCHAR(45) NOT NULL, `UserName` VARCHAR(45) NULL, `PasswordHash` VARCHAR(100) NULL, `SecurityStamp` VARCHAR(45) NULL, PRIMARY KEY (`id`)); CREATE TABLE `IdentityMySQLDatabase`.`roles` ( `Id` VARCHAR(45) NOT NULL, `Name` VARCHAR(45) NULL, PRIMARY KEY (`Id`)); CREATE TABLE `IdentityMySQLDatabase`.`userclaims` ( `Id` INT NOT NULL AUTO_INCREMENT, `UserId` VARCHAR(45) NULL, `ClaimType` VARCHAR(100) NULL, `ClaimValue` VARCHAR(100) NULL, PRIMARY KEY (`Id`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userlogins` ( `UserId` VARCHAR(45) NOT NULL, `ProviderKey` VARCHAR(100) NULL, `LoginProvider` VARCHAR(100) NULL, FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade); CREATE TABLE `IdentityMySQLDatabase`.`userroles` ( `UserId` VARCHAR(45) NOT NULL, `RoleId` VARCHAR(45) NOT NULL, PRIMARY KEY (`UserId`, `RoleId`), FOREIGN KEY (`UserId`) REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade on update cascade, FOREIGN KEY (`RoleId`) REFERENCES `IdentityMySQLDatabase`.`roles` (`Id`) on delete cascade on update cascade);
ایجاد یک اپلیکیشن ASP.NET MVC و پیکربندی آن برای استفاده از MySQL Provider
6. در پنجره New ASP.NET Project قالب MVC را انتخاب کنید و تنظیمات پیش فرض را بپذیرید.
7. در پنجره Solution Explorer روی پروژه IdentityMySQLDemo کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.EntityFramework" را وارد کنید. در لیست نتایج این پکیج را انتخاب کرده و آن را حذف (Uninstall) کنید. پیغامی مبنی بر حذف وابستگیها باید دریافت کنید که مربوط به پکیج EntityFramework است، گزینه Yes را انتخاب کنید. از آنجا که کاری با پیاده سازی فرض نخواهیم داشت، این پکیجها را حذف میکنیم.
8. روی پروژه IdentityMySQLDemo کلیک راست کرده و Add, Reference, Solution, Projects را انتخاب کنید. در دیالوگ باز شده پروژه AspNet.Identity.MySQL را انتخاب کرده و OK کنید.
9. در پروژه IdentityMySQLDemo پوشه Models را پیدا کرده و کلاس IdentityModels.cs را حذف کنید.
10. در پروژه IdentityMySQLDemo تمام ارجاعات ";using Microsoft.AspNet.Identity.EntityFramework" را با ";using AspNet.Identity.MySQL" جایگزین کنید.
11. در پروژه IdentityMySQLDemo تمام ارجاعات به کلاس "ApplicationUser" را با "IdentityUser" جایگزین کنید.
12. کنترلر Account را باز کنید و متد سازنده آنرا مطابق لیست زیر تغییر دهید.
public AccountController() : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase()))) { }
13. فایل web.config را باز کنید و رشته اتصال DefaultConnection را مطابق لیست زیر تغییر دهید.
<add name="DefaultConnection" connectionString="Database=IdentityMySQLDatabase;Data Source=<DataSource>;User Id=<UserID>;Password=<Password>" providerName="MySql.Data.MySqlClient" />
مقادیر <DataSource>, <UserId> و <Password> را با اطلاعات دیتابیس خود جایگزین کنید.
اجرای اپلیکیشن و اتصال به دیتابیس MySQL
5. در این مرحله کاربر جدید باید ایجاد شده و وارد سایت شود.
6. به ابزار MySQL Workbench بروید و محتوای جداول IdentityMySQLDatabase را بررسی کنید. جدول users را باز کنید و اطلاعات کاربر جدید را بررسی نمایید.
برای ساده نگاه داشتن این مقاله از بررسی تمام کدهای لازم خودداری شده، اما اگر مراحل را دنبال کنید و سورس کد نمونه را دریافت و بررسی کنید خواهید دید که پیاده سازی تامین کنندگان سفارشی برای ASP.NET Identity کار نسبتا ساده ای است.
public interface IHasRowIntegrity { string Hash { get; set; } }
internal sealed class RowIntegrityHook : PostActionHook<IHasRowIntegrity> { public override string Name => HookNames.RowIntegrity; public override int Order => int.MaxValue; public override EntityState HookState => EntityState.Unchanged; protected override void Hook(IHasRowIntegrity entity, HookEntityMetadata metadata, IUnitOfWork uow) { metadata.Entry.Property(EFCore.Hash).CurrentValue = uow.EntityHash(entity); } }
//DbContextCore : IUnitOfWork public string EntityHash<TEntity>(TEntity entity) where TEntity : class { var row = Entry(entity).ToDictionary(p => p.Metadata.Name != EFCore.Hash && !p.Metadata.ValueGenerated.HasFlag(ValueGenerated.OnUpdate) && !p.Metadata.IsShadowProperty()); return EntityHash<TEntity>(row); } protected virtual string EntityHash<TEntity>(Dictionary<string, object> row) where TEntity : class { var json = JsonConvert.SerializeObject(row, Formatting.Indented); using (var hashAlgorithm = SHA256.Create()) { var byteValue = Encoding.UTF8.GetBytes(json); var byteHash = hashAlgorithm.ComputeHash(byteValue); return Convert.ToBase64String(byteHash); } }
با توجه به محدودیتهایی (منفصل بودن اشیاء از کانتکست) که در استفاده از TrackGraph در زیرساخت وجود داشت، در اینجا از خواص سایهای چشم پوشی شده است.
services.AddEFCore<ProjectDbContext>() .WithTrackingHook<long>() .WithDeletedEntityHook() .WithRowLevelSecurityHook<long>() .WithRowIntegrityHook() .WithNumberingHook(options => { options.NumberedEntityMap[typeof(Task)] = new NumberedEntityOption { Prefix = "Task", FieldNames = new[] {nameof(Task.BranchId)} }; });