【ASP.NET Identity系列教程(一)】ASP.NET Identity入門

注:本文是【ASP.NET Identity系列教程】的第一篇。本系列教程詳細、完整、深刻地介紹了微軟的ASP.NET Identity技術,描述瞭如何運用ASP.NET Identity實現應用程序的用戶管理,以及實現應用程序的認證與受權等相關技術,譯者但願本系列教程能成爲掌握ASP.NET Identity技術的一份完整而有價值的資料。讀者如果可以按照文章的描述,一邊閱讀、一邊實踐、一邊理解,定能有意想不到的巨大收穫!但願本系列博文可以獲得廣大園友的高度推薦css





13 Getting Started with Identity
13 Identity入門

Identity is a new API from Microsoft to manage users in ASP.NET applications. The mainstay for user management in recent years has been ASP.NET Membership, which has suffered from design choices that were reasonable when it was introduced in 2005 but that have aged badly. The biggest limitation is that the schema used to store the data worked only with SQL Server and was difficult to extend without re-implementing a lot of provider classes. The schema itself was overly complex, which made it harder to implement changes than it should have been.
Identity是微軟在ASP.NET應用程序中管理用戶的一個新的API。近年來用戶管理的基石一直是ASP.NET的Membership。Membership在2005年推出時還算是一個合理的選擇,但目前看來已經嚴重過期了。它最大的限制是用以存儲數據的架構(Database Schema)只能使用SQL Server,並且難以擴展,除非從新實現大量的提供器類。其數據架構自己也過於複雜,使之比理論上還要難以實現修改。html

Microsoft made a couple of attempts to improve Membership prior to releasing Identity. The first was known as simple membership, which reduced the complexity of the schema and made it easier to customize user data but still needed a relational storage model. The second attempt was the ASP.NET universal providers, which I used in Chapter 10 when I set up SQL Server storage for session data. The advantage of the universal providers is that they use the Entity Framework Code First feature to automatically create the database schema, which made it possible to create databases where access to schema management tools wasn't possible, such as the Azure cloud service. But even with the improvements, the fundamental issues of depending on relational data and difficult customizations remained.
在發佈Identity以前,微軟曾作過兩次改善Membership的嘗試。第一個嘗試稱爲Simple Membership(簡單成員),它下降了數據庫架構的複雜性,並使之易於定製用戶數據,但仍然須要關係型存儲模型。第二個嘗試是ASP.NET的Universal Providers(通用提供器),第10章在爲會話數據創建SQL Server存儲庫時曾用過它。Universal Providers的好處是,這些提供器使用了Entity Framework的Code First特性,可以自動地建立數據庫架構,使之可以在架構管理工具沒法訪問的狀況下,例如Azure雲服務,也可以建立數據庫。但即便有了改善,其依賴於關係型數據以及難以定製等根本問題仍然存在。git

To address both problems and to provide a more modern user management platform, Microsoft has replaced Membership with Identity. As you'll learn in this chapter and Chapters 14 and 15, ASP.NET Identity is flexible and extensible, but it is immature, and features that you might take for granted in a more mature system can require a surprising amount of work.
爲了解決這兩個問題並提供一個更現代的用戶管理平臺,微軟用Identity取代了Membership。正如將在本章以及第1四、15章所瞭解到的,ASP.NET Identity靈活且可擴展,但它仍不成熟,你在一些更成熟的系統中可以得到的特性,可能須要超常的工做量。web

Microsoft has over-compensated for the inflexibility of Membership and made Identity so open and so adaptable that it can be used in just about any way—just as long as you have the time and energy to implement what you require.
微軟已經徹底彌補了Membership的不靈活性,使Identity十分開放和普遍適應,幾乎可以以任何方式進行使用——只要你有時間有能力作出你所須要的實現便可。數據庫

In this chapter, I demonstrate the process of setting up ASP.NET Identity and creating a simple user administration tool that manages individual user accounts that are stored in a database.
在本章中,我會演示創建ASP.NET Identity的過程,並建立一個簡單的用戶管理工具,用以管理存儲在數據庫中的個別用戶帳號。編程

ASP.NET Identity supports other kinds of user accounts, such as those stored using Active Directory, but I don't describe them since they are not used that often outside corporations (where Active Directive implementations tend to be so convoluted that it would be difficult for me to provide useful general examples).
ASP.NET Identity還支持其餘類型的用戶帳號,例如用Active Directory(活動目錄)存儲的帳號,但我不會對其進行描述,由於這種帳號一般不會用於公司的外部(這種場合的Active Directory實現每每很複雜,我難以提供有用的通用示例)。bootstrap

In Chapter 14, I show you how to perform authentication and authorization using those user accounts, and in Chapter 15, I show you how to move beyond the basics and apply some advanced techniques. Table 13-1 summarizes this chapter.
在第14章中,我將演示如何用這些用戶帳號進行認證與受權,第15章將演示如何進入高級論題,運用一些高級技術。表13-1是本章概要。瀏覽器

Table 13-1. Chapter Summary
表13-1. 本章概要
Problem
問題
Solution
解決方案
Listing
清單號
Install ASP.NET Identity.
安裝ASP.NET Identity
Add the NuGet packages and define a connection string and an OWIN start class in the Web.config file.
添加NuGet包,並在Web.config文件中定義一個連接字符串和一個OWIN啓動類
1–4
Prepare to use ASP.NET Identity.
使用ASP.NET Identity的準備
Create classes that represent the user, the user manager, the database context, and the OWIN start class.
建立表示用戶、用戶管理器、數據庫上下文的類,以及OWIN類
5–8
Enumerate user accounts.
枚舉用戶帳號
Use the Users property defined by the user manager class.
使用由用戶管理器類定義的Users屬性
9, 10
Create user accounts.
建立用戶帳號
Use the CreateAsync method defined by the user manager class.
使用由用戶管理器類定義的CreateAsync方法
11–13
Enforce a password policy.
強制口令策略
Set the PasswordValidator property defined by the user manager class, either using the built-in PasswordValidator class or using a custom derivation.
設置由用戶管理器類定義的PasswordValidator屬性,既可使用內建的PasswordValidator類,也可使用自定義的派生類。
14–16
Validate new user accounts.
驗證新的用戶帳號
Set the UserValidator property defined by the user manager class, either using the built-in UserValidator class or using a custom derivation.
設置由用戶管理器類定義的UserValidator屬性,既可使用內建的UserValidator類,也可使用自定義的派生類。
17–19
Delete user accounts.
刪除用戶帳號
Use the DeleteAsync method defined by the user manager class.
使用由用戶管理器定義的DeleteAsync方法
20–22
Modify user accounts.
修改用戶帳號
Use the UpdateAsync method defined by the user manager class.
使用由用戶管理器類定義的UpdateAsync方法
23–24

13.1 Preparing the Example Project
13.1 準備示例項目

I created a project called Users for this chapter, following the same steps I have used throughout this book. I selected the Empty template and checked the option to add the folders and references required for an MVC application. I will be using Bootstrap to style the views in this chapter, so enter the following command into the Visual Studio Package Manager Console and press Enter to download and install the NuGet package:
本章根據本書一直採用的一樣步驟建立了一個名稱爲Users的項目。在建立過程當中選擇了「Empty(空)」 模板,並在「Add the folders and references(添加文件夾和引用)」中選中了「MVC」複選框。本章將使用Bootstrap來設置視圖的樣式,所以在Visual Studio的「Package Manager Console(包管理器控制檯)」中輸入如下命令,並按回車,下載並安裝這個NuGet包。緩存

Install-Package -version 3.0.3 bootstrap

I created a Home controller to act as the focal point for the examples in this chapter. The definition of the controller is shown in Listing 13-1. I'll be using this controller to describe details of user accounts and data, and the Index action method passes a dictionary of values to the default view via the View method.
我建立了Home控制器,以做爲本章示例的焦點。該控制器的定義如清單13-1所示。此控制器將用來描述用戶帳號的細節和數據,Index動做方法經過View方法給默認視圖傳遞了一個字典值。服務器

Listing 13-1. The Contents of the HomeController.cs File
清單13-1. HomeController.cs文件的內容

using System.Web.Mvc;
using System.Collections.Generic;
namespace Users.Controllers {
public class HomeController : Controller {
public ActionResult Index() { Dictionary<string, object> data = new Dictionary<string, object>(); data.Add("Placeholder", "Placeholder"); return View(data); } } }

I created a view by right-clicking the Index action method and selecting Add View from the pop-up menu. I set View Name to Index and set Template to Empty (without model). Unlike the examples in previous chapters, I want to use a common layout for this chapter, so I checked the Use a Layout Page option. When I clicked the Add button, Visual Studio created the Views/Shared/_Layout.cshtml and Views/Home/Index.cshtml files. Listing 13-2 shows the contents of the _Layout.cshtml file.
經過右擊Index動做方法,並從彈出菜單選擇「Add View(添加視圖)」,我建立了一個視圖。將「View Name(視圖名稱)」設置爲「Index」,並將「Template(模板)」設置爲「空(無模型)」。與前面幾章的示例不一樣,本章但願使用一個通用的佈局,因而選中了「Use a Layout Page(使用佈局頁面)」複選框。點擊「Add(添加)」按鈕後,Visual Studio建立了Views/Shared/_Layout.cshtmlViews/Home/Index.cshtml文件。清單13-2顯示了_Layout.cshtml文件的內容。

Listing 13-2. The Contents of the _Layout.cshtml File
清單13-2. _Layout.cshtml文件的內容

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</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: #f00; }
    </style>
</head>
<body class="container">
    <div class="container">
        @RenderBody()
    </div>
</body>
</html>

