初识Identity(二)

 本文参考了【ASP.NET Identity系列教程(一)】ASP.NET Identity入门

一、Identity使用前准备项目

本文创建了一个名称为Users的项目。在创建过程中选择了“Empty(空)” 模板,并在“Add the folders and references(添加文件夹和引用)”中选中了“MVC”复选框。本文使用Bootstrap来设置视图的样式,因此在vs中的包管理器控制台中输入以下命令,并按回车,下载并安装这个NuGet包。

Install-Package -version 3.0.3 bootstrap

 

1、创建ASP.NET Identity数据库

  ASP.NET Identity未像Membership那样,被绑定到SQL Server架构,但关系型存储仍是默认的,而且是最简单的选择,这也是我接下来会使用的形式。

  ASP.NET Identity使用Entity Framework的Code First特性自动地创建它的数据架构(Schema),但我仍然需要创建一个用来放置此数据架构以及用户数据的数据库。我这里要使用localdb特性来创建数据库。要记住的是,localdb是包含在Visual Studio之中的,而且是一个简化版的SQL Server,能够让开发者方便地创建和使用数据库。

2、添加Identity包

  Identity是作为一组NuGet包发布的,可将其安装到任何项目,可在“Package Manager Console(包管理器控制台)”中寻找这些安装:

Install-Package Microsoft.AspNet.Identity.EntityFramework –Version 2.0.0(2.2.1)

Install-Package Microsoft.AspNet.Identity.OWIN -Version 2.0.0(2.2.1)

Install-Package Microsoft.Owin.Host.SystemWeb -Version 2.1.0(3.0.1)

3、更新Web.config文件

  为了做好项目使用ASP.NET Identity的准备,需要在Web.config文件中做两处修改。第一处是连接字符串,它描述了之前创建的数据库。第二处修改是定义一个应用程序设置,它命名对OWIN中间件进行初始化的类,并将它用于配置Identity。

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

注:要确保将connectionString的值放在一行中,ASP.NET要求是单一无断行的字符串。

4、创建Entity Framework类

  如果你曾使用过Membership,可能会奇怪ASP.NET Identity为什么需要这么多初始化准备。ASP.NET Identity具有Membership缺乏的可扩展性,但其代价是需要创建一组实现类,由Entity Framework用于管理数据库。在以下,我会演示如何创建所需要的这些类,以便让Entity Framework将它们用于ASP.NET Identity的存储系统。

1.创建用户类

  第一个要定义的类是表现一个用户的类,我这里将它称为“User Class(用户类)”。这个用户类派生于IdentityUser,它是在Microsoft.AspNet.Identity.EntityFramework命名空间中定义的。IdentityUser提供了基本的用户表示,可以通过在它派生的类中添加属性的办法,对这个类进行扩展。下表列出了IdentityUser所定义的内建属性,本文会使用这些属性。

  目前最重要的是这个IdentityUser类只提供了对用户基本信息的访问:用户名、E-mail、电话、哈希口令、角色成员等等。如果希望存储用户的各种附加信息,就需要在IdentityUser派生的类上添加属性,并将它用于表示应用程序中的用户。

注:(1)“内建”和“内置”在含义上是有区别的,“内建”完全是自己创建的,而“内置”有可能是别人做的东西拿来放入其中的。

      (2)Microsoft.AspNet.Identity.EntityFramework命名空间中的类是, Microsoft.AspNet.Identity命名空间中所定义接口的Entity Framework专用的具体实现。例如,IdentityUser便是IUser接口的实现。我使用这些具体类是因为还需要Entity Framework在数据库中存储用户数据。

IdentityUser类定义的属性

