
یادگیری سی شارپ از مفاهیم پایه تا پروژه محور: شیگرایی، کار با SQL و LINQ، ORMها (Entity Framework)، ساخت پروژه مدیریت رستوران با گزارشات حرفهای و امکانات کامل!
مشاهده بیشتر
یادگیری MVC Core از مبانی تا پیشرفته: شیگرایی، Routing، Entity Framework، امنیت، تست یونیت، Razor، Ajax، و پروژههای کاربردی! یک دوره کامل برای تسلط بر توسعه وب با ASP.NET Core. به صورت حضوری و آنلاین!
مشاهده بیشترمشخصات مقاله
ساخت Identity پیشرفته در MVC 5
Identity پیشرفته در MVC 5
پدر این فصل کار این کتاب را با پرداختن به ویژگی های ASP.NET به اتمام می رسانم. به شما نشان خواهم داد که چگونه با تعریف مشخصات اختصاصی در کلاس User طرح دیتابیس را گسترش دهید و برای اعمال این مشخصات بدون نیاز به پاک کردن داده های موجود در دیتابیس ASP.NET Identity از نقل مکان دیتابیس بهره ببرید. همچنین به چگونگی پشتیبانی ASP.NET Identity از مفهوم claim ها خواهم پرداخت و توضیح خواهم داد که چگونه می توان از آن ها در اختیاردهی منعطف دسترسی به action method ها استفاده کرد. این فصل (و این کتاب) را با توضیح چگونگی راحت تر شدن احراز هویت کاربران از طریق اشخاص ثالث توسط ASP.NET Identity تمام خواهم کرد. به احراز هویت از طریق حساب های کاربری گوگل خواهم پرداخت. با این حال، ASP.NET Identity به صورت پیش فرض از حساب های کاربری توییتر، فیسبوک و مایکروسافت نیز پشتیبانی می کند. در جدول 1-15 این فصل به صورت خلاصه بیان شده است.
آماده کردن نمونه پروژه
در این فصل ی خواهم کارم را بر روی پروژه ی Users که در فصل 13 آن را ایجاد کردم و در فصل 14 آن را بهبود بخشیدم ادامه دهم. نیازی نیست هیچ تغییری بر روی برنامه ایجاد کنید اما برنامه را باز کنید و مطمئن شوید که کاربران در دیتابیس وجود دارند.در شکل زیر وضعیت دیتابیس من نشان داده شده است. این دیتابیس از فصل قبل شامل کاربران Admin ، Alice ، Bob و Joe است. برای آنکه این کاربران را چک کنید برنامه را باز کنید، آدرس /Admin/Index را درخواست کنید و به عنوان کاربر Admin هویت خود را احراز کنید.

در این فصل به تعدادی role نیز نیاز دارم. همنطور که در جدول زیر مشاهده می کنید، از RoleAdmincontroller برای ایجاد role های Users و Employees استفاده کرده ام و این کاربران را به این دو role تخصیص داده ام.
شکل زیر پیکربندی Role مورد نیاز نمایش داده شده توسط RoleAdmincontroller را نشان می دهد.