Listing 13-3 shows the contents of the Index.cshtml file.
清單13-3顯示了Index.cshtml文件的內容。

Listing 13-3. The Contents of the Index.cshtml File
清單13-3. Index.cshtml文件的內容

@{
    ViewBag.Title = "Index";
}
<div class="panel panel-primary"> <div class="panel-heading">User Details</div> <table class="table table-striped"> @foreach (string key in Model.Keys) { <tr> <th>@key</th> <td>@Model[key]</td> </tr> } </table> </div>

To test that the example application is working, select Start Debugging from the Visual Studio Debug menu and navigate to the /Home/Index URL. You should see the result illustrated by Figure 13-1.
爲了測試該應用程序示例可以工做,從Visual Studio的「Debug(調試)」菜單中選擇「Start Debugging(啓動調試)」,並導航到/Home/Index網址,即可以看到如圖13-1所示的結果。

圖13-1

Figure 13-1. Testing the example application
圖13-1. 測試示例應用程序

13.2 Setting Up ASP.NET Identity
13.2 創建ASP.NET Identity

For most ASP.NET developers, Identity will be the first exposure to the Open Web Interface for .NET (OWIN). OWIN is an abstraction layer that isolates a web application from the environment that hosts it. The idea is that the abstraction will allow for greater innovation in the ASP.NET technology stack, more flexibility in the environments that can host ASP.NET applications, and a lighter-weight server infrastructure.
對於大多數ASP.NET開發者而言,Identity將是第一個暴露給OWIN(Open Web Interface for .NET——.NET開放Web接口)的組件。OWIN是一個將Web應用程序從託管它的環境中獨立出來的抽象層。其思想是這個獨立出來的抽象層可以使ASP.NET技術堆棧有更大的創新,使託管ASP.NET應用程序的環境有更多的靈活性,並能夠是輕量級的服務器架構。

