【ASP.NET Identity系列教程(1)】ASP.NET Identity入门
13.2.4 Creating the Entity Framework Classes
13.2.4 创建Entity Framework类
1. 创建用户类
第一个要定义的类是表现一个用户的类,我将它称为“User Class(用户类)”。这个用户类派生于IdentityUser,它是在Microsoft.AspNet.Identity.EntityFramework命名空间中定义的。IdentityUser提供了基本的用户表示,可以通过在它派生的类中添加属性的办法,对这个类进行扩展,我会在第15章中对此进行描述。表13-2列出了IdentityUser所定义的内建属性(现在流行将“内建(Built-in)”说成“内置”,但我不会人云亦云,这两者在含义上是有区别的,“内建”完全是自己创建的,而“内置”有可能是别人做的东西拿来放入其中的——译者注),本章将使用这些属性。
| Name 名称 | Description 描述 |
|---|---|
| Claims | Returns the collection of claims for the user, which I describe in Chapter 15 返回用户的声明集合,关于声明(Claims)将在第15章描述 |
| Returns the user's e-mail address 返回用户的E-mail地址 |
|
| Id | Returns the unique ID for the user 返回用户的唯一ID |
| Logins | Returns a collection of logins for the user, which I use in Chapter 15 返回用户的登录集合,将在第15章中使用 |
| PasswordHash | Returns a hashed form of the user password, which I use in the “Implementing the Edit Feature” section 返回哈希格式的用户口令,在“实现Edit特性”中会用到它 |
| Roles | Returns the collection of roles that the user belongs to, which I describe in Chapter 14 返回用户所属的角色集合,将在第14章描述 |
| PhoneNumber | Returns the user's phone number 返回用户的电话号码 |
| SecurityStamp | Returns a value that is changed when the user identity is altered, such as by a password change 返回变更用户标识时被修改的值,例如被口令修改的值 |
| UserName | Returns the username 返回用户名 |
提示:Microsoft.AspNet.Identity.EntityFramework命名空间中的类是, Microsoft.AspNet.Identity命名空间中所定义接口的Entity Framework专用的具体实现。例如,IdentityUser便是IUser接口的实现。我会使用这些具体类,因为我要依靠Entity Framework在数据库中存储我的用户数据,等到ASP.NET Identity变得成熟时,你可能会期望看到这些接口的其他实现,它们使用了不同的存储机制(当然,大多数项目仍然会使用Entity Framework,因为它来自于微软)。
目前最重要的是这个IdentityUser类只提供了对用户基本信息的访问:用户名、E-mail、电话、哈希口令、角色成员等等。如果希望存储用户的各种附加信息,就需要在IdentityUser派生的类上添加属性,并将它用于表示应用程序中的用户,第15章中将演示其做法。
为了创建应用程序中的用户类,我在Models文件夹中创建了一个类文件,名称为AppUser.cs(注意,这个文件名称错了,应当是AppUser.cs),并用它创建了AppUser类,这个类如清单13-5所示。
listing 13-5. The Contents of the AppUser.cs File
清单13-5. AppUser.cs文件的内容
using System;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models {
public class AppUser : IdentityUser {
// additional properties will go here
// 这里将放置附加属性
}
}
以上便是此刻要做的全部工作,第15章会再次讨论这个类,那时会演示如何添加应用程序专用的用户数据属性。
2. 创建数据库上下文类
下一个步骤是创建Entity Framework数据库的上下文,用于对AppUser类进行操作。这可以用Code First特性来创建和管理数据架构,并对数据库所存储的数据进行访问。这个上下文类派生于IdentityDbContext<T> ,其中的T是用户类(即此例中的AppUser)。我在项目中创建了一个文件夹,名称为Infrastructure,并在其中添加了一个类文件,名称为AppIdentityDbContext.cs,其内容如清单13-6所示。
Listing 13-6. The Contents of the AppIdentityDbContext.cs File
清单13-6. 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,用于与数据库连接。这是让清单13-4定义的连接字符串与ASP.NET Identity联结起来的方法。
这个AppIdentityDbContext类还定义了一个静态的构造器,它使用Database.SetInitializer方法指定了一个种植数据库的类(种植数据库的含义是往数据库中植入数据的意思,说穿了,就是用一些数据对数据库进行初始化——译者注),当通过Entity Framework的Code First特性第一次创建数据库架构时,会用到这个类。这个种植类叫做IdentityDbInit,而且我已经提供了一个类,创建了一个占位符,后面可以回过头来在PerformInitialSetup方法中添加语句,就可以种植数据库了。第14章将演示如何种植数据库。
最后,AppIdentityDbContext类定义了一个Create方法。这是由OWIN在必要时创建类实例的办法,这个由OWIN使用的类将在“创建启动类”中进行描述。
注:如果对这些类的意义无法理解,不用担心。如果不熟悉Entity Framework,我建议你将它视为是某种黑箱事物。一旦基础构建块就绪——而且对本章的这些代码进行拷贝——那么就几乎不需要编辑它们了。
3. Creating the User Manager Class
3. 创建用户管理器类
最重要的Identity类之一是“User Manager(用户管理器)”,用来管理用户类实例。用户管理器类必须派生于UserManager<T> ,其中T是用户类。这个UserManager<T> 类并非是专用于Entity Framework的,而且它提供了很多通用特性,用以创建用户并对用户数据进行操作。表13-3列出了UserManager<T> 类为管理用户而定义的基本方法和操作。还有一些其他方法,这里并未全部列出来,我会在适当的情形下对它们进行描述,那时会介绍管理用户数据的不同方式。
| Name 名称 | Description 描述 |
|---|---|
| ChangePasswordAsync(id, old, new) | Changes the password for the specified user. 为指定用户修改口令 |
| CreateAsync(user) | Creates a new user without a password. See Chapter 15 for an example. 创建一个不带口令的新用户,参见第15章示例 |
| CreateAsync(user, pass) | Creates a new user with the specified password. See the “Creating Users” section. 创建一个带有指定口令的新用户,参见“创建用户” |
| DeleteAsync(user) | Deletes the specified user. See the “Implementing the Delete Feature” section. 删除指定用户,参见“实现Delete特性” |
| FindAsync(user, pass) | Finds the object that represents the user and authenticates their password. See Chapter 14 for details of authentication. 查找代表该用户的对象,并认证其口令,详见第14章的认证细节 |
| FindByIdAsync(id) | Finds the user object associated with the specified ID. See the “Implementing the Delete Feature” section. 查找与指定ID相关联的用户对象,参见“实现Delete特性” |
| FindByNameAsync(name) | Finds the user object associated with the specified name. I use this method in the “Seeding the Database” section of Chapter 14. 查找与指定名称相关联的用户对象,第14章“种植数据库”时会用到这个方法 |
| UpdateAsync(user) | Pushes changes to a user object back into the database. See the “Implementing the Edit Feature” section. 将用户对象的修改送入数据库,参见“实现Edit特性” |
| Users | Returns an enumeration of the users. See the “Enumerating User Accounts” section. 返回用户枚举,参见“枚举用户账号” |
提示:请注意方法名以Async结尾的那些方法。因为ASP.NET Identity几乎完全是用C#的异步编程特性实现的,这意味着会并发地执行各种操作,而不会阻塞其他活动。在我开始演示如何创建和管理用户数据时,你便会看到这种情况。对于每一个Async方法也有相应的同步扩展方法。对于大多数示例,我会坚持这种异步方法。但是,如果你需要按顺序执行一些相关的操作,同步方法是有用的。在第14章的“种植数据库”中就包含了一个这样的例子。当你希望从属性内部的getter或setter代码块中调用Identity方法时,同步方法也是有用的,在第15章中,我就是这么做的。
我在Infrastructure文件夹中添加了一个类文件,名称为AppUserManager.cs,用它定义了用户管理器类,名称为AppUserManager,如清单13-7所示。
Listing 13-7. The Contents of the AppUserManager.cs File
清单13-7. AppUserManager.cs文件的内容
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>(); ...
被作为参数传递给Create方法的IOwinContext实现定义了一个泛型的Get方法,它会返回已经在OWIN启动类中注册的对象实例,启动类在以下小节描述。
4. 创建启动类
为了使ASP.NET Identity就绪并能运行,要做的最后一个片段是Start Class(启动类)。在清单13-4中,我定义了一个应用程序设置,它为OWIN指定配置类,如下所示
... <add key="owin:AppStartup" value="Users.IdentityConfig" /> ...
OWIN是独立出现在ASP.NET中的,并且有它自己的约定。其中之一就是,为了加载和配置中间件,并执行所需的其他配置工作,需要有一个被实例化的类。默认情况下,这个类叫做Start,而且是在全局命名空间中定义的。这个类含有一个名称为Configuration的方法,该方法由OWIN基础架构进行调用,并为该方法传递一个Owin.IAppBuilder接口的实现,由它支持应用程序所需中间件的设置。Start类通常被定义成分部类,还有一些其他的类文件,它们分别专用于要用的每一种中间件。
是Identity。为了在应用程序的顶级命名空间定义一个类,我喜欢在Web.config文件中使用应用程序设置。于是我在App_Start文件夹中添加了一个类文件,名称为IdentityConfig.cs,并用它定义了如清单13-8所示的类,这是我在Web.config文件中指定的一个类。
Listing 13-8. The Contents of the IdentityConfig.cs File
清单13-8. IdentityConfig.cs文件的内容
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,将在第14章创建这个控制器来处理这些重定向。
13.3 使用ASP.NET Identity
现在,已经完成了基本设置,可以开始使用ASP.NET Identity在示例应用程序中添加对用户管理的支持了。在以下几小节中,我将演示如何将Identity API用于创建管理工具,这样能够集中化地管理用户。表13-4说明了ASP.NET Identity的情形。
| Question 问题 | Answer 回答 |
|---|---|
| What is it? 什么是ASP.NET Identity? |
ASP.NET Identity is the API used to manage user data and perform authentication and authorization. ASP.NET Identity是用来管理用户数据并执行认证和授权的API |
| Why should I care? 为何要关注它? |
Most applications require users to create accounts and provide credentials to access content and features. ASP.NET Identity provides the facilities for performing these operations. 大多数应用程序都需要用户创建账号,并提供凭据去访问内容和功能。ASP.NET Identity提供了执行这些操作的工具 |
| How is it used by the MVC framework? 如何在MVC框架中使用它? |
ASP.NET Identity isn't used directly by the MVC framework but integrates through the standard MVC authorization features. ASP.NET Identity不是由MVC框架直接使用的,但它集成了标准的MVC授权特性 |
13.3.1 Enumerating User Accounts
13.3.1 枚举用户账号
集中化的用户管理工具在几乎所有应用程序中都是有用的,即使是那些允许用户创建并管理自己账号的情况也是如此。例如,总会有这样一些客户,他们需要大量创建账号,并支持需要检查和调整用户数据的问题。根据本章的观点,管理工具是有用的,因为它们将许多基本的用户管理功能整合到了少量的几个类之中,这对于演示ASP.NET Identity的基本特性是很有用的。
首先,在项目中添加一个控制器,名称为Admin,如清单13-9所示,我将用它定义我的用户管理功能。
Listing 13-9. The Contents of the AdminController.cs File
清单13-9. AdminController.cs文件的内容
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> ,可以用来得到用户管理器类的实例。
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属性的值,以便能够在视图列出用户的细节。清单13-10显示了Views/Admin/Index.cshtml文件的内容,这是通过右击Index动作方法,并从弹出菜单选择“Add View(添加视图)”而创建的。
Listing 13-10. The Contents of the Index.cshtml File in the /Views/Admin Folder
清单13-10. /Views/Admin文件夹中Index.cshtml文件的内容
@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”消息——译者注),如图13-4所示。

浙公网安备 33010602011771号