名称描述
Claims返回用户的声明集合
Email返回用户的E-mail地址
Id返回用户的唯一ID
Logins返回用户的登录集合
PasswordHash返回哈希格式的用户口令,在“实现Edit特性”中会用到它
Roles返回用户所属的角色集合
PhoneNumber返回用户的电话号码
SecurityStamp返回变更用户标识时被修改的值,例如被口令修改的值
UserName返回用户名

  为了创建应用程序中的用户类,我在Models文件夹中创建了一个类文件,名称为AppUser.cs,并用它创建了AppUser类。下列代码为AppUser.cs文件的内容。

using System;
using Microsoft.AspNet.Identity.EntityFramework; 

namespace Users.Models {
    public class AppUser : IdentityUser {
        // additional properties will go here
        // 这里将放置附加属性
    }
}

 

2.创建数据库上下文类

  本步骤是创建Entity Framework数据库的上下文,用于对AppUser类进行操作。这可以用Code First特性来创建和管理数据架构,并对数据库所存储的数据进行访问。这个上下文类派生于IdentityDbContext<T> ,其中的T是用户类(即此例中的AppUser)。我在项目中创建了一个文件夹,名称为Infrastructure,并在其中添加了一个类文件,名称为AppIdentityDbContext.cs,其代码如下:

using System.Data.Entity;
using Microsoft.AspNet.Identity.EntityFramework;
using Users.Models; 

namespace Users.Infrastructure {
    public class AppIdentityDbContext : IdentityDbContext<AppUser> {

        public AppIdentityDbContext() : base("IdentityDb") { }

        static AppIdentityDbContext() {
            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) {
            // initial configuration will go here
            // 初始化配置将放在这儿
        }
    }
}

  这个AppIdentityDbContext类的构造器调用了它的基类,其参数是连接字符串的名称,IdentityDb,用于与数据库连接。这是让下列代码定义的连接字符串与ASP.NET Identity联结起来的方法。

  这个AppIdentityDbContext类还定义了一个静态的构造器,它使用Database.SetInitializer方法指定了一个种植数据库的类(种植数据库的含义是往数据库中植入数据的意思,说穿了,就是用一些数据对数据库进行初始化——译者注),当通过Entity Framework的Code First特性第一次创建数据库架构时,会用到这个类。这个种植类叫做IdentityDbInit,而且我已经提供了一个类,创建了一个占位符,后面可以回过头来在PerformInitialSetup方法中添加语句,就可以种植数据库了。

最后,AppIdentityDbContext类定义了一个Create方法。这是由OWIN在必要时创建类实例的办法,这个由OWIN使用的类将在“创建启动类”中进行描述。

3. 创建用户管理器类

  最重要的Identity类之一是“User Manager(用户管理器)”,用来管理用户类实例。用户管理器类必须派生于UserManager<T> ,其中T是用户类。这个UserManager<T> 类并非是专用于Entity Framework的,而且它提供了很多通用特性,用以创建用户并对用户数据进行操作。下表列出了UserManager<T> 类为管理用户而定义的基本方法和操作。还有一些其他方法,这里并未全部列出来,我会在适当的情形下对它们进行描述,那时会介绍管理用户数据的不同方式。

UserManager<T>类所定义的基本方法和操作
名称描述
ChangePasswordAsync(id, old, new)为指定用户修改口令
CreateAsync(user)创建一个不带口令的新用户
CreateAsync(user, pass)创建一个带有指定口令的新用户
DeleteAsync(user)删除指定用户
FindAsync(user, pass)查找代表该用户的对象,并认证其口令
FindByIdAsync(id)查找与指定ID相关联的用户对象
FindByNameAsync(name)查找与指定名称相关联的用户对象
UpdateAsync(user)将用户对象的修改送入数据库
Users返回用户枚举

  请注意方法名以Async结尾的那些方法。因为ASP.NET Identity几乎完全是用C#的异步编程特性实现的,这意味着会并发地执行各种操作,而不会阻塞其他活动。在我开始演示如何创建和管理用户数据时,你便会看到这种情况。对于每一个Async方法也有相应的同步扩展方法。对于大多数示例,我会坚持这种异步方法。但是,如果你需要按顺序执行一些相关的操作,同步方法是有用的。如果你要是想从属性内部的getter或setter代码块中调用Identity方法时,同步方法也是有用的。

  我在Infrastructure文件夹中添加了一个类文件,名称为AppUserManager.cs,用它定义了用户管理器类,名称为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));
            return manager;
        }
    }
}