OWIN is an open standard (which you can read at http://owin.org/spec/owin-1.0.0.html). Microsoft has created Project Katana, its implementation of the OWIN standard and a set of components that provide the functionality that web applications require. The attraction to Microsoft is that OWIN/Katana isolates the ASP.NET technology stack from the rest of the .NET Framework, which allows a greater rate of change.
OWIN是一個開放標準(參閱http://owin.org/spec/owin-1.0.html)。微軟已經建立了Katana項目,該項目是OWIN標準的實現,並提供了一組組件,這些組件提供了Web應用程序所需的功能。讓微軟感興趣的是OWIN/Katana將ASP.NET技術堆棧從.NET框架的其他部分獨立了出來,這帶來了更大的修改速率(譯者對OWIN還沒太瞭解,這多是指若按照OWIN的方式開發應用程序,可使應用程序更易於修改——譯者注)。

OWIN developers select the services that they require for their application, rather than consuming an entire platform as happens now with ASP.NET. Individual services—known as middleware in the OWIN terminology—can be developed at different rates, and developers will be able to choose between providers for different services, rather than being tied to a Microsoft implementation.
OWIN開發人員爲他們的應用程序選擇所需的服務,而不是像如今這樣單純地使用ASP.NET平臺。個別服務——在OWIN術語中稱爲「Middleware(中間件)」——可能會有不一樣的開發速率,而開發人員將可以在不一樣的服務提供商之間進行選擇,而不是被綁定在微軟實現上。

There is a lot to like about the direction that OWIN and Katana are heading in, but it is in the early days, and it will be some time before it becomes a complete platform for ASP.NET applications. As I write this, it is possible to build Web API and SignalR applications without needing the System.Web namespace or IIS to process requests, but that's about all. The MVC framework requires the standard ASP.NET platform and will continue to do so for some time to come.
OWIN和Katana有不少喜歡發展的方向,但它仍處於早期時期,還須要一段時間才能成爲ASP.NET應用程序的完整平臺。在我編寫本書的時候,它已能夠在不須要System.Web命名空間或者IIS處理請求的狀況下,創建Web API和SignalR應用程序了。MVC框架須要標準的ASP.NET平臺,所以在一段時間內仍將沿用如今的作法。

The ASP.NET platform and IIS are not going away. Microsoft has been clear that it sees one of the most attractive aspects of OWIN as allowing developers more flexibility in which middleware components are hosted by IIS, and Project Katana already has support for the System.Web namespaces. OWIN and Katana are not the end of ASP.NET—rather, they represent an evolution where Microsoft allows developers more flexibility in how ASP.NET applications are assembled and executed.
ASP.NET平臺以及IIS不會消失。微軟已經清楚地看到,OWIN最有吸引力的一個方面是開發者具備了更大的靈活性,中間組件能夠由IIS來託管,而Katana項目已經實現了對System.Web命名空間的支持。OWIN和Katana不是ASP.NET的終結——而是預示着一種變革,微軟讓開發人員可以在ASP.NET應用程序的編譯和執行方面更加靈活。

Tip Identity is the first major ASP.NET component to be delivered as OWIN middleware, but it won't be the last. Microsoft has made sure that the latest versions of Web API and SignalR don't depend on the System.Web namespaces, and that means that any component intended for use across the ASP.NET family of technologies has to be delivered via OWIN. I get into more detail about OWIN in my Expert ASP.NET Web API 2 for MVC Developers book, which will be published by Apress in 2014.
提示:Identity是做爲OWIN中間件而交付的第一個主要的ASP.NET組件,但這不是最後一個。微軟已經保證,Web API和SignalR的最新版本不會依賴於System.Web命名空間,這意味着,打算交叉使用ASP.NET家族技術的任何組件,都必須經過OWIN實行交付。關於OWIN,在個人Expert ASP.NET Web API 2 for MVC Developers一書中有更詳細的論述,該書已由Apress於2014年出版。

OWIN and Katana won't have a major impact on MVC framework developers for some time, but changes are already taking effect—and one of these is that ASP.NET Identity is implemented as an OWIN middleware component. This isn't ideal because it means that MVC framework applications have to mix OWIN and traditional ASP.NET platform techniques to use Identity, but it isn't too burdensome once you understand the basics and know how to get OWIN set up, which I demonstrate in the sections that follow.
OWIN和Katana在一段時間內還不會對MVC框架開發人員發生重大沖擊,但變化正在發生——其中之一即是ASP.NET Identity要做爲OWIN中間件而實現。這種狀況不太理想,由於這意味着,MVC框架的應用程序爲了使用Identity,必須將OWIN和傳統的ASP.NET平臺混搭在一塊兒,這太煩瑣了,在你理解了基本概念而且知道如何創建OWIN(如下幾小節演示)以後,便會明白。

13.2.1 Creating the ASP.NET Identity Database
13.2.1 建立ASP.NET Identity數據庫

ASP.NET Identity isn't tied to a SQL Server schema in the same way that Membership was, but relational storage is still the default—and simplest—option, and it is the one that I will be using in this chapter. Although the NoSQL movement has gained momentum in recent years, relational databases are still the mainstream storage choice and are well-understood in most development teams.
ASP.NET Identity並未像Membership那樣,被綁定到SQL Server架構,但關係型存儲還是默認的,並且是最簡單的選擇,這也是本章中將使用的形式。雖然近年來出現了NoSQL運動勢頭,但關係型數據庫仍然是主要的存儲選擇,並且大多數開發團隊都有良好理解。

ASP.NET Identity uses the Entity Framework Code First feature to automatically create its schema, but I still need to create the database into which that schema—and the user data—will be placed, just as I did in Chapter 10 when I created the database for session state data (the universal provider that I used to manage the database uses the same Code First feature).
ASP.NET Identity使用Entity Framework的Code First特性自動地建立它的數據架構(Schema),但我仍然須要建立一個用來放置此數據架構以及用戶數據的數據庫,就像第10章所作的那樣,當時爲會話狀態數據建立了數據庫(用來管理數據庫的通用提供器一樣使用了Code First特性)。

Tip You don't need to understand how Entity Framework or the Code First feature works to use ASP.NET Identity.
提示:爲了使用ASP.NET Identity,不必定要理解Entity Framework或Code First特性的工做機制。

As in Chapter 10, I will be using the localdb feature to create my database. As a reminder, localdb is included in Visual Studio and is a cut-down version of SQL Server that allows developers to easily create and work with databases.
正如第10章那樣,我將使用localdb特性來建立數據庫。要記住的是,localdb是包含在Visual Studio之中的,並且是一個簡化版的SQL Server,可以讓開發者方便地建立和使用數據庫。

Select SQL Server Object Explorer from the Visual Studio View menu and right-click the SQL Server object in the window that appears. Select Add SQL Server from the pop-up menu, as shown in Figure 13-2.
從Visual Studio的「View(視圖)」菜單中選擇「SQL Server Object Explorer(SQL Server對象資源管理器)」,並在所出現的窗口中右擊「SQL Server」對象。從彈出菜單選擇「Add SQL Server(添加SQL Server)」,如圖13-2所示。

圖13-2

Figure 13-2. Creating a new database connection
圖13-2. 建立一個新的數據庫鏈接

Visual Studio will display the Connect to Server dialog. Set the server name to (localdb)\v11.0, select the Windows Authentication option, and click the Connect button. A connection to the database will be established and shown in the SQL Server Object Explorer window. Expand the new item, right-click Databases, and select Add New Database from the pop-up window, as shown in Figure 13-3.
Visual Studio將顯示「Connect to Server(鏈接到服務器)」對話框,將服務器名稱設置爲(localdb)\v11.0,選擇「Windows Authentication(Windows認證)」選項,點擊「Connect(鏈接)」按鈕。這將創建一個數據庫鏈接,並顯示在「SQL Server對象資源管理器」窗口中。展開這個新項,右擊「Databases(數據庫)」,並從彈出菜單選擇「Add New Database(添加新數據庫)」,如圖13-3所示。

圖13-3

Figure 13-3. Adding a new database
圖13-3. 添加新數據庫

Set the Database Name option to IdentityDb, leave the Database Location value unchanged, and click the OK button to create the database. The new database will be shown in the Databases section of the SQL connection in the SQL Server Object Explorer.
將「Database Name(數據庫名稱)」選項設置爲「IdentityDb」,不用改變「Database Location(數據庫位置)」的值,點擊OK按鈕建立此數據庫。這個新數據庫將出如今「SQL Server對象資源管理器」中「SQL鏈接」的「數據庫」小節中。

13.2.2 Adding the Identity Packages
13.2.2 添加Identity包

Identity is published as a set of NuGet packages, which makes it easy to install them into any project. Enter the following commands into the Package Manager Console:
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)

Visual Studio can create projects that are configured with a generic user account management configuration, using the Identity API. You can add the templates and code to a project by selecting the MVC template when creating the project and setting the Authentication option to Individual User Accounts. I don't use the templates because I find them too general and too verbose and because I like to have direct control over the contents and configuration of my projects. I recommend you do the same, not least because you will gain a better understanding of how important features work, but it can be interesting to look at the templates to see how common tasks are performed.
Visual Studio可以建立一些使用Identity API的項目,這種項目以「泛型用戶帳號管理配置」進行配置。你能夠給項目添加一些模板和代碼,只需在建立項目時選擇MVC模板,並將認證選項設置爲Individual User Accounts(個別用戶帳號)。我沒有使用這種模板,由於我發現它們太普通,也太混亂,並且我喜歡對我項目中的內容和配置有直接的控制。我建議你也這麼作,這不只讓你可以對重要特性的工做機制得到更好的理解,並且,考察模板如何執行常規任務也是有趣的。

13.2.3 Updating the Web.config File
13.2.3 更新Web.config文件

Two changes are required to the Web.config file to prepare a project for ASP.NET Identity. The first is a connection string that describes the database I created in the previous section. The second change is to define an application setting that names the class that initializes OWIN middleware and that is used to configure Identity. Listing 13-4 shows the changes I made to the Web.config file. (I explained how connection strings and application settings work in Chapter 9.)
爲了作好項目使用ASP.NET Identity的準備,須要在Web.config文件中作兩處修改。第一處是鏈接字符串,它描述了我在上一小節中建立的數據庫。第二處修改是定義一個應用程序設置,它命名對OWIN中間件進行初始化的類,並將它用於配置Identity。清單顯示了對Web.config文件的修改(第9章已經解釋過鏈接字符串和應用程序設置)。

Listing 13-4. Preparing the Web.config File for ASP.NET Identity
清單13-4. 爲ASP.NET Identity準備Web.config文件

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <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="Users.IdentityConfig" /> </appSettings> <system.web> <compilation debug="true" targetFramework="4.5.1" /> <httpRuntime targetFramework="4.5.1" /> </system.web> <entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework> </configuration>

Caution Make sure you put the connectionString value on a single line. I had to break it over several lines to make the listing fit on the page, but ASP.NET expects a single, unbroken string. If in doubt, download the source code that accompanies this book, which is freely available from www.apress.com.
警告:要確保將connectionString的值放在一行中,這裏把它給斷開了,是爲了讓清單適應頁面,但ASP.NET要求是單一無斷行的字符串。若是有疑問,請下載本書的伴隨代碼,下載地址:www.apress.com

OWIN defines its own application startup model, which is separate from the global application class that I described in Chapter 3. The application setting, called owin:AppStartup, specifies a class that OWIN will instantiate when the application starts in order to receive its configuration.
OWIN定義了它本身的應用程序啓動模型,與第3章所描述的全局應用程序類是分開的。上述的應用程序設置,名稱爲owin:AppStartup,指定了應用程序啓動時,將由OWIN進行實例化的類,目的是接受它的配置。

Tip Notice that I have set the MultipleActiveResultSets property to true in the connection string. This allows the results from multiple queries to be read simultaneously, which I rely on in Chapter 14 when I show you how to authorize access to action methods based on role membership.
提示:注意,我已經在鏈接字符串中將MultipleActiveResultSets屬性(多活動結果集)設置爲true,這樣能夠經過同時讀取的多個查詢來造成結果,第14章便依賴於這種方式,那時會演示如何根據角色成員來受權訪問動做方法。

13.2.4 Creating the Entity Framework Classes
13.2.4 建立Entity Framework類

If you have used Membership in projects, you may be surprised by just how much initial preparation is required for ASP.NET Identity. The extensibility that Membership lacked is readily available in ASP.NET Identity, but it comes with a price of having to create a set of implementation classes that the Entity Framework uses to manage the database. In the sections that follow, I’ll show you how to create the classes needed to get Entity Framework to act as the storage system for ASP.NET Identity.
若是你曾在項目使用過Membership,可能會感受奇怪,ASP.NET Identity須要的初始化準備怎麼這麼多。在ASP.NET Identity中能夠輕易地使用Membership所缺少的可擴展性,但其代價是須要建立一組實現類,由Entity Framework用於管理數據庫。在如下小節中,我將演示如何建立所須要的這些類,以便讓Entity Framework將它們用於ASP.NET Identity的存儲系統。

1. Creating the User Class
1. 建立用戶類

The first class to define is the one that represents a user, which I will refer to as the user class. The user class is derived from IdentityUser, which is defined in the Microsoft.AspNet.Identity.EntityFramework namespace. IdentityUser provides the basic user representation, which can be extended by adding properties to the derived class, which I describe in Chapter 15. Table 13-2 shows the built-in properties that IdentityUser defines, which are the ones I will be using in this chapter.
第一個要定義的類是表現一個用戶的類,我將它稱爲「User Class(用戶類)」。這個用戶類派生於IdentityUser,它是在Microsoft.AspNet.Identity.EntityFramework命名空間中定義的。IdentityUser提供了基本的用戶表示,能夠經過在它派生的類中添加屬性的辦法,對這個類進行擴展,我會在第15章中對此進行描述。表13-2列出了IdentityUser所定義的內建屬性(如今流行將「內建(Built-in)」說成「內置」,但我不會人云亦云,這二者在含義上是有區別的,「內建」徹底是本身建立的,而「內置」有多是別人作的東西拿來放入其中的——譯者注),本章將使用這些屬性。

Table 13-2. The Properties Defined by the IdentityUser Class
表13-2. IdentityUser類定義的屬性
Name
名稱
Description
描述
Claims Returns the collection of claims for the user, which I describe in Chapter 15
返回用戶的聲明集合,關於聲明(Claims)將在第15章描述
Email 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
返回用戶名

Tip The classes in the Microsoft.AspNet.Identity.EntityFramework namespace are the Entity Framework–specific concrete implementations of interfaces defined in the Microsoft.AspNet.Identity namespace. IdentityUser, for example, is the implementation of the IUser interface. I am working with the concrete classes because I am relying on the Entity Framework to store my user data in a database, but as ASP.NET Identity matures, you can expect to see alternative implementations of the interfaces that use different storage mechanisms (although most projects will still use the Entity Framework since it comes from Microsoft).
提示:Microsoft.AspNet.Identity.EntityFramework命名空間中的類是, Microsoft.AspNet.Identity命名空間中所定義接口的Entity Framework專用的具體實現。例如,IdentityUser即是IUser接口的實現。我會使用這些具體類,由於我要依靠Entity Framework在數據庫中存儲個人用戶數據,等到ASP.NET Identity變得成熟時,你可能會指望看到這些接口的其餘實現,它們使用了不一樣的存儲機制(固然,大多數項目仍然會使用Entity Framework,由於它來自於微軟)。

What is important at the moment is that the IdentityUser class provides access only to the basic information about a user: the use's name, e-mail, phone, password hash, role memberships, and so on. If I want to store any additional information about the user, I have to add properties to the class that I derive from IdentityUser and that will be used to represent users in my application. I demonstrate how to do this in Chapter 15.
目前最重要的是這個IdentityUser類只提供了對用戶基本信息的訪問:用戶名、E-mail、電話、哈希口令、角色成員等等。若是但願存儲用戶的各類附加信息,就須要在IdentityUser派生的類上添加屬性,並將它用於表示應用程序中的用戶,第15章中將演示其作法。

To create the user class for my application, I created a class file called AppUserModels.cs in the Models folder and used it to create the AppUser class, which is shown in Listing 13-5.
爲了建立應用程序中的用戶類,我在Models文件夾中建立了一個類文件,名稱爲AppUserModels.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 // 這裏將放置附加屬性 } }

That's all I have to do at the moment, although I'll return to this class in Chapter 15, when I show you how to add application-specific user data properties.
以上即是此刻要作的所有工做,第15章會再次討論這個類,那時會演示如何添加應用程序專用的用戶數據屬性。

2. Creating the Database Context Class
2. 建立數據庫上下文類

The next step is to create an Entity Framework database context that operates on the AppUser class. This will allow the Code First feature to create and manage the schema for the database and provide access to the data it stores. The context class is derived from IdentityDbContext<T>, where T is the user class (AppUser in the example). I created a folder called Infrastructure in the project and added to it a class file called AppIdentityDbContext.cs, the contents of which are shown in Listing 13-6.
下一個步驟是建立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 // 初始化配置將放在這兒 } } }

The constructor for the AppIdentityDbContext class calls its base with the name of the connection string that will be used to connect to the database, which is IdentityDb in this example. This is how I associate the connection string I defined in Listing 13-4 with ASP.NET Identity.
這個AppIdentityDbContext類的構造器調用了它的基類,其參數是鏈接字符串的名稱,IdentityDb,用於與數據庫鏈接。這是讓清單13-4定義的鏈接字符串與ASP.NET Identity聯結起來的方法。

The AppIdentityDbContext class also defines a static constructor, which uses the Database.SetInitializer method to specify a class that will seed the database when the schema is first created through the Entity Framework Code First feature. My seed class is called IdentityDbInit, and I have provided just enough of a class to create a placeholder so that I can return to seeding the database later by adding statements to the PerformInitialSetup method. I show you how to seed the database in Chapter 14.
這個AppIdentityDbContext類還定義了一個靜態的構造器,它使用Database.SetInitializer方法指定了一個種植數據庫的類(種植數據庫的含義是往數據庫中植入數據的意思,說穿了,就是用一些數據對數據庫進行初始化——譯者注),當經過Entity Framework的Code First特性第一次建立數據庫架構時,會用到這個類。這個種植類叫作IdentityDbInit,並且我已經提供了一個類,建立了一個佔位符,後面能夠回過頭來在PerformInitialSetup方法中添加語句,就能夠種植數據庫了。第14章將演示如何種植數據庫。

Finally, the AppIdentityDbContext class defines a Create method. This is how instances of the class will be created when needed by the OWIN, using the class I describe in the 「Creating the Start Class」 section.
最後,AppIdentityDbContext類定義了一個Create方法。這是由OWIN在必要時建立類實例的辦法,這個由OWIN使用的類將在「建立啓動類」中進行描述。

Note Don't worry if the role of these classes doesn't make sense. If you are unfamiliar with the Entity Framework, then I suggest you treat it as something of a black box. Once the basic building blocks are in place—and you can copy the ones into your chapter to get things working—then you will rarely need to edit them.
注:若是對這些類的意義沒法理解,不用擔憂。若是不熟悉Entity Framework,我建議你將它視爲是某種黑箱事物。一旦基礎構建塊就緒——並且對本章的這些代碼進行拷貝——那麼就幾乎不須要編輯它們了。

3. Creating the User Manager Class
3. 建立用戶管理器類

One of the most important Identity classes is the user manager, which manages instances of the user class. The user manager class must be derived from UserManager<T> , where T is the user class. The UserManager<T> class isn't specific to the Entity Framework and provides more general features for creating and operating on user data. Table 13-3 shows the basic methods and properties defined by the UserManager<T> class for managing users. There are others, but rather than list them all here, I'll describe them in context when I describe the different ways in which user data can be managed.
最重要的Identity類之一是「User Manager(用戶管理器)」,用來管理用戶類實例。用戶管理器類必須派生於UserManager<T> ,其中T是用戶類。這個UserManager<T> 類並不是是專用於Entity Framework的,並且它提供了不少通用特性,用以建立用戶並對用戶數據進行操做。表13-3列出了UserManager<T> 類爲管理用戶而定義的基本方法和操做。還有一些其餘方法,這裏並未所有列出來,我會在適當的情形下對它們進行描述,那時會介紹管理用戶數據的不一樣方式。

Table 13-3. The Basic Methods and Properties Defined by the UserManager<T> Class
表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.
返回用戶枚舉,參見「枚舉用戶帳號」

Tip Notice that the names of all of these methods end with Async. This is because ASP.NET Identity is implemented almost entirely using C# asynchronous programming features, which means that operations will be performed concurrently and not block other activities. You will see how this works once I start demonstrating how to create and manage user data. There are also synchronous extension methods for each Async method. I stick to the asynchronous methods for most examples, but the synchronous equivalents are useful if you need to perform multiple related operations in sequence. I have included an example of this in the 「Seeding the Database」 section of Chapter 14. The synchronous methods are also useful when you want to call Identity methods from within property getters or setters, which I do in Chapter 15.
提示:請注意方法名以Async結尾的那些方法。由於ASP.NET Identity幾乎徹底是用C#的異步編程特性實現的,這意味着會併發地執行各類操做,而不會阻塞其餘活動。在我開始演示如何建立和管理用戶數據時,你便會看到這種狀況。對於每個Async方法也有相應的同步擴展方法。對於大多數示例,我會堅持這種異步方法。可是,若是你須要按順序執行一些相關的操做,同步方法是有用的。在第14章的「種植數據庫」中就包含了一個這樣的例子。當你但願從屬性內部的getter或setter代碼塊中調用Identity方法時,同步方法也是有用的,在第15章中,我就是這麼作的。

I added a class file called AppUserManager.cs to the Infrastructure folder and used it to define the user manager class, which I have called AppUserManager, as shown in Listing 13-7.
我在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; } } }