اضافه کردن مشخصات کاربری اختصاصی
زمانی که من در فصل 13 برای نشان دادن کاربران کلاس AppUser را ایجاد کردم، یادآور شدم که کلاس پایه مجموعه ای اولیه از مشخصات جهت توصیف کاربر مانند آدرس ایمیل و شماره تلفن را تعریف می کند. اغلب برنامه ها نیاز دارند که اطلاعات بیشتری در رابطه با کاربر ذخیره کنند (مانند اولویت های برنامه ای پایدار و جزئیاتی مانند آدرس ها). به طور خلاصه تمامی داده هایی که برای اجرای برنامه مفید بوده و باید بین session ها ماندگار باشند. در ASP.NET Membership این کار از طریق سیستم پروفایل کاربری انجام می شد، اما رویکرد ASP.NET Identity در این باره متفاوت است.
با توجه به اینکه سیستم ASP.NET Identity برای ذخیره سازی داده های خود به صورت پیش فرض از Entity Framework استفاده می کند، جهت تعریف کردن اطلاعات اضافی برای کاربر تنها کافیست مشخصاتی را به کلاس user اضافه کرده و ایجاد طرح دیتابیس مورد نیاز برای ذخیره سازی آن ها را بر عهده ی ویژگی Code First گذاشت. در جدول زیر مشخصات کاربری اختصاصی به زبان ساده بیان شده است.
تعریف مشخصات اختصاصی
در قطعه کد زیر نشان می دهد که چگونه من برای ارائه ی شهری که کاربر در آن زندگی می کند، مشخصه ی ساده ای را به کلاس AppUser اضافه کرده ام.
using System; using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models { public enum Cities { LONDON, PARIS, CHICAGO } public class AppUser : IdentityUser { public Cities City { get; set; } } }
برشمارشی به نام Cities را تعریف کرده ام که کار آن تعریف مقادیر برخی از شهرهای بزرگ است. همچنین مشخصه ای به نام City را به کلاس AppUser اضافه کرده ام. برای آنکه کاربر بتواند مشخصه ی City خود را ببیند و ویرایش کند اکشن هایی را همانطور که در قطعه کد زیر مشاهده می کنید به Home controller اضافه کرده ام.
using System.Web.Mvc; using System.Collections.Generic; using System.Web; using System.Security.Principal; using System.Threading.Tasks; using Users.Infrastructure; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Users.Models; namespace Users.Controllers { public class HomeController : Controller { [Authorize] public ActionResult Index() { return View(GetData("Index")); } [Authorize(Roles = "Users")] public ActionResult OtherAction() { return View("Index", GetData("OtherAction")); } private Dictionary< string, object > GetData(string actionName) { Dictionary< string, object > dict = new Dictionary< string, object >(); dict.Add("Action", actionName); dict.Add("User", HttpContext.User.Identity.Name); dict.Add("Authenticated", HttpContext.User.Identity.IsAuthenticated); dict.Add("Auth Type", HttpContext.User.Identity.AuthenticationType); dict.Add("In Users Role", HttpContext.User.IsInRole("Users")); return dict; } [Authorize] public ActionResult UserProps() { return View(CurrentUser); } [Authorize] [HttpPost] public async Task< ActionResult > UserProps(Cities city) { AppUser user = CurrentUser; user.City = city; await UserManager.UpdateAsync(user); return View(user); } private AppUser CurrentUser { get { return UserManager.FindByName(HttpContext.User.Identity.Name); } } private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager< AppUserManager >(); } } } }
مشخصه ی CurrentUser را اضافه کرده ام که برای بازیابی نمونه ی AppUser جهت ارائه ی کاربر موجود از کلاس AppUserManager استفاده می کند. شیء AppUser را به صورت شیء view model در نسخه ی GET مربوط به UserProps action method عبور داده ام که متد POST برای به روز رسانی مقدار مشخصه ی جدید City از آن استفاده می کند. در قطعه کد زیر ویو UserProps.cshtml نشان داده شده است. این view مشخصه ی City را نمایش داده و شامل فرمی برای تغییر آن است.
@using Users.Models @model AppUser @{ ViewBag.Title = "UserProps";} < div class="panel panel-primary" > < div class="panel-heading" > Custom User Properties< /div > < table class="table table-striped" > < tr >< th >City< /th >< td >@Model.City< /td >< /tr > < /table > < /div > @using (Html.BeginForm()) { < div class="form-group" > < label >City< /label > @Html.DropDownListFor(x = > x.City, new SelectList(Enum.GetNames(typeof(Cities)))) < /div > < button class="btn btn-primary" type="submit" >Save< /button > }
هشدار بعد از ایجاد view برنامه را باز نکنید. در بخشی که در پیش داریم به شما نشان خواهم داد که چگونه محتوای داخل دیتابیس را حفظ کنید. اگر شما الان برنامه را باز کنید کاربران ASP.NET Identity حذف خواهند شد.
آمادگی برای انتقال دیتابیس
رفتار پیش فرض ویژگی Entity Framework Code First به این صورت است که جداول را داخل دیتابیس می اندازد و هر زمان که کلاس های محرک طرح تغییر کنند، آن ها را مجددا ایجاد می کند. در فصل 14 زمانی که من امکان پشتیبانی از role ها را در برنامه اضافه کردم، این موضوع را مشاهده کردید: وقتی که برنامه باز شد دیتابیس ریست شد و حساب های کاربری از بین رفتند.
هنوز زمان باز کردن برنامه فرا نرسیده است. اما اگر برخلاف این امر بخواهید برنامه را باز کنید، نتیجه ی مشابهی را مشاهده خواهید کرد. پاک شدن داده ها در برنامه نویسی معمولا مشکل محسوب نمی شود. اما اگر این کار در محیط تولید جزئی از برنامه اتفاق بیفتد می تواند فاجعه بار باشد. زیرا این کار باعث می شود تمامی حساب های کاربران واقعی پاک شود و طی restore شدن backup ها باعث ایجاد ترس شود. در این بخش می خواهم به شما چگونگی استفاده از ویژگی انتقال دیتابیس را بیاموزم. این ویژگی طرح Code First را به شیوه ی نرم تری آپدیت می کند و داده های موجود در خود را حفظ می کند.
اولین قدم، صادر کردن دستور زیر در کنسول Visual Studio Package Manager است.
Enable-Migrations –EnableAutomaticMigrations
این دستور پشتیبانی از انتقال دیتابیس را فراهم کرده و در Solution Explorer که شامل فایل کلاس Configuration.cs است پوشه ی Migrations را ایجاد می کند. محتوای داخل این فایل در قطعه کد زیر نشان داده شده است.
namespace Users.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration: DbMigrationsConfiguration< Users.Infrastructure.AppIdentityDbContext > { public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Users.Infrastructure.AppIdentityDbContext"; } protected override void Seed(Users.Infrastructure.AppIdentityDbContext context) { // This method will be called after migrating to the latest version. // You can use the DbSet< T >.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // // context.People.AddOrUpdate( // p = > p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // } } }
شاید برایتان سوال باشد که چرا دستور انتقال دیتابیس را در کنسولی دارید وارد می کنید که کار آن مدیریت بسته های نرم افزاری NuGet است. پاسخ این است که کنسول Package Manager در واقع PowerShell است، که ابزاری چندمنظوره بوده که توسط Visual Studio به خوبی معرفی نشده است. می توانید از این کنسول برای صادر کردن طیف گسترده ای از دستورات مفید استفاده کنید. برای اطلاعات بیشتر به لینک http://go.microsoft.com/fwlink/?LinkID=108518 مراجعه کنید.
جهت انتقال محتویات موجود در دیتابیس به طرح جدید از این کلاس استفاده می شود و برای فراهم کردن موقعیتی جهت به روز رسانی گزارش های موجود در دیتابیس متد Seed فراخوانی خواهد شد. در قطعه کد زیر می توانید مشاهده کنید که من برای آنکه مقدار پیش فرضی را برای مشخصه ی جدید City که آن را به کلاس AppUser اضافه کرده ام تنظیم کنم، چگونه از متد Seed استفاده کرده ام. (برای آنکه سبک کد نویسی معمول خودم را نشان دهم فایل این کلاس را به روز رسانی کرده ام.)
using System.Data.Entity.Migrations; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Infrastructure; using Users.Models; namespace Users.Migrations { internal sealed class Configuration: DbMigrationsConfiguration< AppIdentityDbContext >{ public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Users.Infrastructure.AppIdentityDbContext"; } protected override void Seed(AppIdentityDbContext context) { AppUserManager userMgr = new AppUserManager(new UserStore< AppUser >(context)); AppRoleManager roleMgr = new AppRoleManager(new RoleStore< AppRole >(context)); string roleName = "Administrators"; string userName = "Admin"; string password = "MySecret"; string email = "admin@example.com"; if (!roleMgr.RoleExists(roleName)) { roleMgr.Create(new AppRole(roleName)); } AppUser user = userMgr.FindByName(userName); if (user == null) { userMgr.Create(new AppUser { UserName = userName, Email = email }, password); user = userMgr.FindByName(userName); } if (!userMgr.IsInRole(user.Id, roleName)) { userMgr.AddToRole(user.Id, roleName); foreach (AppUser dbUser in userMgr.Users) { dbUser.City = Cities.PARIS; } context.SaveChanges(); } } }
همانطور که مشاهده می کنید بخش اعظمی از کدی که من به متد Seed اضافه کرده ام از کلاس IdentityDbInit برداشته شده است. من از این کلاس در فصل 14 برای پیگردی دیتابیس به کمک یک کاربر مدیریتی استفاده کرده ام. دلیل این امر این است که کلاس جدید Configuration که برای اشافه کردن امکان پشتیبانی از انتقال دیتابیس اضافه شده است، جای خود را به تابع پیگردی کلاس IdentityDbInit خواهد داد. من این کلاس را به زودی آپدیت خواهم کرد. گذشته از تضمین اینکه کاربر ادمینی وجود داشته باشد، دستورات مهم موجود در متد Seed دستوراتی هستند که مقدار اولیه ی مشخصه ی City را که به کلاس AppUser اضافه کرده ام مشخص می کنند، مانند ذیل:
... foreach (AppUser dbUser in userMgr.Users) { dbUser.City = Cities.PARIS; } context.SaveChanges(); ...
نیازی به تنظیم مقدار پیش فرضی برای مشخصات جدید نیست. هدف من بیان این مطلب بود که متد Seed موجود در کلاس Configuration می تواند گزارش های کاربری موجود در دیتابیس را به روز رسانی کند.
زمانی که می خواهید مقادیر مشخصات موجود در متد Seed را در پروژه های واقعی تنظیم کنید، احتیاط کنید زیرا هر بار که شما طرح را تغییر دهید، مقادیر اعمال می شوند و هر مقداری که کاربر از زمان به روز رسانی شدن طرح تنظیم کرده باشد، override می شود. من تنها برای آنکه نشان دهم که این کار شدنی است، مقدار مشخصه City را تنظیم کرده ام.
تغییر کلاس زمینه دیتابیس
دلیل اینکه من کد پیگردی را به کلاس Configuration اضافه کرده ام این است که باید کلاس IdentityDbInit را تغییر دهم. در حال حاضر، کلاس IdentityDbInit از کلاس با نام توصیفی DropCreateDatabaseIfModelC hanges< AppIdentityDbContext > مشتق شده است که همانطور که از نام آن پیداست، وظیفه آن انداختن کل دیتابیس پس از تغییر کلاس های Code First است. در قطعه کد زیر تغییراتی که من بر روی کلاس IdentityDbInit اعمال کرده ام تا از اثرگذاری آن بر روی دیتابیس جلوگیری کنم را می توانید مشاهده کنید.
using System.Data.Entity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Models; using Microsoft.AspNet.Identity; namespace Users.Infrastructure { public class AppIdentityDbContext: IdentityDbContext< AppUser > { public AppIdentityDbContext(): base("IdentityDb") { } static AppIdentityDbContext() { Database.SetInitializer< AppIdentityDbContext >(new IdentityDbInit()); } public static AppIdentityDbContext Create() { return new AppIdentityDbContext(); } } public class IdentityDbInit : NullDatabaseInitializer< AppIdentityDbContext > { } }
برای جلوگیری از تغییر کردن طرح، متدهای تعریف شده توسط این کلاس را حذف کرده ام و پایه ی آن را به NullDatabaseInitializer< AppIdentityDbContext > تغییر داده ام.
اجرای انتقال دیتابیس
تنها چیزی که باقی می ماند ایجاد و اعمال انتقال دیتابیس است. ابتدا دستور زیر را در کنسول Package Manager اجرا کنید.
Add-Migration CityProperty
این کار باعث می شود migration ای به نام CityProperty ایجاد شود (اسامی migration خود را به گونه ای انتخاب می کنم که بیانگر تغییرات اعمالی من باشند).
فایل کلاس جدیدی به پوشه ی migrations اضافه خواهد شد که اسم آن بیانگر زمان اجرای دستور و اسم migration است. برای مثال اسم فایل من 201402262244036_CityProperty.cs است. محتوای داخل این فایل شامل جزئیات چگونگی تغییر دیتابیس طی انتقال آن توسط Entity Framework است.
namespace Users.Migrations { using System; using System.Data.Entity.Migrations; public partial class Init : DbMigration { public override void Up() { AddColumn("dbo.AspNetUsers", "City", c => c.Int(nullable: false)); } public override void Down() { DropColumn("dbo.AspNetUsers", "City"); } } }
متد Up بیانگر تغییراتی است که پس از upgrade شدن دیتابیس باید بر روی طرح اعمال شوند. این موضوع برای این حالت به معنی اضافه کردن ستون City به جدول AspNetUsers است. این جدول جهت ذخیره سازی گزارشات کاربری در دیتابیس ASP.NET Identity کاربرد دارد. مرحله ی آخر اجرای انتقال دیتابیس یا همان migration است. بدون باز کردن برنامه دستور زیر را در کنسول Package Manager اجرا کنید.
Update-Database –TargetMigration CityProperty
بعد از انجام این کار طرح دیتابیس اصلاح شده و کد موجود در متد Configuration.Seed اجرا می شود. حساب های کاربری موجود حفظ می شوند و توسط مشخصه ی City بهبود می یابند (این مشخصه را در متد Seed بر روی Paris قرار داده ام).
امتحان کردن انتقال دیتابیس
برای آن که نتیجه ی انتقال دیتابیس را تست کنید، برنامه را باز کنید، به آدرس /Home/UserProps بروید و به عنوان یکی از اعضای Identity هویت خود را احراز کنید (مثلا به عنوان Alice با رمز عبور MySecret). بعد از احراز شدن هویتتان مقدار موجود مشخصه ی City مربوط به کاربر را مشاهده خواهید کرد و امکان تغییر آن برایتان فراهم است.

تعریف مشخصه ی بیشتر
حالا که کار انتقال دیتابیس انجام شد تنها برای آن که به شما نشان دهم که چگونه تغییرات بعدی مدیریت خواهند شد و برای آنکه مثال مفیدتری (کم خطرتر) از استفاده از متد Configuration.Seed را به شما نشان دهم، می خواهم یک مشخصه ی دیگر را تعریف کنم. در قطعه کد زیر نشان داده شده است که من چگونه مشخصه ی Country را به کلاس AppUser اضافه کرده ام.
using System; using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models { public enum Cities { LONDON, PARIS, CHICAGO } public enum Countries { NONE, UK, FRANCE, USA } public class AppUser : IdentityUser { public Cities City { get; set; } public Countries Country { get; set; } public void SetCountryFromCity(Cities city) { switch (city) { case Cities.LONDON: Country = Countries.UK; Break: case Cities.PARIS: Country = Countries.FRANCE; break; case Cities.CHICAGO: Country = Countries.USA; break; default: Country = Countries.NONE; break; } } } }
برای آنکه اسم کشورها را تعریف کنم، یک برشمارش و یک متد کمکی که کار آن انتخاب کردن مقدار کشور بر اساس مشخصه ی City است را اضافه کرده ام. در تیتر 9-15 تغییراتی که من بر روی کلاس Configuration اعمال کرده ام نشان داده شده است. به گونه ای که متد Seed بر اساس City و تنها در صورتی که مقدار Country برابر با NONE باشدمشخصه ی Country را تنظیم می کند (این مقداردر زمانی که انتقال دیتابیس انجام شده است برابر با NONE است زیرا Entity Framework ستون های برشمارش را بر روی مقدار اولیه تنظیم می کند).
using System.Data.Entity.Migrations; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Infrastructure; using Users.Models; namespace Users.Migrations { internal sealed class Configuration : DbMigrationsConfiguration{ public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Users.Infrastructure.AppIdentityDbContext"; } protected override void Seed(AppIdentityDbContext context) { AppUserManager userMgr = new AppUserManager(new UserStore (context)); AppRoleManager roleMgr = new AppRoleManager(new RoleStore (context)); string roleName = "Administrators"; string userName = "Admin"; string password = "MySecret"; string email = "admin@example.com"; if (!roleMgr.RoleExists(roleName)) { roleMgr.Create(new AppRole(roleName)); } AppUser user = userMgr.FindByName(userName); if (user == null) { userMgr.Create(new AppUser { UserName = userName, Email = email }, password); user = userMgr.FindByName(userName); } if (!userMgr.IsInRole(user.Id, roleName)) { userMgr.AddToRole(user.Id, roleName); } foreach (AppUser dbUser in userMgr.Users) { if (dbUser.Country == Countries.NONE) { dbUser.SetCountryFromCity(dbUser.City); } } context.SaveChanges(); } } }
این شیوه از پیگردی در پروژه های واقعی بیشتر به کار می آید زیرا این شیوه مقدار مشخصه ی Country را تنها در صورتی تنظیم می کند که مقدار یکی از مشخصه ها تنظیم نشده باشد (انتقال های دیتابیس بعدی از این کار متاثر نخواهند شد و انتخاب کاربر از بین نخواهد رفت).
برای مطالعه سرفصل آموزش پیشرفته MVC کلیک نمایید .