在ASP.NET MVC5 及 Visual Studio 2013 中为Identity帐户系统配置数据库连接及Code-First数据库迁移

在ASP.NET MVC5 及 Visual Studio 2013 中为Identity帐户系统配置数据库连接及Code-First数据库迁移


最近发布的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

  • 配置数据库链接
  • 配置Entity Framework数据库迁移及种子数据库
  • 种子数据库的初始用户记录
  • 扩展IdentityModel类使其具备附加属性
  • 更新数据库以反映实体类的修改
  • 更多的资源和感兴趣的项目
  • 在ASP.NET MVC5中扩展Identity帐户系统及实现基于角色的认证管理系统

ASP.NET Identity系统的基本组件

开箱即用,当你在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文件,会发如今 元素下有一个单节点,其中"DefaultConnection"被添加到一个连接字符串集合中。

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框架数据迁移和种子数据库


随着咱们项目的进展,咱们须要将对类的修改及时反映到数据库中。咱们也会想部署新的数据库或者发布了新的数据库版本,咱们将会须要一些初始数据(种子数据库)。相比于之前的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命令建立或更新种子数据库


当全部工做完成后,咱们在控制台输入以下命令:

Update-Database

当命令完成后,控制台应该是这样的:

若是一切顺利,咱们如今看到的数据库已经从新建立了,全部预期的表像之前同样。此外,若是咱们使用SQL命令SELECT * FROM dbo.AspNetUsers就会发现咱们如今有四个测试用户:

数据表dbo.AspNetUsers查询的结果:

如今,咱们已经制定了基本的迁移策略,让咱们来看看扩展ApplicationUser类加入一些额外的数据字段。

扩展IdentityModel类使其具备附加属性


根据新的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);
}

更新Register ViewModel


如今。咱们已经为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视图


咱们还须要修改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中测试,调整。加油。

相关文章
相关标签/搜索