The static Create method will be called when Identity needs an instance of the AppUserManager, which will happen when I perform operations on user data—something that I will demonstrate once I have finished performing the setup.
在Identity須要一個AppUserManager的實例時,將會調用靜態的Create方法,這種狀況將在對用戶數據執行操做時發生——也是在完成設置以後要演示的事情。

To create an instance of the AppUserManager class, I need an instance of UserStore<AppUser> . The UserStore<T> class is the Entity Framework implementation of the IUserStore<T> interface, which provides the storage-specific implementation of the methods defined by the UserManager class. To create the UserStore<AppUser> , I need an instance of the AppIdentityDbContext class, which I get through OWIN as follows:
爲了建立AppUserManager類的實例,我須要一個UserStore<AppUser> 實例。這個UserStore<T> 類是IUserStore<T> 接口的Entity Framework實現,它提供了UserManager類所定義的存儲方法的實現。爲了建立UserStore<AppUser> ,又須要AppIdentityDbContext類的一個實例,這是經過OWIN按以下辦法獲得的:

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

The IOwinContext implementation passed as an argument to the Create method defines a generically typed Get method that returns instances of objects that have been registered in the OWIN start class, which I describe in the following section.
被做爲參數傳遞給Create方法的IOwinContext實現定義了一個泛型的Get方法,它會返回已經在OWIN啓動類中註冊的對象實例,啓動類在如下小節描述。

4. Creating the Start Class
4. 建立啓動類

The final piece I need to get ASP.NET Identity up and running is a start class. In Listing 13-4, I defined an application setting that specified a configuration class for OWIN, like this:
爲了使ASP.NET Identity就緒並能運行,要作的最後一個片斷是Start Class(啓動類)。在清單13-4中,我定義了一個應用程序設置,它爲OWIN指定配置類,以下所示:

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

OWIN emerged independently of ASP.NET and has its own conventions. One of them is that there is a class that is instantiated to load and configure middleware and perform any other configuration work that is required. By default, this class is called Start, and it is defined in the global namespace. This class contains a method called Configuration, which is called by the OWIN infrastructure and passed an implementation of the Owin.IAppBuilder interface, which supports setting up the middleware that an application requires. The Start class is usually defined as a partial class, with its other class files dedicated to each kind of middleware that is being used.
OWIN是獨立出如今ASP.NET中的,而且有它本身的約定。其中之一就是,爲了加載和配置中間件,並執行所需的其餘配置工做,須要有一個被實例化的類。默認狀況下,這個類叫作Start,並且是在全局命名空間中定義的。這個類含有一個名稱爲Configuration的方法,該方法由OWIN基礎架構進行調用,併爲該方法傳遞一個Owin.IAppBuilder接口的實現,由它支持應用程序所需中間件的設置。Start類一般被定義成分部類,還有一些其餘的類文件,它們分別專用於要用的每一種中間件。

I freely ignore this convention, given that the only OWIN middleware that I use in MVC framework applications is Identity. I prefer to use the application setting in the Web.config file to define a single class in the top-level namespace of the application. To this end, I added a class file called IdentityConfig.cs to the App_Start folder and used it to define the class shown in Listing 13-8, which is the class that I specified in the Web.config folder.
我隨意地忽略了這一約定,由於我在MVC框架應用程序中使用的惟一OWIN中間件就是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"), }); } } }

The IAppBuilder interface is supplemented by a number of extension methods defined in classes in the Owin namespace. The CreatePerOwinContext method creates a new instance of the AppUserManager and AppIdentityDbContext classes for each request. This ensures that each request has clean access to the ASP.NET Identity data and that I don't have to worry about synchronization or poorly cached database data.
IAppBuilder接口是由一些擴展方法提供的,這些擴展方法的定義在Owin命名空間的一些類中。CreatePerOwinContext用於建立AppUserManager的新實例,AppIdentityDbContext類用於每個請求。這樣保證每個請求對ASP.NET Identity數據有清晰的訪問,我沒必要爲同步時的狀況或匱乏的數據緩存而操心。

The UseCookieAuthentication method tells ASP.NET Identity how to use a cookie to identity authenticated users, where the options are specified through the CookieAuthenticationOptions class. The important part here is the LoginPath property, which specifies a URL that clients should be redirected to when they request content without authentication. I have specified /Account/Login, and I will create the controller that handles these redirections in Chapter 14.
UseCookieAuthentication方法告訴ASP.NET Identity如何用Cookie去標識已認證的用戶,以及經過CookieAuthenticationOptions類指定的選項在哪兒。這裏重要的部分是LoginPath屬性,它指定了一個URL,這是未認證客戶端請求內容時要重定向的地址。我在其中指定了/Account/Login,將在第14章建立這個控制器來處理這些重定向。

13.3 Using ASP.NET Identity
13.3 使用ASP.NET Identity

Now that the basic setup is out of the way, I can start to use ASP.NET Identity to add support for managing users to the example application. In the sections that follow, I will demonstrate how the Identity API can be used to create administration tools that allow for centralized management of users. Table 13-4 puts ASP.NET Identity into context.
如今,已經完成了基本設置,能夠開始使用ASP.NET Identity在示例應用程序中添加對用戶管理的支持了。在如下幾小節中,我將演示如何將Identity API用於建立管理工具,這樣可以集中化地管理用戶。表13-4說明了ASP.NET Identity的情形。