在Identity需要一个AppUserManager的实例时,将会调用静态的Create方法,这种情况将在对用户数据执行操作时发生——也是在完成设置之后要演示的事情。

为了创建AppUserManager类的实例,我需要一个UserStore<AppUser> 实例。这个UserStore<T> 类是IUserStore<T> 接口的Entity Framework实现,它提供了UserManager类所定义的存储方法的实现。为了创建UserStore<AppUser> ,又需要AppIdentityDbContext类的一个实例,这是通过OWIN按如下办法得到的:

AppIdentityDbContext db = context.Get<AppIdentityDbContext>();

4. 创建启动类

为了使ASP.NET Identity就绪并能运行,要做的最后一个片段是Start Class(启动类)。在清下列代码中,我定义了一个应用程序设置,它为OWIN指定配置类,如下所示:

<add key="owin:AppStartup" value="Users.IdentityConfig" />

 OWIN是独立出现在ASP.NET中的,并且有它自己的约定。其中之一就是,为了加载和配置中间件,并执行所需的其他配置工作,需要有一个被实例化的类。默认情况下,这个类叫做Start,而且是在全局命名空间中定义的。这个类含有一个名称为Configuration的方法,该方法由OWIN基础架构进行调用,并为该方法传递一个Owin.IAppBuilder接口的实现,由它支持应用程序所需中间件的设置。Start类通常被定义成分部类,还有一些其他的类文件,它们分别专用于要用的每一种中间件。

我随意地忽略了这一约定,因为我在MVC框架应用程序中使用的唯一OWIN中间件就是Identity。为了在应用程序的顶级命名空间定义一个类,我喜欢在Web.config文件中使用应用程序设置。于是我在App_Start文件夹中添加了一个类文件,名称为IdentityConfig.cs,并用它定义了如下的类,是我在Web.config文件中指定的一个类。

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Users.Infrastructure; 

namespace Users {
    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 PathString("/Account/Login"),
            });
        }
    }
}

IAppBuilder接口是由一些扩展方法提供的,这些扩展方法的定义在Owin命名空间的一些类中。CreatePerOwinContext用于创建AppUserManager的新实例,AppIdentityDbContext类用于每一个请求。这样保证每一个请求对ASP.NET Identity数据有清晰的访问,我不必为同步时的情况或匮乏的数据缓存而操心。

UseCookieAuthentication方法告诉ASP.NET Identity如何用Cookie去标识已认证的用户,以及通过CookieAuthenticationOptions类指定的选项在哪儿。这里重要的部分是LoginPath属性,它指定了一个URL,这是未认证客户端请求内容时要重定向的地址。我在其中指定了/Account/Login,创建这个控制器来处理这些重定向。

 

 5、Identity的使用

1.枚举账号管理

  集中化的用户管理工具在几乎所有应用程序中都是有用的,即使是那些允许用户创建并管理自己账号的情况也是如此。例如,总会有这样一些客户,他们需要大量创建账号,并支持需要检查和调整用户数据的问题。根据本章的观点,管理工具是有用的,因为它们将许多基本的用户管理功能整合到了少量的几个类之中,这对于演示ASP.NET Identity的基本特性是很有用的。

  首先,在项目中添加一个控制器,名称为Admin,如清单13-9所示,我将用它定义我的用户管理功能。

 

using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity.Owin;
using Users.Infrastructure; 

namespace Users.Controllers {
    public class AdminController : Controller {

        public ActionResult Index() {
            return View(UserManager.Users);
        }

