
یادگیری سی شارپ از مفاهیم پایه تا پروژه محور: شیگرایی، کار با SQL و LINQ، ORMها (Entity Framework)، ساخت پروژه مدیریت رستوران با گزارشات حرفهای و امکانات کامل!
مشاهده بیشتر
یادگیری MVC Core از مبانی تا پیشرفته: شیگرایی، Routing، Entity Framework، امنیت، تست یونیت، Razor، Ajax، و پروژههای کاربردی! یک دوره کامل برای تسلط بر توسعه وب با ASP.NET Core. به صورت حضوری و آنلاین!
مشاهده بیشترمشخصات مقاله
آزمایش نحوه عملکرد Create در ASP.Net MVC 5
آزمایش نحوه عملکرد Create
برای آنکه قابلیت ایجاد حساب های کاربری جدید را آزمایش کنید، نرم افزار را باز کنید و به آدرس /Admin/Index بروید و در نهایت بر روی دکمه Create کلیک کنید. فرم باز شده را با مقادیر ارائه شده در جدول زیر پر کنید.
دامنه هایی وجود دارند که می توان از آن ها برای تست نرم افزارهای خود استفاده کنید، هر چند که این دامنه ها چندان بین برنامه نویسان شناخته شده نیستند. لیست کاملی از این دامنه ها را می توانید در https://tools.ietf.org/html/rfc2606 مشاهده کنید.
بعد از اینکه مقادیر جدول را وارد کردید بر روی دکمه Create کلیک کنید. بعد از انجام این کار ASP.NET Identity حساب کاربری را ایجاد می کند. پس از آنکه مرورگر شما به سمت متد indext action هدایت شود، همانطور که در شکل زیر نشان داده شده است این کاربر نمایش داده می شود. با توجه به اینکه ID ها به صورت تصادفی برای هر یک از حساب های کاربری تولید شده اند، ID متفاوتی را مشاهده می کنید.

دوباره بر روی دکمه Create کلیک کنید و همان جزئیات جدول 6-13 را در فرم باز شده وارد کنید. این دفعه اگر بخواهید فرم را ارسال کنید از طریق model validation summary خطایی را مشاهده خواهید کرد.

تایید یا validate کردن رمز عبورها
یکی از رایج ترین الزامات موجود به ویژه در نرم افزارهای شرکتی، اجبار کردن مقررات مربوط به رمز عبور (password policy) است. در همین راستا ASP.NET Identity کلاس PasswordValidator را در اختیار گذاشته است تا بتوان با استفاده از مشخصات ارائه شده در جدول زیر این مقررات را پیکربندی کرد.
Password policy با ایجاد نمونه ای از کلاس PasswordValidator ، تنظیم مقادیر مشخصه و استفاده از این شیء به عنوان مقدار مشخصه ی PasswordValidator در متد Create انجام می شود. OWIN مانند در قطعه کد زیر از این متد برای نمونه سازی کلاس AppUserManager استفاده می کند.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models; namespace Users.Infrastructure { public class AppUserManager : UserManager< AppUser > { public AppUserManager(IUserStore< AppUser > store) : base(store) { } public static AppUserManager Create(IdentityFactoryOptions< AppUserManager > options, IOwinContext context) { AppIdentityDbContext db = context.Get< AppIdentityDbContext >(); AppUserManager manager = new AppUserManager( new UserStore< AppUser >(db)); manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true }; return manager; } } }
مقرراتی که من برای رمز عبور استفاده کرده ام به این صورت است که حداقل 6 کاراکتر و ترکیبی از کاراکترهای کوچک و بزرگ باید در رمز عبور وجود داشته باشند. اگر می خواهید اعمال شدن این مقررات را مشاهده کنید تنها کافیست که نرم افزار را باز کنید، به آدرس /Admin/ Index بروید، بر روی دکمه ی Create کلیک کنید و در نهایت حساب کاربری ای را ایجاد کنید که رمز عبور آن secret باشد. با توجه به اینکه این رمز عبور، با مقررات جدید رمز عبور مطابقت ندارد، همانطور که در شکل زیر نشان داده شده است، خطایی در model state نمایش داده می شود.