Table 13-4. Putting Content Caching by Attribute in Context
表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 枚舉用戶帳號

Centralized user administration tools are useful in just about all applications, even those that allow users to create and manage their own accounts. There will always be some customers who require bulk account creation, for example, and support issues that require inspection and adjustment of user data. From the perspective of this chapter, administration tools are useful because they consolidate a lot of basic user management functions into a small number of classes, making them useful examples to demonstrate the fundamental features of ASP.NET Identity.
集中化的用戶管理工具在幾乎全部應用程序中都是有用的,即便是那些容許用戶建立並管理本身帳號的狀況也是如此。例如,總會有這樣一些客戶,他們須要大量建立帳號,並支持須要檢查和調整用戶數據的問題。根據本章的觀點,管理工具是有用的,由於它們將許多基本的用戶管理功能整合到了少許的幾個類之中,這對於演示ASP.NET Identity的基本特性是頗有用的。

I started by adding a controller called Admin to the project, which is shown in Listing 13-9, and which I will use to define my user administration functionality.
首先,在項目中添加一個控制器,名稱爲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>(); } } } }

The Index action method enumerates the users managed by the Identity system; of course, there aren't any users at the moment, but there will be soon. The important part of this listing is the way that I obtain an instance of the AppUserManager class, through which I manage user information. I will be using the AppUserManager class repeatedly as I implement the different administration functions, and I defined the UserManager property in the Admin controller to simplify my code.
動做方法枚舉由Identity系統管理的用戶,固然,此刻尚未任何用戶,但立刻就會有了。該清單重要的部分是我得到類實例的方式,經過它,我能夠管理用戶信息。在我實現不一樣的管理功能時,我會反覆使用AppUserManager類,並且,我在Admin控制器中定義了UserManager屬性,以便簡化代碼。

The Microsoft.Owin.Host.SystemWeb assembly adds extension methods for the HttpContext class, one of which is GetOwinContext. This provides a per-request context object into the OWIN API through an IOwinContext object. The IOwinContext isn't that interesting in an MVC framework application, but there is another extension method called GetUserManager<T> that is used to get instances of the user manager class.
Microsoft.Owin.Host.SystemWeb程序集爲HttpContext類添加了一些擴展方法,其中之一即是GetOwinContext。它經過一個IOwinContext對象,將基於每請求的上下文對象提供給WOIN API。MVC框架應用程序對IOwinContext不感興趣,可是有另一個擴展方法,叫作GetUserManager<T> ,能夠用來獲得用戶管理器類的實例。

Tip As you may have gathered, there are lots of extension methods in ASP.NET Identity; overall, the API is something of a muddle as it tries to mix OWIN, abstract Identity functionality, and the concrete Entity Framework storage implementation.
提示:正如你所推斷的同樣,ASP.NET Identity中有許多擴展方法,整體而言,這個API是一種混合體,它試圖將WOIN、抽象Identity功能以及具體的Entity Framework存儲實現混合在一塊兒。

I called the GetUserManager with a generic type parameter to specify the AppUserManager class that I created earlier in the chapter, like this:
我用一個泛型參數調用了GetUserManager,用以指定本章前面建立的AppUserManager類,以下所示:

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

Once I have an instance of the AppUserManager class, I can start to query the data store. The AppUserManager.Users property returns an enumeration of user objects—instances of the AppUser class in my application—which can be queried and manipulated using LINQ.
一旦有了AppUserManager類的實例,即可以開始查詢數據存儲了。AppUserManager.Users屬性返回了一個用戶對象的枚舉——應用程序中AppUser類的實例——因而能夠用LINQ對這個枚舉進行查詢和維護。

In the Index action method, I pass the value of the Users property to the View method so that I can list details of the users in the view. Listing 13-10 shows the contents of the Views/Admin/Index.cshtml file that I created by right-clicking the Index action method and selecting Add View from the pop-up menu.
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" })

This view contains a table that has rows for each user, with columns for the unique ID, username, and e-mail address. If there are no users in the database, then a message is displayed, as shown in Figure 13-4.
這個視圖含有一個表格,每一個用戶爲一行,帶有惟一ID、用戶名以及E-mail地址的表格列。若是數據庫中沒有用戶,那麼會顯示一條消息(圖中的「No User Accounts」消息——譯者注),如圖13-4所示。

圖13-4

Figure 13-4. Display the (empty) list of users
圖13-4. 顯示用戶列表(空)

I included a Create link in the view (which I styled as a button using Bootstrap) that targets the Create action on the Admin controller. I'll implement this action shortly to support adding users.
在視圖中有一個Create的連接(其樣式用Bootstrap造成了一個按鈕),它的目標是Admin控制器中的Create動做。爲了支持添加用戶,我會很快實現這個動做。

RESETTING THE DATABASE
重置數據庫

When you start the application and navigate to the /Admin/Index URL, it will take a few moments before the contents rendered from the view are displayed. This is because the Entity Framework has connected to the database and determined that there is no schema defined. The Code First feature uses the classes I defined earlier in the chapter (and some which are contained in the Identity assemblies) to create the schema so that it is ready to be queried and to store data.
當啓動應用程序並導航到/Admin/Index URL時,在視圖渲染的內容被顯示出來以前,會花一些時間。這是由於Entity Framework已經連接到數據庫,並發現此時還沒有定義數據庫架構。Code First特性使用本章前面定義的類(以及包含在Identity程序集中的類)建立該架構,以便作好查詢和存儲數據的準備。

You can see the effect by opening the Visual Studio SQL Server Object Explorer window and expanding entry for the IdentityDB database schema, which will include tables with names such as AspNetUsers and AspNetRoles.
經過打開Visual Studio的「SQL Server Object Explorer(SQL Server對象資源管理器)」窗口,並展開IdentityDB數據庫架構,即可以看到其中包含了一些數據表,如AspNetUsersAspNetRoles等等 。

To delete the database, right-click the IdentityDb item and select Delete from the pop-up menu. Check both of the options in the Delete Database dialog and click the OK button to delete the database.
要刪除該數據庫,右擊IdentityDb條目,並從彈出菜單選擇「Delete(刪除)」。選中「Delete Database(刪除數據庫)」對話框中的複選框,並點擊OK,以刪除該數據庫。

Right-click the Databases item, select Add New Database (as shown in Figure 13-3), and enter IdentityDb in the Database Name field. Click OK to create the empty database. The next time you start the application and navigate to the Admin/Index URL, the Entity Framework will detect that there is no schema and re-create it.
右擊「Databases(數據庫)」條目,選擇「Add New Database(添加新數據庫)」(如圖13-3所示),並在「Database Name(數據庫名)」字段中輸入IdentityDb,點擊OK,即可以建立空數據庫。下一次啓動應用程序,並導航到Admin/Index URL時,Entity Framework又將偵測到沒有數據庫架構,又會從新建立此數據庫。

13.3.2 Creating Users
13.3.2 建立用戶

I am going to use MVC framework model validation for the input my application receives, and the easiest way to do this is to create simple view models for each of the operations that my controller supports. I added a class file called UserViewModels.cs to the Models folder and used it to define the class shown in Listing 13-11. I'll add further classes to this file as I define models for additional features.
對於應用程序接收的輸入,我打算使用MVC架構的模型驗證,最容易的作法是爲控制器所支持的每一種操做建立一個簡單的視圖模型。我在Models文件夾中添加了一個類文件,名稱爲serViewModels.cs,並用它定義瞭如清單13-11所示的類。隨着我爲其餘特性定義模型,會進一步地在這個文件中添加一些類。

Listing 13-11. The Contents of the UserViewModels.cs File
清單13-11. UserViewModels.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; } } }

The initial model I have defined is called CreateModel, and it defines the basic properties that I require to create a user account: a username, an e-mail address, and a password. I have used the Required attribute from the System.ComponentModel.DataAnnotations namespace to denote that values are required for all three properties defined in the model.
我所定義的第一個模型叫作CreateModel,它定義了建立用戶帳號所須要的基本屬性:用戶名、E-mail地址以及口令。這個模型中所定義的全部屬性都使用了System.ComponentModel.DataAnnotations命名空間中的Required註釋屬性,用以說明這些值是必須的。

I added a pair of Create action methods to the Admin controller, which are targeted by the link in the Index view from the previous section and which uses the standard controller pattern to present a view to the user for a GET request and process form data for a POST request. You can see the new action methods in Listing 13-12.
我在Admin控制器中添加了一對Create動做方法,它們是上一小節Index視圖中的那個連接所指向的目標,並且,對於GET請求以及處理表單數據的POST請求,使用的是標準的控制器模式將視圖表現給用戶。能夠從清單13-12中看到這兩個新的動做方法。

Listing 13-12. Defining the Create Action Methods in the AdminController.cs File
清單13-12. 在AdminController.cs文件中定義Create動做方法

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>(); } } } }

The important part of this listing is the Create method that takes a CreateModel argument and that will be invoked when the administrator submits their form data. I use the ModelState.IsValid property to check that the data I am receiving contains the values I require, and if it does, I create a new instance of the AppUser class and pass it to the UserManager.CreateAsync method, like this:
該清單重要的部分是以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); 
...

The result from the CreateAsync method is an implementation of the IdentityResult interface, which describes the outcome of the operation through the properties listed in Table 13-5.
CreateAsync方法的結果是一個IdentityResult接口的實現,它經過表13-5中的屬性描述操做的輸出。