        private AppUserManager UserManager {
            get {
                return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
            }
        }
    }
}

  动作方法枚举由Identity系统管理的用户,当然,此刻还没有任何用户,但马上就会有了。该清单重要的部分是我获得类实例的方式,通过它,我可以管理用户信息。在我实现不同的管理功能时,我会反复使用AppUserManager类,而且,我在Admin控制器中定义了UserManager属性,以便简化代码。

   Microsoft.Owin.Host.SystemWeb程序集为HttpContext类添加了一些扩展方法,其中之一便是GetOwinContext。它通过一个IOwinContext对象,将基于每请求的上下文对象提供给WOIN API。MVC框架应用程序对IOwinContext不感兴趣,但是有另外一个扩展方法,叫做GetUserManager<T> ,可以用来得到用户管理器类的实例。

  就像我们看到的,ASP.NET Identity中有许多扩展方法,总体而言,这个API是一种混合体,它试图将WOIN、抽象Identity功能以及具体的Entity Framework存储实现混合在一起。

  我用一个泛型参数调用了GetUserManager,用以指定本章前面创建的AppUserManager类,如下所示:

 return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();

一旦有了AppUserManager类的实例,便可以开始查询数据存储了。AppUserManager.Users属性返回了一个用户对象的枚举——应用程序中AppUser类的实例——于是可以用LINQ对这个枚举进行查询和维护。

Index动作方法中,给View方法传递了Users属性的值,以便能够在视图列出用户的细节。下面代码显示了Views/Admin/Index.cshtml文件的内容,这是通过右击Index动作方法,并从弹出菜单选择“Add View(添加视图)”而创建的。

@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></tr>
        @if (Model.Count() == 0) {
            <tr><td colspan="3" 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>
                </tr>
            }
        }
    </table>
</div>
@Html.ActionLink("Create", "Create", null, new { @class = "btn btn-primary" })

这个视图含有一个表格,每个用户为一行,带有唯一ID、用户名以及E-mail地址的表格列。如果数据库中没有用户,那么会显示一条消息“No User Accounts”消息。

在视图中有一个Create的链接(其样式用Bootstrap形成了一个按钮),它的目标是Admin控制器中的Create动作。为了支持添加用户,我会很快实现这个动作。

当启动应用程序并导航到/Admin/Index URL时,在视图渲染的内容被显示出来之前,会花一些时间。这是因为Entity Framework已经链接到数据库,并发现此时尚未定义数据库架构。Code First特性使用本章前面定义的类(以及包含在Identity程序集中的类)创建该架构,以便做好查询和存储数据的准备。

通过打开Visual Studio的“SQL Server Object Explorer(SQL Server对象资源管理器)”窗口,并展开IdentityDB数据库架构,便可以看到其中包含了一些数据表,如AspNetUsers和AspNetRoles等等 。

要删除该数据库,右击IdentityDb条目,并从弹出菜单选择“Delete(删除)”。选中“Delete Database(删除数据库)”对话框中的复选框,并点击OK,以删除该数据库。

右击“Databases(数据库)”条目,选择“Add New Database(添加新数据库)”(如图13-3所示),并在“Database Name(数据库名)”字段中输入IdentityDb,点击OK,便可以创建空数据库。下一次启动应用程序,并导航到Admin/Index URL时,Entity Framework又将侦测到没有数据库架构,又会重新创建此数据库。

2.创建用户

对于应用程序接收的输入,我打算使用MVC架构的模型验证,最容易的做法是为控制器所支持的每一种操作创建一个简单的视图模型。我在Models文件夹中添加了一个类文件,名称为serViewModels.cs,并用它定义了如下所示的类。随着我为其他特性定义模型,会进一步地在这个文件中添加一些类。

using System.ComponentModel.DataAnnotations; 

namespace Users.Models {