پیاده سازی ارزیابی رمز عبور به صورت اختصاصی
سیستم پیشفرض تایید رمز عبور برای اغلب نرم افزارها مناسب است، اما گاهی اوقات ممکن است نیاز باشد که مقررات رمز عبور را به صورت اختصاصی ایجاد کنید. این امر به ویژه در مواقعی که بخواهید برای شرکتی تجاری که در آن مقررات رمز عبور به صورت پیچیده مرسوم است، نرم افزاری را توسعه دهید کاربرد دارد. برای انجام این کار باید کلاس جدیدی را از PasswordValidatator مشتق و متد ValidateAsync، override شود. برای نمونه، فایل کلاسی به نام CustomPasswordValidator.cs را در پوشه Infrastructure ایجاد کرده ام و از آن برای تعریف کلاس نشان داده شده در قطعه کد استفاده کرده ام.
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Identity; namespace Users.Infrastructure { public class CustomPasswordValidator : PasswordValidator { public override async Task< IdentityResult > ValidateAsync(string pass) { IdentityResult result = await base.ValidateAsync(pass); if (pass.Contains("12345")) { var errors = result.Errors.ToList(); errors.Add("Passwords cannot contain numeric sequences"); result = new IdentityResult(errors); } return result; } } }
متد ValidateAsync را override کرده ام و برای آنکه از validation check پیشفرض بهره مند شوم ، base implementation را فراخوانی می کنم. متد ValidateAsync رمز عبور کاربر را دریافت می کند و من مطمئن می شود که این رمز عبور شامل اعداد پشت سر هم 12345 نباشد. مشخصات کلاس IdentityResult در حالت read-only قرار دارند به این معنی که اگر من بخواهم خطای مربوط به ارزیابی را گزارش دهم، باید نمونه جدیدی را ایجاد کنم، خطای ام را به تمامی خطاهای مربوط به base implementation متصل کنم و لیست ترکیب شده را به صورت آرگومان constructor عبور دهم. من برای متصل کردن خطاهای پایه با خطاهای اختصاصی خودم از LINQ استفاده کرده ام.
در قطعه کد کاربرد password validator اختصاصی من در کلاس AppUserManager را می توانید مشاهده کنید.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models; namespace Users.Infrastructure { public class AppUserManager : UserManager< AppUser > { public AppUserManager(IUserStore< AppUser > store) : base(store) { } public static AppUserManager Create(IdentityFactoryOptions< AppUserManager > options, IOwinContext context) { AppIdentityDbContext db = context.Get< AppIdentityDbContext >(); AppUserManager manager = new AppUserManager( new UserStore< AppUser >(db)); manager.PasswordValidator = new CustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true }; return manager; } } }
برای اینکه این ارزیابی اختصاصی رمزعبور را امتحان کنید، سعی کنید حساب کاربری جدیدی را با رمز عبور secret12345 ایجاد کنید.
این رمز عبور دو قانون مربوط به ارزیابی رمز عبور را نقض می کند ( یکی مربوط به ارزیابی پیشفرض و دیگری مربوط به پیاده سازی اختصاصی خودم). پیام خطای مربوط به هر دو مشکل در model state اضافه شده و زمانی که دکمه Create فشرده شود این خطاها نمایش داده می شوند.