Table 13-5. The Properties Defined by the IdentityResult Interface
表13-5. IdentityResult接口所定義的屬性
Name
名稱
Description
描述
Errors Returns a string enumeration that lists the errors encountered while attempting the operation
返回一個字符串枚舉,其中列出了嘗試操做期間所遇到的錯誤
Succeeded Returns true if the operation succeeded
在操做成功時返回true

USING THE ASP.NET IDENTITY ASYNCHRONOUS METHODS
使用ASP.NET Identity異步方法

You will notice that all of the operations for manipulating user data, such as the UserManager.CreateAsync method I used in Listing 13-12, are available as asynchronous methods. Such methods are easily consumed with the async and await keywords. Using asynchronous Identity methods allows your action methods to be executed asynchronously, which can improve the overall throughput of your application.
你會注意到,維護用戶數據的全部操做,如清單13-12中所用到的UserManager.CreateAsync方法,均可以做爲異步方法來使用。這種方法很容易與asyncawait關鍵字一塊兒使用。使用異步的Identity方法讓你的動做方法可以異步執行,這能夠從總體上改善應用程序。

However, you can also use synchronous extension methods provided by the Identity API. All of the commonly used asynchronous methods have a synchronous wrapper so that the functionality of the UserManager.CreateAsync method can be called through the synchronous UserManager.Create method. I use the asynchronous methods for preference, and I recommend you follow the same approach in your projects. The synchronous methods can be useful for creating simpler code when you need to perform multiple dependent operations, so I used them in the 「Seeding the Database」 section of Chapter 14 as a demonstration.
然而,你也可使用那些由Identity API提供的同步擴展方法。全部經常使用的異步方法都有一個同步的封裝程序,所以UserManager.CreateAsync方法的功能能夠經過同步的UserManager.Create方法進行調用。我更喜歡使用異步方法,並且我建議在你的項目中遵循一樣的方式。當須要執行多個有依賴的操做時,爲了造成簡單的代碼,同步方法多是有用的,所以,做爲一個演示,我在第14章的「種植數據庫」小節中使用了它們。

I inspect the Succeeded property in the Create action method to determine whether I have been able to create a new user record in the database. If the Succeeded property is true, then I redirect the browser to the Index action so that list of users is displayed:
Create動做方法中,我檢測了Succeeded屬性,以肯定是否可以在數據庫建立一條新的用戶記錄。若是Succeeded屬性爲true,那麼便將瀏覽器重定向到Index動做,以便顯示用戶列表:

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

If the Succeeded property is false, then I call the AddErrorsFromResult method, which enumerates the messages from the Errors property and adds them to the set of model state errors, taking advantage of the MVC framework model validation feature. I defined the AddErrorsFromResult method because I will have to process errors from other operations as I build the functionality of my administration controller. The last step is to create the view that will allow the administrator to create new accounts. Listing 13-13 shows the contents of the Views/Admin/Create.cshtml file.
若是Succeeded屬性爲false,那麼便調用AddErrorsFromResult方法,該方法枚舉了Errors屬性中的消息,並將它們添加到模型狀態的錯誤消息集合中,此過程利用了MVC框架的模型驗證特性。我定義AddErrorsFromResult方法,是由於隨着進一步地構建這個管理控制器的功能,還必須處理來自其餘操做的錯誤消息。最後一步是建立視圖,讓管理員建立新帳號。清單13-13顯示了文件的Views/Admin/Create.cshtml內容。

Listing 13-13. The Contents of the Create.cshtml File
清單13-13. Create.cshtml文件的內容

@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"}) }

There is nothing special about this view—it is a simple form that gathers values that the MVC framework will bind to the properties of the model class that is passed to the Create action method.
該視圖沒有什麼特別的地方——只是一個簡單的表單,用來收集一些值,MVC框架會將它們綁定到模型類的屬性上,而後傳遞給Create動做方法。

1. Testing the Create Functionality
1. 測試Create功能

To test the ability to create a new user account, start the application, navigate to the /Admin/Index URL, and click the Create button. Fill in the form with the values shown in Table 13-6.
爲了測試建立新用戶帳號的能力,啓動應用程序,導航到/Admin/Index網址,並點擊Create按鈕。用一些值填充表單,如表13-6所示。

Table 13-6. The Values for Creating an Example User
表13-6. 建立示例用戶的值
Name
名稱
Value
Name
用戶名
Joe
Email
E-mail地址
joe@example.com
Password
口令
Secret

Tip Although not widely known by developers, there are domains that are reserved for testing, including example.com. You can see a complete list at https://tools.ietf.org/html/rfc2606.
提示:雖然不是不少開發人員都知道,有一些域名是保留用於測試的,包括example.com。能夠在https://tools.ietf.org/html/rfc2606網站看到完整的列表。

Once you have entered the values, click the Create button. ASP.NET Identity will create the user account, which will be displayed when your browser is redirected to the Index action method, as shown in Figure 13-5. You will see a different ID value because IDs are randomly generated for each user account.
一旦輸入了這些值,點擊「Create」按鈕。ASP.NET Identity將建立此用戶帳號,當瀏覽器被重定向到Index動做方法,會顯示出該用戶的信息,如圖13-5所示。從圖中能夠看到不一樣的ID值,這是由於對於每一個用戶帳號,ID是隨機生成的。

圖13-5

Figure 13-5. The effect of adding a new user account
圖13-5. 添加新用戶帳號的效果

Click the Create button again and enter the same details into the form, using the values in Table 13-6. This time when you submit the form, you will see an error reported through the model validation summary, as shown in Figure 13-6.
再次點擊「Create」按鈕,並在表章中輸入一樣細節,即,使用表13-6中的值。當這一次遞交表單時,會看到一條經過模型驗證摘要報告的錯誤,如圖13-6所示。

圖13-6

Figure 13-6. An error trying to create a new user
圖13-6. 試圖建立新用戶時的錯誤

13.3.3 Validating Passwords
13.3.3 驗證口令

One of the most common requirements, especially for corporate applications, is to enforce a password policy. ASP.NET Identity provides the PasswordValidator class, which can be used to configure a password policy using the properties described in Table 13-7.
一個最經常使用的需求,特別是對於公司的應用程序,是強制口令策略。ASP.NET Identity提供了一個PasswordValidator類,能夠用表13-7所描述的屬性來配置口令策略。

Table 13-7. The Properties Defined by the PasswordValidator Class
表13-7. PasswordValidator類定義的屬性
Name
名稱
Description
描述
RequiredLength Specifies the minimum length of a valid passwords.
指定合法口令的最小長度
RequireNonLetterOrDigit When set to true, valid passwords must contain a character that is neither a letter nor a digit.
當設置爲true時,合法口令必須含有非字母和數字的字符
RequireDigit When set to true, valid passwords must contain a digit.
當設置爲true時,合法口令必須含有數字
RequireLowercase When set to true, valid passwords must contain a lowercase character.
當設置爲true時,合法口令必須含有小寫字母
RequireUppercase When set to true, valid passwords must contain an uppercase character.
當設置爲true時,合法口令必須含有大寫字母

A password policy is defined by creating an instance of the PasswordValidator class, setting the property values, and using the object as the value for the PasswordValidator property in the Create method that OWIN uses to instantiate the AppUserManager class, as shown in Listing 13-14.
定義口令策略的辦法是,建立一個PasswordValidator類實例、設置其屬性的值,並在OWIN用來實例化AppUserManager類的Create方法中將該對象做爲PasswordValidator屬性的值,如清單13-14所示。

Listing 13-14. Setting a Password Policy in the AppUserManager.cs File
清單13-14. 在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));
manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true };

return manager; } } }

I used the PasswordValidator class to specify a policy that requires at least six characters and a mix of uppercase and lowercase characters. You can see how the policy is applied by starting the application, navigating to the /Admin/Index URL, clicking the Create button, and trying to create an account that has the password secret. The password doesn't meet the new password policy, and an error is added to the model state, as shown in Figure 13-7.
我用PasswordValidator類指定了一個策略,它要求至少6個字符,並混用大小字符。經過啓動應用程序,即可以看到該口令策略的運用狀況,導航到/Admin/Index網址,點擊Create按鈕,並嘗試建立一個有口令加密的帳號。若口令不知足這一新策略,便會在模型狀態中添加一條錯誤消息,如圖13-7所示。

圖13-7

Figure 13-7. Reporting an error when validating a password
圖13-7. 驗證口令時報告的錯誤

Implementing a Custom Password Validator
實現自定義口令驗證器

The built-in password validation is sufficient for most applications, but you may need to implement a custom policy, especially if you are implementing a corporate line-of-business application where complex password policies are common. Extending the built-in functionality is done by deriving a new class from PasswordValidatator and overriding the ValidateAsync method. As a demonstration, I added a class file called CustomPasswordValidator.cs in the Infrastructure folder and used it to define the class shown in Listing 13-15.
內建的口令驗證對於大多數應用程序足夠了,但你可能須要實現自定義的策略,尤爲是在你實現一個公司的在線業務應用程序時,每每須要複雜的口令策略。這種對內建功能進行擴展的作法是,從PasswordValidatator派生一個新類,並重寫ValidateAsync方法。做爲一個演示,我在Infrastructure文件夾中添加了一個類文件,名稱爲CustomPasswordValidator.cs,如清單13-15所示。

Listing 13-15. The Contents of the CustomPasswordValidator.cs File
清單13-15. CustomPasswordValidator.cs文件的內容

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity; 
namespace Users.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("Passwords cannot contain numeric sequences"); result = new IdentityResult(errors); } return result; } } }