    public class CreateModel {
        [Required]
        public string Name { get; set; }
        [Required]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

我所定义的第一个模型叫做CreateModel,它定义了创建用户账号所需要的基本属性:用户名、E-mail地址以及口令。这个模型中所定义的所有属性都使用了System.ComponentModel.DataAnnotations命名空间中的Required注释属性,用以说明这些值是必须的。

我在Admin控制器中添加了一对Create动作方法,它们是上一小节Index视图中的那个链接所指向的目标,而且,对于GET请求以及处理表单数据的POST请求,使用的是标准的控制器模式将视图表现给用户。可以从下列代码中看到这两个新的动作方法。

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 {

        public ActionResult Index() {
            return View(UserManager.Users);
        }

        public ActionResult Create() {
            return View();
        }

        [HttpPost]
        public async Task<ActionResult> Create(CreateModel model) {
            if (ModelState.IsValid) {
                AppUser 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 (string error in result.Errors) {
                ModelState.AddModelError("", error);
            }
        }

        private AppUserManager UserManager {
            get {
                return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
            }
        }
    }
}

该代码重要的部分是以CreateModel为参数并在管理员递交其表单数据时调用的那个Create动作方法(POST版的Create动作方法——译者注)。我用ModelState.IsValid属性检查所接收到的数据是否包含了我所需要的数据,如果是,便创建一个AppUser类的新实例,并将它传递给UserManager.CreateAsync方法,如下所示:

...
AppUser user = new AppUser {UserName = model.Name, Email = model.Email};
IdentityResult result = await UserManager.CreateAsync(user, model.Password); 
...

CreateAsync方法的结果是一个IdentityResult接口的实现,它通过下表的属性描述操作的输出。

名称描述
Errors返回一个字符串枚举,其中列出了尝试操作期间所遇到的错误
Succeeded在操作成功时返回true

可以看到,维护用户数据的所有操作,如上述所用到的UserManager.CreateAsync方法,都可以作为异步方法来使用。这种方法很容易与async和await关键字一起使用。使用异步的Identity方法让你的动作方法能够异步执行,这可以从整体上改善应用程序。

同时也也可以使用那些由Identity API提供的同步扩展方法。所有常用的异步方法都有一个同步的封装程序,因此UserManager.CreateAsync方法的功能可以通过同步的UserManager.Create方法进行调用。当需要执行多个有依赖的操作时,为了形成简单的代码,同步方法可能是有用的。

Create动作方法中,我检测了Succeeded属性,以确定是否能够在数据库创建一条新的用户记录。如果Succeeded属性为true,那么便将浏览器重定向到Index动作,以便显示用户列表:

...
if (result.Succeeded) {
    return RedirectToAction("Index");
} else {
    AddErrorsFromResult(result);
}
...

如果Succeeded属性为false,那么便调用AddErrorsFromResult方法,该方法枚举了Errors属性中的消息,并将它们添加到模型状态的错误消息集合中,此过程利用了MVC框架的模型验证特性。我定义AddErrorsFromResult方法,是因为随着进一步地构建这个管理控制器的功能,还必须处理来自其他操作的错误消息。最后一步是创建视图,让管理员创建新账号。

@model Users.Models.CreateModel
@{ ViewBag.Title = "Create User";}

<h2>Create User</h2>
@Html.ValidationSummary(false)
@using (Html.BeginForm()) {
    <div class="form-group">
        <label>Name</label>
        @Html.TextBoxFor(x => x.Name, new { @class = "form-control"})
    </div>
    <div class="form-group">
        <label>Email</label>
        @Html.TextBoxFor(x => x.Email, new { @class = "form-control" })
    </div>
    <div class="form-group">
        <label>Password</label>
        @Html.PasswordFor(x => x.Password, new { @class = "form-control" })
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
    @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default"})
}

该视图只是一个简单的表单,用来收集一些值,MVC框架会将它们绑定到模型类的属性上,然后传递给Create动作方法。

转载于:https://www.cnblogs.com/Pinapple/p/6739768.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值