本篇體驗在ASP.NET MVC中使用ASP.NET Identity增刪改查用戶。css
源碼在這裏:https://github.com/darrenji/UseIdentityCRUDUserInMVChtml
在VS2013中建立一個MVC項目,用默認的"無身份驗證"做爲身份驗證機制。jquery
經過控制檯下載Bootstrap。git
Install-Package -version 3.0.3 bootstrapgithub
下載成功後,在解決方案下的Content和Scripts多了該版本的css和js文件。web
把建立項目默認HomeController中的全部Action以及/Views/Home下的全部視圖刪除。數據庫
熱熱身bootstrap
先來作一個簡單練習。服務器
在HomeController中的Index方法中,把一個字典傳遞給視圖。app
public class HomeController : Controller{public ActionResult Index(){Dictionary<string, object> data = new Dictionary<string, object>();data.Add("placeholder", "placeholder");return View(data);}}
_Layout.cshtml設置以下:
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ASP.NET Identity實戰</title><link href="~/Content/bootstrap.min.css" rel="stylesheet" /><link href="~/Content/bootstrap-theme.min.css" rel="stylesheet" /><style>.container {padding-top:10px;}.validation-summary-errors{color:red;}</style></head><body><div class="container">@RenderBody()</div>@Scripts.Render("~/bundles/jquery")@Scripts.Render("~/bundles/bootstrap")@RenderSection("scripts", required: false)</body>
Home/Index.cshtml視圖中:
@{ViewBag.Title = "Index";Layout = "~/Views/Shared/_Layout.cshtml";}<div class="panel panel-primary"><div class="panel-heading">用戶明細</div><table class="table table-striped">@foreach (string key in Model.Keys){<tr><th>@key</th><td>@Model[key]</td></tr>}</table></div>
前期準備
分別安裝以下組件。
Install-Package Microsoft.AspNet.Identity.EntityFramework –Version 2.0.0
Install-Package Microsoft.AspNet.Identity.OWIN -Version 2.0.0
Install-Package Microsoft.Owin.Host.SystemWeb -Version 2.1.0
配置Web.config以下:
<?xml version="1.0" encoding="utf-8"?><!--
有關如何配置 ASP.NET 應用程序的詳細信息,請訪問http://go.microsoft.com/fwlink/?LinkId=301880--><configuration><configSections><!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /></configSections><connectionStrings><add name="IdentityDb" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/></connectionStrings><appSettings><add key="webpages:Version" value="3.0.0.0" /><add key="webpages:Enabled" value="false" /><add key="ClientValidationEnabled" value="true" /><add key="UnobtrusiveJavaScriptEnabled" value="true" /><add key="owin:AppStartup" value="WebApplication4.IdentityConfig" /></appSettings><system.web><compilation debug="true" targetFramework="4.5" /><httpRuntime targetFramework="4.5" /></system.web><runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"><dependentAssembly><assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" /><bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" /><bindingRedirect oldVersion="1.0.0.0-5.0.0.0" newVersion="5.0.0.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" /><bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" /><bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" /></dependentAssembly><dependentAssembly><assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" /><bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" /></dependentAssembly></assemblyBinding></runtime><entityFramework><defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" /><providers><provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /></providers></entityFramework></configuration>
以上,
● 增長了connectionStrings節點,將自動建立localdb數據庫
● 在appSettings節點中增長了一個key爲owin:AppStartup項,這是確保OWIN運行正常的全局配置
在Models文件夾下建立以下類。
public class AppUser : IdentityUser{}
在解決方案下建立Infrastructure文件夾。
在Infrastructure文件夾下建立一個上下文類,須要實現IdentityDbContext<>接口。
public class AppIdentityDbContext : IdentityDbContext<AppUser>{public AppIdentityDbContext(): base("IdentityDb"){}static AppIdentityDbContext(){//使用EF Code First第一次建立的時候調用Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());}public static AppIdentityDbContext Create(){return new AppIdentityDbContext();}}//初始化public class IdentityDbInit : DropCreateDatabaseIfModelChanges<AppIdentityDbContext>{protected override void Seed(AppIdentityDbContext context){PerformInitialSetup(context);base.Seed(context);}//初始化工做public void PerformInitialSetup(AppIdentityDbContext context){ }}
在Infrastructure文件夾下建立一個管理用戶的類,須要繼承UserManager<AppUser>類。
還記得,先前在appSettings節點中配置了一個以下方式:
<add key="owin:AppStartup" value="WebApplication4.IdentityConfig" />
OWIN須要一個全局啓動文件,默認會到項目的頂級命名空間下找IdentityConfig這個類。
那就在App_Start中建立IdentityConfig這個類,這個類在WebApplication4這個命名空間下。namespace WebApplication4{public class IdentityConfig{public void Configuration(IAppBuilder app){app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);app.UseCookieAuthentication(new CookieAuthenticationOptions {AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,LoginPath = new Microsoft.Owin.PathString("/Account/Login")});}}}
顯示用戶
建立AdminController,如今能夠向視圖傳遞全部的用戶了,編寫以下:
public class AdminController : Controller{public ActionResult Index(){return View(UserManager.Users);}private AppUserManager UserManager{get{return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();}}}
再建立Admin/Index.cshtml類型爲IEnumerable<AppUser>的強類型視圖。
@model IEnumerable<WebApplication4.Models.AppUser>@{ViewBag.Title = "Index";Layout = "~/Views/Shared/_Layout.cshtml";}<div class="panel panel-primary"><div class="panel-heading">全部用戶帳戶</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">尚未建立用戶</td></tr>}else{foreach (WebApplication4.Models.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", new { id = user.Id },new { @class = "btn btn-primary btn-xs" })<button class="btn btn-danger btn-xs"type="submit">刪除</button>}</td></tr>}}</table></div>@Html.ActionLink("建立用戶", "Create", null, new { @class = "btn btn-primary" })
建立用戶
在Models文件夾下建立一個視圖模型。
namespace WebApplication4.Models{public class CreateModel{public string Id { get; set; }[Required]public string Name { get; set; }[Required]public string Email { get; set; }[Required]public string Password { get; set; }}}
在AdminController中添加建立用戶相關的方法。
public class AdminController : Controller{public ActionResult Index(){return View(UserManager.Users);}//建立顯示public ActionResult Create(){return View();}[HttpPost]public async Task<ActionResult> Create(CreateModel model){if(ModelState.IsValid){var user = new AppUser{UserName = model.Name, Email = model.Email};IdentityResult result = await UserManager.CreateAsync(user, model.Password);if(result.Succeeded){return RedirectToAction("Index");}else{AddErrorsFromResult(result);}}return View(model);}//建立接收private void AddErrorsFromResult(IdentityResult result){foreach(var error in result.Errors){ModelState.AddModelError("", error);}}private AppUserManager UserManager{get{return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();}}}
在Admin/Create.cshtml視圖頁中:
@model WebApplication4.Models.CreateModel@{ViewBag.Title = "Create";Layout = "~/Views/Shared/_Layout.cshtml";}<h2>Create</h2>@using (Html.BeginForm()){@Html.AntiForgeryToken()<div class="form-horizontal"><h4>建立用戶</h4><hr />@Html.ValidationSummary(true)<div class="form-group">@Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.EditorFor(model => model.Name)@Html.ValidationMessageFor(model => model.Name)</div></div><div class="form-group">@Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.EditorFor(model => model.Email)@Html.ValidationMessageFor(model => model.Email)</div></div><div class="form-group">@Html.LabelFor(model => model.Password, new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.EditorFor(model => model.Password)@Html.ValidationMessageFor(model => model.Password)</div></div><div class="form-group"><div class="col-md-offset-2 col-md-10"><input type="submit" value="建立用戶" class="btn btn-default" /></div></div></div>}<div>@Html.ActionLink("返回", "Index")</div>
點擊"建立"按鈕,建立成功返回顯示用戶頁面。
oh, my god,只是配置了一下就有數據了? 數據在哪呢?
點擊左上角的"服務器資源管理器",右鍵"IdentityDb",點擊"刷新"。
再打開AspNetUsers表,剛建立的用戶赫然在列。
好像還有點欠缺,用戶輸入密碼的時候,總應該有些限制吧。
能想到的,ASP.NET Identity都爲咱們準備好了。有一個PasswordValidator類就是幹這個的。
在Infrastructure文件夾中建立一個PasswordValidator類的繼承子類。
namespace WebApplication4.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("密碼中包含太多連續數字");result = new IdentityResult(errors);}return result;}}}
而後須要把這個規則告訴UserManager。
namespace WebApplication4.Infrastructure{public class AppUserManager : UserManager<AppUser>{public AppUserManager(IUserStore<AppUser> store) : base(store) { }public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context){//identity ef上下文AppIdentityDbContext db = context.Get<AppIdentityDbContext>();//與identity ef相關的UserStoreIUserStore<AppUser> us = new UserStore<AppUser>(db);AppUserManager manager = new AppUserManager(us);//密碼相關manager.PasswordValidator = new CustomPasswordValidator {RequiredLength = 6,RequireNonLetterOrDigit = false,RequireDigit = false,RequireLowercase = true,RequireUppercase = true};return manager;}}}
再次運行程序,建立用戶頁面,嘗試輸入不經過的密碼。
不過,關於密碼的規則,彷佛能夠在View Model的驗證層面就能夠解決掉。
編輯和刪除用戶
在AdminController中增長編輯和刪除的部分。
public class AdminController : Controller{public ActionResult Index(){return View(UserManager.Users);}//建立顯示public ActionResult Create(){return View();}//建立接收[HttpPost]public async Task<ActionResult> Create(CreateModel model){if(ModelState.IsValid){var user = new AppUser{UserName = model.Name, Email = model.Email};IdentityResult result = await UserManager.CreateAsync(user, model.Password);if(result.Succeeded){return RedirectToAction("Index");}else{AddErrorsFromResult(result);}}return View(model);}//編輯顯示public async Task<ActionResult> Edit(string id){AppUser user = await UserManager.FindByIdAsync(id);if(User != null){CreateModel createModel = new CreateModel();createModel.Id = user.Id;createModel.Email = user.Email;createModel.Name = user.UserName;createModel.Password = user.PasswordHash;return View(createModel);}else{return RedirectToAction("Index");}}//接收編輯[HttpPost]public async Task<ActionResult> Edit(CreateModel createModel){if(ModelState.IsValid){AppUser user = await UserManager.FindByIdAsync(createModel.Id);if (user != null){//關於郵箱user.Email = createModel.Email;IdentityResult validEmail = await UserManager.UserValidator.ValidateAsync(user);if (!validEmail.Succeeded){AddErrorsFromResult(validEmail);}user.UserName = createModel.Name;//關於密碼IdentityResult validPass = null;if (createModel.Password != string.Empty){validPass = await UserManager.PasswordValidator.ValidateAsync(createModel.Password);if (validPass.Succeeded){user.PasswordHash = UserManager.PasswordHasher.HashPassword(createModel.Password);}else{AddErrorsFromResult(validPass);}}user.Email = createModel.Email;//驗證結果if ((validEmail.Succeeded && validPass == null) || (validEmail.Succeeded&& createModel.Password != string.Empty && validPass.Succeeded)){IdentityResult result = await UserManager.UpdateAsync(user);if (result.Succeeded){return RedirectToAction("Index");}else{AddErrorsFromResult(result);}}else{ModelState.AddModelError("", "無此用戶");}}return View(createModel);}else{return View(createModel);}}//刪除[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[] { "沒有此用戶" });}}private void AddErrorsFromResult(IdentityResult result){foreach(var error in result.Errors){ModelState.AddModelError("", error);}}private AppUserManager UserManager{get{return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();}}}
Admin/Edit.cshtml視圖。
@model WebApplication4.Models.CreateModel@{ViewBag.Title = "Edit";Layout = "~/Views/Shared/_Layout.cshtml";}<h2>Edit</h2>@using (Html.BeginForm()){@Html.AntiForgeryToken()<div class="form-horizontal"><hr />@Html.ValidationSummary(true)@Html.HiddenFor(model => model.Id)<div class="form-group">@Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.EditorFor(model => model.Name)@Html.ValidationMessageFor(model => model.Name)</div></div><div class="form-group">@Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.EditorFor(model => model.Email)@Html.ValidationMessageFor(model => model.Email)</div></div><div class="form-group">@Html.LabelFor(model => model.Password, new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.EditorFor(model => model.Password)@Html.ValidationMessageFor(model => model.Password)</div></div><div class="form-group"><div class="col-md-offset-2 col-md-10"><input type="submit" value="保存" class="btn btn-default" /></div></div></div>}<div>@Html.ActionLink("返回", "Index")</div>
另外,若是刪除失敗,跳轉到Shared/Error.cshtml視圖頁。
@model IEnumerable<string>@{ ViewBag.Title = "Error";}<div class="alert alert-danger">@switch (Model.Count()){case 0:@: Something went wrong. Please try againbreak;case 1:@Model.First();break;default:@: 發現以下錯誤:<ul>@foreach (string error in Model){<li>@error</li>}</ul>break;}</div>@Html.ActionLink("肯定", "Index", null, new { @class = "btn btn-default" })
至此,使用ASP.NET Identy實現對用戶的增刪改查完畢,ASP.NET Identity真的很好很強大!