你的位置:首页 > ASP.net教程

[ASP.net教程]ASP.NET Identity系列02,在ASP.NET MVC中增删改查用户


本篇体验在ASP.NET MVC中使用ASP.NET Identity增删改查用户。

 

源码在这里:https://github.com/darrenji/UseIdentityCRUDUserInMVC

 

在VS2013中创建一个MVC项目,用默认的"无身份验证"作为身份验证机制。

 

通过控制台下载Bootstrap。

 

Install-Package -version 3.0.3 bootstrap

 

下载成功后,在解决方案下的Content和Scripts多了该版本的css和js文件。

 

把创建项目默认HomeController中的所有Action以及/Views/Home下的所有视图删除。


热热身

 

先来做一个简单练习。


在HomeController中的Index方法中,把一个字典传递给视图。

 

  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>

 

1

 

前期准备


 

分别安装如下组件。

 

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如下:

 

<??>
<!--
 有关如何配置 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 ="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" })


4

 

创建用户

 

在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>


5

 

点击"创建"按钮,创建成功返回显示用户页面。

 

6

 

oh, my god,只是配置了一下就有数据了? 数据在哪呢?

 

点击左上角的"服务器资源管理器",右键"IdentityDb",点击"刷新"。

 

7

 

再打开AspNetUsers表,刚创建的用户赫然在列。

 

8

 

好像还有点欠缺,用户输入密码的时候,总应该有些限制吧。

 

能想到的,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相关的UserStore
      IUserStore<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;
    }
  }
}

 

再次运行程序,创建用户页面,尝试输入不通过的密码。

 

9


不过,关于密码的规则,似乎可以在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 again
      break;
    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真的很好很强大!