ارزیابی جزئیات مربوط به کاربر
می توان با ایجاد نمونه ای از کلاس UserValidator و استفاده از مشخصاتی که این کلاس تعریف می کند، ارزیابی کلی تری را جهت محدود کردن مقادیر دیگر مشخصات کاربر انجام داد. در جدول زیر مشخصات UserValidator نشان داده شده است.
ارزیابی جزئیات کاربر را می توان با ایجاد نمونه ای از کلاس UserValidator و تخصیص آن به مشخصه ی UserValidator کلاس user magaer داخل متد Create انجام داد. OWIN از متد Create برای ایجاد نمونه استفاده می کند. در قطعه کد زیر نمونه ای از استفاده از کلاس validator پیشفرض نشان داده شده است.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models; namespace Users.Infrastructure { public class AppUserManager : UserManager< AppUser > { public AppUserManager(IUserStore< AppUser > store) : base(store) { } public static AppUserManager Create(IdentityFactoryOptions< AppUserManager > options, IOwinContext context) { AppIdentityDbContext db = context.Get< AppIdentityDbContext >(); AppUserManager manager = new AppUserManager( new UserStore< AppUser >(db)); manager.PasswordValidator = new CustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true }; manager.UserValidator = new UserValidator< AppUser >(manager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true }; return manager; } } }
کلاس UserValidator پارامتر ژنریکی را می گیرد که این پارامتر نوع کلاس user (که در این حالت AppUser است) را مشخص می کند. آرگومان constructor این کلاس ، کلاس user manager بوده و این آرگومان نمونه ای از این کلاس است (در نرم افزار من منظور AppUserManager است).
ازریابی رمز عبور که به صورت پیشفرض وجود دارد، چندان پیشرفته نیست، اما می توانید با ایجاد کلاسی که از UserValidator مشتق شده باشد به صورت اختصاصی قوانین مربوط به ارزیابی رمز عبور را ایجاد کنید.
using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Users.Models; namespace Users.Infrastructure { public class CustomUserValidator : UserValidator< AppUser > { public CustomUserValidator(AppUserManager mgr): base(mgr) { } public override async Task< IdentityResult > ValidateAsync(AppUser user) { IdentityResult result = await base.ValidateAsync(user); if (!user.Email.ToLower().EndsWith("@example.com")) { var errors = result.Errors.ToList(); errors.Add("Only example.com email addresses are allowed"); result = new IdentityResult(errors); } return result; } } }
Constructor مربوط به کلاس مشتق شده باید نمونه ای از کلاس user manager را گرفته و base implementationرا فراخوانی کند تا بتوان validation check های پیشفرض را اجرا کرد. برای آنکه بتوان ارزیابی رمز عبور را به صورت اختصاصی پیاده سازی کرد باید متد ValidateAsync را override کرد. این متد نمونه ای از کلاس user را گرفته و شیء IdentityResult را برگشت می دهد. در مقررات ارزیابی اختصاصی ام، کاربران تنها می توانند آدرس ایمیل خود را به صورت دامنه های example.com وارد کنند.همچنین در این مقررات برای ترکیب پیام های خطای خودم با پیام هایی که توسط کلاس base تولید می شوند، از LINQ به شیوه سابق بهره برده ام.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models; namespace Users.Infrastructure { public class AppUserManager : UserManager< AppUser > { public AppUserManager(IUserStore< AppUser > store) : base(store) {} public static AppUserManager Create(IdentityFactoryOptions< AppUserManager > options, IOwinContext context) { AppIdentityDbContext db = context.Get< AppIdentityDbContext >(); AppUserManager manager = new AppUserManager( new UserStore< AppUser >(db)); manager.PasswordValidator = new CustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true }; manager.UserValidator = new CustomUserValidator(manager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true }; return manager; } } }
حالا برای مشاهده ی نتیجه، مانند شکل زیر حساب کاربری ای با یک آدرس ایمیل مانند bob@otherdomain.com ایجاد کنید.