I have overridden the ValidateAsync method and call the base implementation so I can benefit from the built-in validation checks. The ValidateAsync method is passed the candidate password, and I perform my own check to ensure that the password does not contain the sequence 12345. The properties of the IdentityResult class are read-only, which means that if I want to report a validation error, I have to create a new instance, concatenate my error with any errors from the base implementation, and pass the combined list as the constructor argument. I used LINQ to concatenate the base errors with my custom one.
這裏重寫了ValidateAsync方法,並調用了基實現,所以可以受益於內建的驗證檢查。給ValidateAsync方法傳遞了申請的口令,而後執行了本身的檢查,確保口令中不含數據序列12345。IdentityResult類的屬性是隻讀的,這意味着,若是想報告驗證錯誤,則必須建立一個新實例,把這些錯誤與基實現的錯誤聯繫在一塊兒,並將這種組合而成的列表做爲構造器參數進行傳遞。爲了將基實現的錯誤與自定義錯誤聯繫起來,我使用了LINQ。

Listing 13-16 shows the application of my custom password validator in the AppUserManager class.
清單13-6顯示了在AppUserManager類中使用自定義口令驗證器的應用程序。

Listing 13-16. Applying a Custom Password Validator in the AppUserManager.cs File
清單13-16. 在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));
manager.PasswordValidator = new CustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true };
return manager; } } }

To test the custom password validation, try to create a new user account with the password secret12345. This will break two of the validation rules—one from the built-in validator and one from my custom implementation. Error messages for both problems are added to the model state and displayed when the Create button is clicked, as shown in Figure 13-8.
爲了測試自定義口令驗證器,嘗試建立一個以口令密碼爲12345的新用戶帳號。這會打破兩條規則——一條來自於內建驗證器,一條來自於自定義實現。兩個錯誤的錯誤消息被添加到了模型狀態,並在點擊Create按鈕時被顯示出來,如圖13-8所示。

圖13-8

Figure 13-8. The effect of a custom password validation policy
圖13-8. 自定義口令驗證策略的效果

13.3.4 Validating User Details
13.3.4 驗證用戶細節

More general validation can be performed by creating an instance of the UserValidator class and using the properties it defines to restrict other user property values. Table 13-8 describes the UserValidator properties.
還能夠執行更通常的驗證,辦法是建立UserValidator類的實例,並使用它所定義的屬性,以限制用戶其餘屬性的值。表13-8描述了UserValidator的屬性。

Table 13-8. The Properties Defined by the UserValidator Class
表13-8. UserValidator類所定義的屬性
Name
名稱
Description
描述
AllowOnlyAlphanumericUserNames When true, usernames can contain only alphanumeric characters.
當爲true時,用戶名只能含有字母數字字符
RequireUniqueEmail When true, e-mail addresses must be unique.
當爲true時,郵件地址必須惟一

Performing validation on user details is done by creating an instance of the UserValidator class and assigning it to the UserValidator property of the user manager class within the Create method that OWIN uses to create instances. Listing 13-17 shows an example of using the built-in validator class.
對用戶細節執行驗證的作法是建立UserValidator類實例,並在OWIN用來建立實例的Create方法中,將它賦給用戶管理器類的UserValidator屬性。清單13-17演示了使用內建驗證器類的一個示例。

Listing 13-17. Using the Built-in user Validator Class in the AppUserManager.cs File
清單13-17. 在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));
manager.PasswordValidator = new CustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true };
manager.UserValidator = new UserValidator<AppUser>(manager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true };
return manager; } } }

The UserValidator class takes a generic type parameter that specifies the type of the user class, which is AppUser in this case. Its constructor argument is the user manager class, which is an instance of the user manager class (which is AppUserManager for my application).
UserValidator類有一個泛型的類型參數,它指定了用戶類的類型,即本示例中的AppUser。它的構造器參數是用戶管理器類,這是用戶管理器類(此應用程序中的AppUserManager)的一個實例。

The built-in validation support is rather basic, but you can create a custom validation policy by creating a class that is derived from UserValidator. As a demonstration, I added a class file called CustomUserValidator.cs to the Infrastructure folder and used it to create the class shown in Listing 13-18.
內建的驗證支持是至關基本的,但經過建立UserValidator的派生類,能夠建立自定義驗證策略。做爲一個演示,我在Infrastructure文件夾中添加了一個名稱爲CustomUserValidator.cs的類文件,並用它建立了如清單13-19所示的類。

Listing 13-18. The Contents of the CustomUserValidator.cs File
清單13-18. CustomUserValidator.cs文件的內容

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Users.Models; 
namespace Users.Infrastructure { public class CustomUserValidator : UserValidator<AppUser> {
public CustomUserValidator(AppUserManager mgr) : base(mgr) { }
public override async Task<IdentityResult> ValidateAsync(AppUser user) { IdentityResult result = await base.ValidateAsync(user); if (!user.Email.ToLower().EndsWith("@example.com")) { var errors = result.Errors.ToList(); errors.Add("Only example.com email addresses are allowed"); result = new IdentityResult(errors); } return result; } } }

The constructor of the derived class must take an instance of the user manager class and call the base implementation so that the built-in validation checks can be performed. Custom validation is implemented by overriding the ValidateAsync method, which takes an instance of the user class and returns an IdentityResult object. My custom policy restricts users to e-mail addresses in the example.com domain and performs the same LINQ manipulation I used for password validation to concatenate my error message with those produced by the base class. Listing 13-19 shows how I applied my custom validation class in the Create method of the AppUserManager class, replacing the default implementation.
這個派生類的構造器必須以用戶管理器類實例爲參數,並調用基實現,纔可以執行內建的驗證檢查。自定義驗證是經過重寫ValidateAsync方法而實現的,該方法以用戶類實例爲參數,並返回一個IdentityResult對象。上述自定義驗證策略將用戶的E-mail地址限制在example.com主域內,並執行了與口令驗證一樣的LINQ操做,將這裏的錯誤消息與基類產生的錯誤消息關聯在一塊兒。清單13-19演示瞭如何在AppUserManager類的Create方法中運用自定義驗證類,用以替換默認的實現。

Listing 13-19. Using a Custom User Validation Class in the AppUserManager.cs File
清單13-19. 在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));
manager.PasswordValidator = new CustomPasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true };
manager.UserValidator = new CustomUserValidator(manager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true };

return manager; } } }

You can see the result if you try to create an account with an e-mail address such as bob@otherdomain.com, as shown in Figure 13-9.
若是試圖建立一個E-mail地址爲bob@otherdomain.com的帳號,便會看到如圖13-9所示的結果。

圖13-9

Figure 13-9. An error message shown by a custom user validation policy
圖13-9. 由自定義用戶驗證顯示的錯誤消息

13.4 Completing the Administration Features
13.4 完成管理特性

I only have to implement the features for editing and deleting users to complete my administration tool. In Listing 13-20, you can see the changes I made to the Views/Admin/Index.cshtml file to target Edit and Delete actions in the Admin controller.
爲了完成本例的管理工具,我只需實現編輯和刪除用戶的特性。在清單13-20中能夠看到我對Views/Admin/Index.cshtml文件所作修改,它們的目標是Admin控制器中的EditDelete方法。

Listing 13-20. Adding Edit and Delete Buttons to the Index.cshtml File
清單13-20. 在Index.cshtml文件中添加「Edit」和「Delete」按鈕

@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><th></th></tr> 
        @if (Model.Count() == 0) {
            <tr><td colspan="4" 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>
                    <td>
                        @using (Html.BeginForm("Delete", "Admin",
                                    new { id = user.Id })) {
                            @Html.ActionLink("Edit", "Edit", new { id = user.Id },
                                    new { @class = "btn btn-primary btn-xs" })
                            <button class="btn btn-danger btn-xs" type="submit">
                                Delete
                            </button>
                        }
                    </td> 
                </tr>
            }
        }
    </table>
</div>
@Html.ActionLink("Create", "Create", null, new { @class = "btn btn-primary" })

Tip You will notice that I have put the Html.ActionLink call that targets the Edit action method inside the scope of the Html.Begin helper. I did this solely so that the Bootstrap styles will style both elements as buttons displayed on a single line.
提示:你可能會注意到,我在Html.Begin輔助器的範圍內放入了一個Html.ActionLink調用,其目標爲Edit動做方法。這麼作純粹是爲了讓Bootstrap將兩個元素的樣式做爲按鈕顯示成一行。

13.4.1 Implementing the Delete Feature
13.4.1 實現Delete特性

The user manager class defines a DeleteAsync method that takes an instance of the user class and removes it from the database. In Listing 13-21, you can see how I have used the DeleteAsync method to implement the delete feature of the Admin controller.
用戶管理器類定義了一個DeleteAsync方法,它以用戶類實例爲參數,並將其從數據庫刪除。在清單13-21中,能夠看到如何用DeleteAsync方法來實現Admin控制器的刪除特性。

Listing 13-21. Deleting Users in the AdminController.cs File
清單13-21. AdminController.cs文件中的刪除用戶

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 {
// ...other action methods omitted for brevity... // ...出於簡化,這裏忽略了其餘方法...
[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[] { "User Not Found" }); } }
private void AddErrorsFromResult(IdentityResult result) { foreach (string error in result.Errors) { ModelState.AddModelError("", error); } }
private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); } } } }

My action method receives the unique ID for the user as an argument, and I use the FindByIdAsync method to locate the corresponding user object so that I can pass it to DeleteAsync method. The result of the DeleteAsync method is an IdentityResult, which I process in the same way I did in earlier examples to ensure that any errors are displayed to the user. You can test the delete functionality by creating a new user and then clicking the Delete button that appears alongside it in the Index view.
該動做方法以用戶的惟一ID做爲參數,而且使用FindByIdAsync方法定位了相應的用戶對象,以便將該對象傳遞給DeleteAsync方法。DeleteAsync方法的結果是一個IdentityResult,對它的處理方式與以前示例的方式相同,以確保將任何錯誤都顯示給用戶。爲了測試這個刪除功能,能夠建立一個新的用戶,而後在Index視圖中點擊出如今該用戶旁邊的「Delete」按鈕。

