最近發佈的ASP.NET MVC 5 及Visual Studio 2013中爲咱們帶來的最顯著的變化就是Identity
帳戶管理系統(原來使用ASP.NET Forms Membership)。此外,還有一些Entity Framework Code-First數據庫遷移的細微變化。html
在這篇文章中,咱們將回顧Identity帳戶管理系統的一些細節,它們保存在您指定的外部Sql Server(或您選擇的任何其餘數據庫)中,而不是在App_Data文件夾中默認的本地數據庫而且配置Entity Framework遷移帶種子數據的數據庫遷移。jquery
開箱即用,當你在Visual Studio 2013中使用默認的模板建立一個ASP.NET MVC5項目,您會獲得一個已經包括基本的身份和帳戶管理系統的能夠運行的網站。在當前的配置下,當您點擊註冊帳戶時,將在您項目的APP_DATA文件夾中建立一個SQL Server CE(.sdf)或SQL Express(.mdf)數據庫文件。redis
在默認的MVC5項目解決方案資源管理器的Identity賬戶類:數據庫
在Identity系統中, IdentityModel.cs是必不可少的。在代碼編輯器中打開這個文件,咱們將會看到定義了兩個類:後端
在 IdentityModel.cs文件中的代碼:架構
using Microsoft.AspNet.Identity.EntityFramework; namespace DbMigrationExample.Models { public class ApplicationUser : IdentityUser { } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection") { } } }
ApplicationUser類,繼承自Identity框架的IdentityUser類。這是ASP.NET MVC 5中,身份管理系統的基礎單元。這個類在默認的項目代碼中定義是空的,所以只能使用基類IdentityUser類公開的基本屬性。咱們能夠經過增長咱們本身的屬性來擴展ApplicationUser類,並反映在數據庫中。更多的相關知識mvc
在這裏咱們還發現了另外一個類ApplicationDbContext,這是用於將咱們的程序用戶數據與Entity Framework框架互動並持久化數據庫(該數據庫可能會與應用程序其餘部分的數據庫並不相同)。須要注意的是,這個類並無繼承自DbContext類(一般使用Entity Framework框架的狀況下,會從這個類繼承)。換句話說,ApplicationDbContext繼承於定義在Microsoft.AspNet.Identity.EntityFramework中包含"Code-First"爲基類的Identity系統的一部分。框架
經過這兩個類,MVC框架提供了完整的生成和使用Identity帳戶的數據庫系統。一樣的,咱們能夠經過擴展基類來知足咱們本身的須要。asp.net
最後,請注意AccountViewModels.cs文件,在這裏實際上定義了咱們應用程序視圖所使用的ViewModels。經過這種設計,使得View得到知足須要的信息來渲染View,避免面向公衆的應用程序暴露過多的內部數據信息。ViewModels從架構來看是很是有用的設計組件,而且有效的防止了數據的泄露。async
正如咱們前面所提到的同樣,MVC框架默認的會在咱們的應用程序的App_Data文件夾中生成Sql CE或Sql Express的數據文件,除非咱們明確的告訴框架不須要這樣作。咱們須要作的就是,改變ApplicationDbContext鏈接字符串,將其指向咱們真正的生產環境數據庫。
ApplicationDbContext類在構造函數中向基類傳遞了一個名爲"DefaultConnection"的鏈接字符串。若是咱們打開Web.Config文件,會發如今
Web.Config文件中的鏈接字符串:
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v110; AttachDbFilename=|DataDirectory|\aspnet-DbMigrationExample-20131027114355.mdf;Initial Catalog=aspnet-DbMigrationExample-20131027114355;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>
改變目標數據庫的最簡單作法就是修改Web.Config文件中的"DefaultConnection"鏈接字符串的詳細信息。在這種狀況下,咱們將會把連接替換成SQL CE的數據庫鏈接,由於我已經在個人開發機器上部署了這種數據庫(可是,您也可使用你所但願的任何數據庫)。
指向本地Sql Server實例的"DefaultConnection":
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=XIVMAIN\XIVSQL; Initial Catalog=DbMigrationExample;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>
如今,咱們來啓動這個VS2013 MVC項目,咱們將會看到以下的界面
默認MVC項目的模板主頁:
在這個頁面上,我點擊右上角的「註冊」按鈕:
默認MVC項目模板的註冊頁面:
當我填寫完註冊內容,並點擊「Register」按鈕,我將會被引導回到主頁。主頁頁面上將顯示我已經做爲註冊用戶而且已經登陸到系統中了。
這一切都不是使人驚訝的,咱們作這些的緣由是爲了看到咱們剛纔註冊所生成的數據庫。打開SQL Server Management Studio (SSMS),咱們將會看到一個「DbMigrationExample」的新數據庫:
請注意,在這裏建立了不少數據表。其實咱們只是定義了一個ApplicationUser類,其餘的都是由繼承自IdentityDbContext類的ApplicationDbContext類所建立。
項目的默認配置實際上只使用了dbo.AspNetUsers數據表,可是,你已經能夠看到已經建立了一個全方位的身份管理數據表,包括角色管理和外部受權(使用Google/Facebook/Twitter的帳戶進行第三方登陸)。
隨着咱們項目的進展,咱們須要將對類的修改及時反映到數據庫中。咱們也會想部署新的數據庫或者發佈了新的數據庫版本,咱們將會須要一些初始數據(種子數據庫)。相比於之前的Entity Framework和MVC版本,咱們可使用EF Code First來實現這個目的。
在開始以前,我須要刪除註冊時建立的數據庫,而且從新開始。
爲了開始在你的項目中使用數據遷移,請在Visual Studio中選擇工具菜單中的「NuGet程序包管理器」-> " 程序包管理器控制檯"。程序包管理器控制檯會出如今屏幕底部,咱們只須要輸入以下代碼:
Enable-Migrations –EnableAutomaticMigrations
一旦咱們按下回車鍵,系統將會在忙碌一會,生成Migrations文件夾並進行相關配置。當任務運行完畢後,咱們的控制檯窗口應該是這樣的:
在控制檯啓用Enable-Migrations命令後:
因爲種種緣由,咱們可能須要在建立數據庫的時候部署一些初步的數據記錄。咱們可能須要對數據庫的某些表預填一些靜態值,或者咱們可能只是須要用於系統開發工做的一些測試數據。咱們將部署一個填充了一對重複的數據記錄的數據庫。
一旦咱們運行Enable-Migrations命令後,在項目的根目錄中將會生成一個Migrations文件夾。若是咱們打開這個文件夾中的Configuration.cs文件,咱們將會看到以下內容:
namespace DbMigrationExample.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; internal sealed class Configuration : DbMigrationsConfiguration<DbMigrationExample.Models.ApplicationDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; } protected override void Seed(DbMigrationExample.Models.ApplicationDbContext 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" } // ); // } } }
咱們須要像下面同樣修改Configuration類,這樣就會在數據庫被建立的同時建立咱們的測試數據。請注意,咱們增長了一下Using語句,包括Microsoft.AspNet.Identity,Microsoft.AspNet.Identity.EntityFramework以及項目中的Models:
修改Migrations文件夾中的Configuration.cs文件中的代碼以下:
using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using DbMigrationExample.Models; namespace DbMigrationExample.Migrations { internal sealed class Configuration : DbMigrationsConfiguration<DbMigrationExample.Models.ApplicationDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; } protected override void Seed(ApplicationDbContext context) { var manager = new UserManager<ApplicationUser>( new UserStore<ApplicationUser>( new ApplicationDbContext())); for (int i = 0; i < 4; i++) { var user = new ApplicationUser() { UserName = string.Format("User{0}", i.ToString()) }; manager.Create(user, string.Format("Password{0}", i.ToString())); } } } }
咱們已經在用戶數據表中添加了種子數據,如今咱們在控制檯中輸入以下命令:
Enable-Migration初始化命令:
Add-Migration Init
當命令完成運行(這可能須要幾秒鐘)咱們的控制檯窗口看起來是這樣的:
Enable-Migration Init執行後,控制檯的輸出:
在這個點上咱們的Add-Migration Init建立了,咱們再也不像之前同樣須要建立腳手架代碼。若是咱們打開Migrations文件夾會發現包含測試數據的代碼文件已經生成了。然而,咱們並無去修改或建立數據庫。
當全部工做完成後,咱們在控制檯輸入以下命令:
Update-Database
當命令完成後,控制檯應該是這樣的:
若是一切順利,咱們如今看到的數據庫已經從新建立了,全部預期的表像之前同樣。此外,若是咱們使用SQL命令SELECT * FROM dbo.AspNetUsers就會發現咱們如今有四個測試用戶:
數據表dbo.AspNetUsers查詢的結果:
如今,咱們已經制定了基本的遷移策略,讓咱們來看看擴展ApplicationUser類加入一些額外的數據字段。
根據新的Asp.Net Identity Model,他擴充了基本的用戶信息內容,是咱們的應用程序管理相關內容更加容易。例如,假設咱們但願咱們的用戶信息,包含電子郵件地址,以及完整的名字和姓氏。咱們能夠爲這些項目在ApplicationUser類中添加屬性。而後更新Controllers, ViewModels, 和Views,使得用戶註冊時,填寫這些新增的內容。
首先,讓咱們回到ApplicationUser類,並添加咱們想要的屬性:
using Microsoft.AspNet.Identity.EntityFramework; // Add this to bring in Data Annotations: using System.ComponentModel.DataAnnotations; namespace DbMigrationExample.Models { public class ApplicationUser : IdentityUser { [Required] public string FirstName { get; set; } [Required] public string LastName { get; set; } [Required] public string Email { get; set; } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection") { } } }
在上文中,咱們增長了咱們所須要的三個屬性到ApplicationUser類中,而且增長了[Required]屬性。爲了實現這個屬性的增長,咱們必須在類文件的頂部增長 Using System.ComponentModel.DataAnnotations;
更新AccountController的Register方法
咱們須要更新AccountController的Register方法。目前,該代碼建立ApplicationUser並設置UserName屬性:
目前,只設置了用戶名屬性:
var user = new ApplicationUser() { UserName = model.UserName };
咱們須要添加如下內容(如下注釋的代碼),使咱們的控制器正常工做:
更新AccountController的Register方法來設置新的用戶屬性:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { // Add the following to populate the new user properties // from the ViewModel data: var user = new ApplicationUser() { UserName = model.UserName, FirstName = model.FirstName, LastName = model.LastName, Email = model.Email }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
如今。咱們已經爲ApplicationUser類添加了新的用戶屬性。同時咱們還須要爲用戶在註冊過程當中提供輸入值的新的方法。若是咱們打開 AccountViewModels.cs文件,咱們會看到定義了一些ViewModel類。最底部是RegisterViewModel類,目前它看來是這樣的:
默認的RegisterViewModel類:
public class RegisterViewModel { [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } }
咱們要添加咱們的新特性,因此咱們修改以下:
修改RegisterViewModel類:
public class RegisterViewModel { [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } [Required] [Display(Name = "First name")] public string FirstName { get; set; } [Required] [Display(Name = "Last name")] public string LastName { get; set; } [Required] [Display(Name = "Email")] public string Email { get; set; } }
咱們還須要修改Register.cshtml來匹配視圖,在Views => Account文件夾打開Register.cshtml文件,它應該是這樣的:
默認的Register.cshtml文件:
@model DbMigrationExample.Models.RegisterViewModel @{ ViewBag.Title = "Register"; } <h2>@ViewBag.Title.</h2> @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() <h4>Create a new account.</h4> <hr /> @Html.ValidationSummary() <div class="form-group"> @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" class="btn btn-default" value="Register" /> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
在「ConfirmPassword」屬性後添加新的屬性:
修改Register.cshml文件:
<h2>@ViewBag.Title.</h2> @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() <h4>Create a new account.</h4> <hr /> @Html.ValidationSummary() <div class="form-group"> @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) </div> </div> // Add new properties here: <div class="form-group"> @Html.LabelFor(m => m.FirstName, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.LastName, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.LastName, new { @class = "form-control" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" class="btn btn-default" value="Register" /> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
到目前爲止,咱們已經修改了咱們的數據模型實體類,即ApplicationUser類。在咱們的應用程序中,EF框架爲咱們將這個類映射到後端數據庫中的dbo.AspNetUsers數據表。爲了咱們更新的內容咱們須要再次運行遷移。在作遷移以前,咱們須要作一件事情。咱們須要將新的FirstName, LastName和Email屬性到咱們的種子數據庫中。
更新Seed方法:
protected override void Seed(ApplicationDbContext context) { var manager = new UserManager<ApplicationUser>( new UserStore<ApplicationUser>( new ApplicationDbContext())); for (int i = 0; i < 4; i++) { var user = new ApplicationUser() { UserName = string.Format("User{0}", i.ToString()), // Add the following so our Seed data is complete: FirstName = string.Format("FirstName{0}", i.ToString()), LastName = string.Format("LastName{0}", i.ToString()), Email = string.Format("Email{0}@Example.com", i.ToString()), }; manager.Create(user, string.Format("Password{0}", i.ToString())); } }
如今,若是咱們再次運行Update-Database命令,咱們對實體對象的修改將會反應到dbo.AspNetUsers數據表的架構中,可是,咱們的種子數據將不會被更新,由於Entity Framework不能這樣作,這樣作將會致使數據丟失等嚴重的後果。雖然有不少方法能夠完成這種任務,可是這已經超出了本文的探討範圍。在這裏,咱們將手工刪除數據庫而且再次運行Update-Database命令。然而,EF框架會認爲咱們的數據來源於已經存在的遷移命令,因此咱們必須執行Update-Database -force命令。
一旦咱們手工刪除了數據庫,而且執行了Update-Database –force命令,咱們的控制檯輸出應該是這樣的:
快速從新運行咱們的查詢代表,事實上,新的字段被添加到咱們的表,而且測試數據填充完畢:
如今,咱們已經更新了咱們的Registration.cshtml視圖,控制器的方法,ViewModel和數據庫,當咱們運行咱們的應用程序,並去註冊時,咱們看到更新後登記表格界面:
一旦咱們完成表單並點擊註冊按鈕,咱們成功登陸,咱們完美的將數據持久化到數據庫中,隨時在咱們的應用中使用。
另外,咱們也能夠註銷,並做爲咱們的測試用戶經過點擊「登陸」連接來測試登陸:
成功登陸:
更新到ASP.NET MVC 5會得到更多的好處並不是常酷的。在本文中,我只是介紹基本的更新帳戶管理的內容。可是ASP.NET和MVC爲咱們作了恨多的工做,使咱們可以很容易的完成基於角色的身份管理,而且能夠將社交帳號集成進來(例如Google +, Facebook, 和Twitter.)。
英文很差,這麼好的資料,如今纔看到。目測改代碼在VS2013+mvc5中可以完美從新,只是EF框架及一些組件的小版本變化須要調整。而後,本身將在VS2015 RC + MVC6中測試,調整。加油。