تکمیل کردن ویژگی های ابزار مدیریتی (Administration)
حالا برای تکمیل ابزار مدیریتی خود تنها کافیست که ویژگی های مربوط به ویرایش و حذف کاربران را پیاده سازی کنم. در قطعه کد زیر می توانید تغییراتی که من در همین راستا در Admin controller بر روی فایل Views/Admin/Index.cshtml اعمال کرده ام را مشاهده کنید.
@using Users.Models @model IEnumerable< AppUser > @{ ViewBag.Title = "Index"; } < div class="panel panel-primary" > < div class="panel-heading" > User Accounts < /div > < table class="table table-striped" > < tr >< th >ID< /th >< th >Name< /th >< th >Email< /th >< th >< /th >< /tr > @if (Model.Count() == 0) { < tr >< td colspan="4" class="text-center" >No User Accounts< /td >< /tr > } else { foreach (AppUser user in Model) { < tr > < td >@user.Id< /td > < td >@user.UserName< /td > < td >@user.Email< /td > < td > @using (Html.BeginForm("Delete", "Admin", new { id = user.Id })) { @Html.ActionLink("Edit", "Edit", new { id = user.Id }, new { @class = "btn btn-primary btn-xs" }) < button class="btn btn-danger btn-xs" type="submit" > Delete < /button > } < /td > < /tr > } } < /table > < /div > @Html.ActionLink("Create", "Create", null, new { @class = "btn btn-primary" })
همانطور که می بینید من Html.ActionLink را فراخوانی کرده ام . Html.ActionLink درون محدوده ی Html.Beginhelper به متد Edit action رسیدگی می کند.
پیاده سازی ویژگی Delete
کلاس User manager متد DeleteAsync را تعریف می کند. این متد نمونه از کلاس User را دریافت کرده و آن را از دیتابیس حذف می کند. در قطعه زیر می توانید مشاهده کنید که من برای پیاده سازی ویژگی delete مربوط به admin controller چگونه از این متد استفاده کرده ام.
using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity.Owin; using Users.Infrastructure; using Users.Models; using Microsoft.AspNet.Identity; using System.Threading.Tasks; namespace Users.Controllers { public class AdminController : Controller { // ...other action methods omitted for brevity... [HttpPost] public async Task< ActionResult > Delete(string id) { AppUser user = await UserManager.FindByIdAsync(id); if (user != null) { IdentityResult result = await UserManager.DeleteAsync(user); if (result.Succeeded) { return RedirectToAction("Index"); } else { return View("Error", result.Errors); } } else { return View("Error", new string[] { "User Not Found" }); } } private void AddErrorsFromResult(IdentityResult result) { foreach (string error in result.Errors) { ModelState.AddModelError("", error); } } private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager< AppUserManager >(); } } } }
متد action ای من ایجاد کرده ام، ID منحصر به فرد کاربر را به صورت یک آرگومان دریافت می کند و برای شناسایی مکان شی کاربر متناظر و عبور دادن این شیء به متد DeleteAsync از متد FindByIdAsync استفاده کرده ام. نتیجه متد DeleteAsync ، IdentityResult است. برای آنکه مطمئن شوم که پیام خطایی برای کاربر نمایش داده می شود مانند مثال های قبل این نتیجه را پردازش می کنم. برای آنکه کارایی delete را امتحان کنید، تنها کافیست که کاربر جدیدی را ایجاد کرده و بر روی دکمه ی Delete که در Index view در کنار آن قرار دارد کلیک کنید.
با توجه به اینکه هیچ view ای مربوط به Delete action وجود ندارد، برای نشان دادن خطاها در پوشه ی Views/Shared فایلی به نام Error.cshtml را ایجاد کرده ام. محتوای داخل این فایل در قطعه کد زیر نمایش داده شده است.
@model IEnumerable< string > @{ ViewBag.Title = "Error";} < div class="alert alert-danger" > @switch (Model.Count()) { case 0: @: Something went wrong. Please try again break; case 1: @Model.First(); break; default: @: The following errors were encountered: < ul > @foreach (string error in Model) { < li >@error< /li > } } < /ul > break; < /div > @Html.ActionLink("OK", "Index", null, new { @class = "btn btn-default" })
این view را در پوشه ی Views/Shared قرار داده ام تا controller های دیگر مانند کنترلر ای که من برای مدیریت نقش ها و عضویت نقش محور در فصل 14 ایجاد کردم نیز بتوانند از آن استفاده کنند.
پیاده سازی ویژگی Edit
برای تکمیل کردن ابزارهای مدیریتی، باید امکان ویرایش آدرس های ایمیل و رمزعبور حساب های کاربری را نیز ایجاد کنم. در حال حاضر این مشخصه ها، مشخصه هایی هستند که توسط کاربران تعریف می شوند، اما در فصل 15 به شما یاد خواهم داد که به کمک مشخصه های اختصاصی این رویکرد را گسترش دهید. متد های edit action ای که من به Admin controller اضافه کرده ام را می توانید در قطعه کد زیر مشاهده کنید.
using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity.Owin; using Users.Infrastructure; using Users.Models using Microsoft.AspNet.Identity; using System.Threading.Tasks; namespace Users.Controllers { public class AdminController : Controller { // ...other action methods omitted for brevity... public async Task< ActionResult > Edit(string id) { AppUser user = await UserManager.FindByIdAsync(id); if (user != null) { return View(user); } else { return RedirectToAction("Index"); } } [HttpPost] public async Task< ActionResult > Edit(string id, string email, string password) { AppUser user = await UserManager.FindByIdAsync(id); if (user != null) { user.Email = email; IdentityResult validEmail = await UserManager.UserValidator.ValidateAsync(user); if (!validEmail.Succeeded) { AddErrorsFromResult(validEmail); } IdentityResult validPass = null; if (password != string.Empty) { validPass = await UserManager.PasswordValidator.ValidateAsync(password); if (validPass.Succeeded) { user.PasswordHash = UserManager.PasswordHasher.HashPassword(password); } else { AddErrorsFromResult(validPass); } } if ((validEmail.Succeeded && validPass == null) || ( validEmail.Succeeded && password != string.Empty && validPass.Succeeded)) { IdentityResult result = await UserManager.UpdateAsync(user); if (result.Succeeded) { return RedirectToAction("Index"); } else { AddErrorsFromResult(result); } } } else { ModelState.AddModelError("", "User Not Found"); } return View(user); } private void AddErrorsFromResult(IdentityResult result) { foreach (string error in result.Errors) { ModelState.AddModelError("", error); } } private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager< AppUserManager >(); } } } }
Edit action که مورد هدف درخواست های GET است، برای فراخوانی متد FindByIdAsync از رشته ی به کار رفته در Index view استفاده می کند تا شیء AppUser ای را دریافت کند که نمایانگر کاربر باشد.
طی پیاده سازی های پیچیده تر، درخواست POST به همراه آرگومان های ID کاربر، آدرس جدید ایمیل و رمز عبور دریافت می شود. برای تکمیل کردن عملیات ویرایش باید کارهای متعددی را انجام دهم.
اولین کار، ارزیابی مقادیری است که دریافت می کنم. در حال حاضر من با یک شی ء user ساده کار می کنم( هر چند که در فصل 15 به شما نشان خواهم داد که چگونه داده های ذخیره شده ی کاربران را شخصی سازی کنید) با این حال، برای آنکه مطمئن شوم که مقررات اختصاصی تعریف شده در بخش های « ارزیابی جزئیات مربوط به کاربر » و « ارزیابی رمزهای عبور» را نقض نمی کنم، باید داده های مربوط به کاربر را ارزیابی کنم. در همین راستا کار خود را با ارزیابی آدرس ایمیل آغاز می کنم، مانند زیر:
... user.Email = email; IdentityResult validEmail = await UserManager.UserValidator.ValidateAsync(user); if (!validEmail.Succeeded) { AddErrorsFromResult(validEmail); } ...
توجه داشته باشید که قبل از انجام ارزیابی من باید مقدار مشخصه Email را تغییر دهم، چون متد ValidateAsync تنها نمونه های کلاس user را قبول می کند.
قدم بعد تغییر رمز عبور است ( در صورت وارد شدن رمز عبور). ASP.NET Identity به جای ذخیره سازی خود رمز عبور، hash های آن ها را ذخیره سازی می کند تا از سرقت این رمزها جلوگیری شود. قدم بعدی که باید برداشته شود، ارزیابی رمز عبور و تولید کد hash ای است که در دیتابیس قرار است ذخیره شود تا بتوان هویت کاربر را محرز کرد.
رمزهای عبور از طریق پیاده سازی IpasswordHasherinterface که خود از طریق AppUserManager حاصل می شود به hash تبدیل می شوند. IpasswordHasherinterface متد HashPasswordmethod را تعریف می کند که این متد یک آرگومان رشته ای را گرفته و مقدار hash شده ی آن را برگشت می دهد. مانند زیر:
... if (password != string.Empty) { validPass = await UserManager.PasswordValidator.ValidateAsync(password); if (validPass.Succeeded) { user.PasswordHash = UserManager.PasswordHasher.HashPassword(password); } else { AddErrorsFromResult(validPass); } } ...
تا زمانی که متد UpdateAsync فراخوانی نشود، تغییرات اعمال شده بر کلاس user در دیتابیس ذخیره نمی شوند.
... if ((validEmail.Succeeded && validPass == null)|| ( validEmail.Succeeded && password != string.Empty && validPass.Succeeded)) { IdentityResult result = await UserManager.UpdateAsync(user); if (result.Succeeded) { return RedirectToAction("Index"); } else { AddErrorsFromResult(result); } } ...
ایجاد View
view آخرین مولفه ای است که باید ایجاد شود. وظیفه view رندر کردن مقادیر موجود برای کاربر و اجازه دادن به مقادیر جدید برای submit شدن به controller است. در قطعه کد زیر محتوای داخل فایل Views/Admin/Edit.cshtml نشان داده شده است.
@model Users.Models.AppUser @{ ViewBag.Title = "Edit"; } @Html.ValidationSummary(false) < h2 >Edit User< /h2 > < div class="form-group" > < label >Name< /label > < p class="form-control-static" >@Model.Id< /p > < /div > @using (Html.BeginForm()) { @Html.HiddenFor(x = > x.Id) < div class="form-group" > < label >Email< /label > @Html.TextBoxFor(x = > x.Email, new { @class = "form-control" }) < /div > < div class="form-group" > < label >Password< /label > < input name="password" type="password" class="form-control" / > < /div > < button type="submit" class="btn btn-primary" >Save< /button > @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default" }) }
مورد خاصی در این view وجود ندارد. این view، ID کاربر را نمایش می دهد و مانند شکل زیر فرمی را برای ویرایش آدرس ایمیل و رمز عبور ارائه می کند. لازم به ذکر است که آیدی کاربر به صورت متن استاتیک بوده و قابل تغییر نیست.

خلاصه
در این فصل به شما نشان دادم که چگونه پیکربندی و کلاس های مورد نیاز ASP.NET Identity را ایجاد کرده و از آن ها جهت ایجاد ابزارهای مدیریت کاربر استفاده کنید. در فصل بعد به شما نشان خواهم داد که چگونه به کمک ASP.NET Identity احراز هویت و اختیاردهی را انجام دهید.
برای مطالعه سرفصل آموزش پیشرفته MVC کلیک نمایید .