There is no view associated with the Delete action, so to display any errors I created a view file called Error.cshtml in the Views/Shared folder, the contents of which are shown in Listing 13-22.
Delete動做沒有相關聯的視圖,所以,爲了將錯誤顯示出來,我在Views/Shared文件夾中建立了一個視圖文件,名稱爲Error.cshtml,其內容如清單13-22所示。

Listing 13-22. The Contents of the Error.cshtml File
清單13-22. 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:
            @: The following errors were encountered:
                <ul>
                    @foreach (string error in Model) {
                        <li>@error</li>
                    }
                </ul>
            break;
    }
</div>
@Html.ActionLink("OK", "Index", null, new { @class = "btn btn-default" })

Tip I put this view in the Views/Shared folder so that it can be used by other controllers, including the one I create to manage roles and role membership in Chapter 14.
提示:我將此視圖放在Views/Shared文件夾中,是爲了其餘控制器也可以使用它,包括第14章爲了管理角色及其成員要建立的控制器。

13.4.2 Implementing the Edit Feature
13.4.2 實現Edit特性

To complete the administration tool, I need to add support for editing the e-mail address and password for a user account. These are the only properties defined by users at the moment, but I'll show you how to extend the schema with custom properties in Chapter 15. Listing 13-23 shows the Edit action methods that I added to the Admin controller.
爲了完成用戶管理工具,我須要爲編輯用戶帳號的E-mail地址和口令的支持。此時這些是用戶定義僅有的屬性,不過到第15章時,將演示如何擴展數據庫架構,使用戶帶有自定義屬性。清單13-23顯示了添加到Admin控制器中的Edit動做方法。

Listing 13-23. Adding the Edit Actions in the AdminController.cs File
清單13-23. 在AdminController.cs文件中添加Edit動做

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 {
// ...other action methods omitted for brevity... // ...出於簡化,這裏忽略了其餘動做方法...
public async Task<ActionResult> Edit(string id) { AppUser user = await UserManager.FindByIdAsync(id); if (user != null) { return View(user); } else { return RedirectToAction("Index"); } }
[HttpPost] public async Task<ActionResult> Edit(string id, string email, string password) { AppUser user = await UserManager.FindByIdAsync(id); if (user != null) { user.Email = email; IdentityResult validEmail = await UserManager.UserValidator.ValidateAsync(user); if (!validEmail.Succeeded) { AddErrorsFromResult(validEmail); } IdentityResult validPass = null; if (password != string.Empty) { validPass = await UserManager.PasswordValidator.ValidateAsync(password); if (validPass.Succeeded) { user.PasswordHash = UserManager.PasswordHasher.HashPassword(password); } else { AddErrorsFromResult(validPass); } } if ((validEmail.Succeeded && validPass == null) || ( validEmail.Succeeded && password != string.Empty && validPass.Succeeded)) { IdentityResult result = await UserManager.UpdateAsync(user); if (result.Succeeded) { return RedirectToAction("Index"); } else { AddErrorsFromResult(result); } } } else { ModelState.AddModelError("", "User Not Found"); } return View(user); }

private void AddErrorsFromResult(IdentityResult result) { foreach (string error in result.Errors) { ModelState.AddModelError("", error); } }
private AppUserManager UserManager { get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); } } } }

The Edit action targeted by GET requests uses the ID string embedded in the Index view to call the FindByIdAsync method in order to get an AppUser object that represents the user.
由GET請求做爲目標的Edit動做使用Index視圖中的ID字符串調用FindByIdAsync方法,目的是得到表示該用戶的AppUser對象。

The more complex implementation receives the POST request, with arguments for the user ID, the new e-mail address, and the password. I have to perform several tasks to complete the editing operation.
那個較複雜的實現接收POST請求,以用戶ID、新E-mail地址以及口令爲參數。我必須執行幾個任務才能完成這種編輯操做。

The first task is to validate the values I have received. I am working with a simple user object at the moment—although I'll show you how to customize the data stored for users in Chapter 15—but even so, I need to validate the user data to ensure that I don't violate the custom policies defined in the 「Validating User Details」 and 「Validating Passwords」 sections. I start by validating the e-mail address, which I do like this:
第一個任務是驗證接收到的值。此時使用的是一個簡單的用戶對象——儘管第15章將演示如何自定義存儲用戶的數據——但即便如此,仍是須要驗證用戶數據,以確保不會與「驗證用戶細節」和「驗證口令」小節中定義的自定義策略相沖突。首先驗證E-mail地址,作法以下:

...
user.Email = email;
IdentityResult validEmail = await UserManager.UserValidator.ValidateAsync(user); 
if (!validEmail.Succeeded) {
    AddErrorsFromResult(validEmail);
}
...

Tip Notice that I have to change the value of the Email property before I perform the validation because the ValidateAsync method only accepts instances of the user class.
提示:注意,在執行驗證以前,必須修改Email屬性的值,由於ValidateAsync方法只接收用戶類的實例。

The next step is to change the password, if one has been supplied. ASP.NET Identity stores hashes of passwords, rather than the passwords themselves—this is intended to prevent passwords from being stolen. My next step is to take the validated password and generate the hash code that will be stored in the database so that the user can be authenticated (which I demonstrate in Chapter 14).
下一個步驟是在已提供口令時,修改用戶口令。ASP.NET Identity存儲的是口令的哈希值,而不是口令自己——目的是防止口令被竊取。個人下一個步驟是取得這個已驗證的口令,並生成將被存儲到數據庫中的哈希碼,以便讓用戶可以被認證,這將在第14章演示。

Passwords are converted to hashes through an implementation of the IPasswordHasher interface, which is obtained through the AppUserManager.PasswordHasher property. The IPasswordHasher interface defines the HashPassword method, which takes a string argument and returns its hashed value, like this:
將口令轉換成哈希值是經過IPasswordHasher接口的實現來作的,該接口實現是經過AppUserManager.PasswordHasher屬性得到的。IPasswordHasher接口定義了HashPassword方法,它以一個字符串爲參數,返回該字符串的哈希值,以下所示:

...
if (password != string.Empty) {
    validPass = await UserManager.PasswordValidator.ValidateAsync(password); 
    if (validPass.Succeeded) {
        user.PasswordHash = UserManager.PasswordHasher.HashPassword(password); 
    } else {
        AddErrorsFromResult(validPass);
    }
}
...

Changes to the user class are not stored in the database until the UpdateAsync method is called, like this:
對用戶類的修改直到調用UpdateAsync方法時,纔會存儲到數據庫中,以下所示:

...
if ((validEmail.Succeeded && validPass == null)
        || ( validEmail.Succeeded && password != string.Empty &&
        validPass.Succeeded)) {
    IdentityResult result = await UserManager.UpdateAsync(user); 
    if (result.Succeeded) {
        return RedirectToAction("Index");
    } else {
        AddErrorsFromResult(result);
    }
}
...

1. Creating the View
1. 建立視圖

The final component is the view that will render the current values for a user and allow new values to be submitted to the controller. Listing 13-24 shows the contents of the Views/Admin/Edit.cshtml file.
最後一個組件是渲染當前用戶值並將新值遞交給控制器的視圖。清單13-24顯示了Views/Admin/Edit.cshtml文件的內容。

Listing 13-24. The Contents of the Edit.cshtml File
清單13-24. Edit.cshtml文件的內容

@model Users.Models.AppUser
@{ ViewBag.Title = "Edit"; }
@Html.ValidationSummary(false)
<h2>Edit User</h2> 
<div class="form-group"> <label>Name</label> <p class="form-control-static">@Model.Id</p> </div> @using (Html.BeginForm()) { @Html.HiddenFor(x => x.Id) <div class="form-group"> <label>Email</label> @Html.TextBoxFor(x => x.Email, new { @class = "form-control" }) </div> <div class="form-group"> <label>Password</label> <input name="password" type="password" class="form-control" /> </div> <button type="submit" class="btn btn-primary">Save</button> @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default" }) }

There is nothing special about the view. It displays the user ID, which cannot be changed, as static text and provides a form for editing the e-mail address and password, as shown in Figure 13-10. Validation problems are displayed in the validation summary section of the view, and successfully editing a user account will return to the list of accounts in the system.
該視圖沒什麼特別的。它將用戶ID顯示成靜態文本,這是不能修改的,而且提供了一個對E-mail地址和口令進行編輯的表單,如圖13-10所示。驗證問題會顯示在視圖的驗證摘要處,當成功編輯一個用戶帳號時,會返回到系統的帳號列表。

圖13-10

Figure 13-10. Editing a user account
圖13-10. 編輯用戶帳號

13.5 Summary
13.5 小結

In this chapter, I showed you how to create the configuration and classes required to use ASP.NET Identity and demonstrated how they can be applied to create a user administration tool. In the next chapter, I show you how to perform authentication and authorization with ASP.NET Identity.
在本章中,我演示瞭如何建立使用ASP.NET Identity所需的配置和類,而且演示瞭如何運用它們來建立用戶管理工具。下一章將演示如何用ASP.NET Identity執行認證與受權。


看完此文若是以爲有所收穫,請給個推薦。 你的推薦是我繼續下去的動力,也會讓更多人關注並獲益,這也是你的貢獻。

相關文章
相關標籤/搜索