註:本文是ASP.NET Identity系列教程的第一篇。本系列教程詳細、完整、深入地介紹了微軟的ASP.NET Identity技術,描述瞭如何運用ASP.NET Identity實現應用程式的用戶管理,以及實現應用程式的認證與授權等相關技術,譯者希望本系列教程成為掌握ASP.NET Ident...
註:本文是【ASP.NET Identity系列教程】的第一篇。本系列教程詳細、完整、深入地介紹了微軟的ASP.NET Identity技術,描述瞭如何運用ASP.NET Identity實現應用程式的用戶管理,以及實現應用程式的認證與授權等相關技術,譯者希望本系列教程能成為掌握ASP.NET Identity技術的一份完整而有價值的資料。讀者若是能夠按照文章的描述,一邊閱讀、一邊實踐、一邊理解,定能有意想不到的巨大收穫!希望本系列博文能夠得到廣大園友的高度推薦。
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,而且難以擴展,除非重新實現大量的提供器類。其數據架構本身也過於複雜,使之比理論上還要難以實現修改。
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雲服務,也能夠創建資料庫。但即使有了改善,其依賴於關係型數據以及難以定製等根本問題仍然存在。
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。正如將在本章以及第14、15章所瞭解到的,ASP.NET Identity靈活且可擴展,但它仍不成熟,你在一些更成熟的系統中能夠獲得的特性,可能需要超常的工作量。
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實現往往很複雜,我難以提供有用的通用示例)。
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是本章概要。
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.cshtml和Views/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所示的結果。
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所示。
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所示。
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所定義的內建屬性(現在流行將“內建”說成“內置”,其實這兩者在含義上有很大差別,“內建”完全是自己創建的,而“內置”有可能是別人做的東西拿來放入其中的——譯者註),本章將使用這些屬性。
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 |
■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> 類為管理用戶而定義的基本方法和操作。還有一些其他方法,這裡並未全部列出來,我會在適當的情形下對它們進行描述,那時會介紹管理用戶數據的不同方式。
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的情形。