本文章是由機器翻譯。

技術最前線

使用 ASP.NET Identity 儲存使用者資料

Dino Esposito

Dino EspositoASP.NETVisual Studio2013年的身份是簡化輛無趣但必要的任務,管理使用者資料和建立一個更有效的會員制度的途徑。以前,我提供ASP.NET身份 API 的概述 (msdn.microsoft.com/magazine/dn605872) 和審查其參與社交網路和 OAuth 協定 (msdn.microsoft.com/magazine/dn745860)。在這篇文章,我將擴展ASP.NET身份的使用者資料表示與基礎資料存儲區啟動的可擴充性點。

奠定基礎

首先,讓我們在Visual Studio2013年中創建一個新的空白ASP.NETMVC 專案。 所有嚮導提供的預設值是好的但一定要選擇單個使用者的身份驗證模式。搭建的代碼將使用者資料存儲在本地SQL Server檔與自動生成的名稱與以下公約:aspnet-[專案名稱]-[RandomNumber]。該代碼還使用Entity Framework來訪問使用者資料庫的讀寫能力。使用者資料表示形式是在 ApplicationUser 類中:

public class ApplicationUser : IdentityUser
{
}

正如你所看到的 ApplicationUser 類繼承的系統提供的 IdentityUser 類。要自訂的使用者表示,讓我們從這裡開始,並向類中添加新成員:

public class ApplicationUser : IdentityUser
{
  public String MagicCode { get; set; }
}

類的名稱 — — 在本示例中的 ApplicationUser — — 不是強制要求,你可以改變它,如果你想。在此示例中,我特意選擇了怪異的"魔法代碼"欄位,以指示雙的可能性。您可以添加需要 UI 如出生日期、 社會安全號碼或任何其他你想要在註冊過程中指定的欄位。您還可以添加的欄位,您需要有,默默地 (例如,特定于應用程式的"神奇"的代碼) 可以計算,但當使用者記錄實際上創造。ApplicationUser 類包含預設情況下中列出的成員圖 1

圖 1 在 IdentityUser 基類上定義的成員

Member 說明
Id 唯一自動生成識別碼 (GUID) 的表。此欄位是主鍵。
UserName 顯示名稱的使用者。
PasswordHash 因提供的密碼的雜湊值。
SecurityStamp 在特定點在 UserManager 物件存留期中自動創建的 GUID。通常情況下,它具有創建和更新時添加或移除密碼更改或社會的登錄名。安全郵票一般使用者資訊創建快照並會自動將使用者記錄,如果什麼都沒有改變。
鑒別器 此列是特定于Entity Framework持久性模型,確定特定的行所屬的類。你要有一個唯一的鑒別符的值,為每個類層次結構植根于 IdentityUser 中。

 

中列出的欄位圖 1,當您以程式設計方式添加在 ApplicationUser 類定義中,以及任何其他欄位最終存儲在資料庫表中。預設表名稱是 AspNetUsers。此外,IdentityUser 類公開更多的屬性,如登錄名、 索賠和角色。這些屬性並不存儲在 AspNetUsers 表中,但在同一資料庫中其他側表中找到自己的位置 — — AspNetUserRoles、 AspNetUserLogins 和 AspNetUserClaims (見圖 2)。

預設結構的ASP.NET標識使用者資料庫
圖 2 預設結構的ASP.NET標識使用者資料庫

修改 ApplicationUser 類並不能確保你會立即反映在使用者介面中,並保存到資料庫中的附加欄位。幸運的是,更新資料庫,並不需要太多的工作。

搭建的代碼更改

您將需要編輯的應用程式視圖和模型以反映新的領域。申請表格在哪裡新使用者在網站註冊,您需要添加一些標記來顯示的使用者介面,神奇的代碼和任何其他額外的欄位。圖 3 顯示修訂的代碼為 CSHTML 剃鬚刀檔註冊表單後面。

圖 3 Razor 檔向使用者提供額外的欄位

@using (Html.BeginForm("Register", "Account",
  FormMethod.Post, new 
    { @class = "form-horizontal", role = "form" }))
{
  @Html.AntiForgeryToken()
  <h4>Create a new account.</h4>
  <hr />
  @Html.ValidationSummary()
  <div class="form-group">
    @Html.LabelFor(m => m.MagicCode, 
      new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
      @Html.TextBoxFor(m => m.MagicCode, 
        new { @class = "form-control" })
    </div>
  </div>   
  ...
}

CSHTML 登記表格基於視圖模型類,通常被稱為 RegisterViewModel。圖 4 顯示 RegisterViewModel 類,把它插到大多數ASP.NETMVC 應用程式基於資料批註的經典驗證機制所需的更改。

圖 4 更改為登記冊視圖模型類

public class RegisterViewModel
{
  [Required]
  [Display(Name = "User name")]
  public string UserName { get; set; }
  [Required]
  [StringLength(100, ErrorMessage = 
    "The {0} must be at least {1} character long.",
    MinimumLength = 6)]
  [DataType(DataType.Password)]
  [Display(Name = "Password")]
  public string Password { get; set; }
  [DataType(DataType.Password)]
  [Display(Name = "Confirm password")]
  [Compare("Password", ErrorMessage = 
    "Password and confirmation do not match.")]
  public string ConfirmPassword { get; set; }
  [Required]
  [Display(Name = "Internal magic code")]
  public string MagicCode { get; set; }
}

這些變化是不夠,然而。還有一個所需的更多步驟,這是最關鍵。你必須通過額外的資料,直至將它寫入持久存儲區的層。您需要對流程從註冊表單 POST 操作的控制器方法做進一步更改:

public async Task<ActionResult> Register(RegisterViewModel model)
{
  if (ModelState.IsValid) {
    var user = new ApplicationUser() { UserName = model.UserName,
      MagicCode = model.MagicCode };
    var result = await UserManager.CreateAsync(user, model.Password);
    if (result.Succeeded) {
      await SignInAsync(user, isPersistent: false);
      return RedirectToAction("Index", "Home");
    }
}

關鍵的變化保存為神奇的代碼 (或任何其他您想要將添加到使用者定義) 所發佈的資料。這被保存到 ApplicationUser 實例被傳遞給 CreateAsync 方法的 UserManager 物件。

尋找到持久存儲區

在由ASP.NETMVC 範本生成的示例代碼中,AccountController 類具有在這裡定義的成員:

public UserManager<ApplicationUser> UserManager { get; private set; }

UserManager 類進行具現化,將使用者存儲物件傳遞。ASP.NET身份附帶的預設使用者存儲區:

var defaultUserStore = new UserStore<ApplicationUser>(new ApplicationDbContext())

就像 ApplicationUser,ApplicationDbCoNtext 類繼承自 (名為 IdentityDbCoNtext) 的系統定義的類,並環繞Entity Framework做真正的持久性的工作。好消息是你可以完全拔下的預設存儲機制和滾你自己。可以基於您的自訂儲存引擎SQL Server和Entity Framework,並只需使用一個不同的架構。您也可以利用一個完全不同的儲存引擎如 MySQL 或 NoSQL 解決方案。讓我們看看如何安排基於 RavenDB 的嵌入式版本的使用者存儲區 (ravendb.net)。你需要的類的原型如下所示:

public class RavenDbUserStore<TUser> :
  IUserStore<TUser>, IUserPasswordStore<TUser>
  where TUser : TypicalUser
{
  ...
}

如果您打算支援登錄、 角色和索賠,你會需要實現多個介面。最小的工作解決方案,IUserStore 和 IUserPasswordStore 是足夠的。類典型­使用者是我創建了繼續解耦ASP.NET的身份基礎設施盡可能多地從一個自訂類:

public class TypicalUser : IUser
{
  // IUser interface
  public String Id { get; set; }
  public String UserName { get; set; }
  // Other members
  public String Password { get; set; }
  public String MagicCode { get; set; }
}

至少,使用者類必須實現 IUser 介面。該介面計數兩個成員 — — 使用者名和 Id。你可能會想要添加密碼成員。這是被保存在 RavenDB 存檔的使用者類。

向您的專案,通過 RavenDB.Embedded NuGet 套裝程式添加 RavenDB 支援。在 global.asax,你還需要用下面的代碼初始化資料庫:

private static IDocumentStore _instance;
public static IDocumentStore Initialize()
{
  _instance = new EmbeddableDocumentStore 
    { ConnectionStringName = "RavenDB" };
  _instance.Initialize();
  return _instance;
}

連接字串指向應創建資料庫的路徑。在ASP.NETWeb 應用程式中,自然適合是在 App_Data 下的子資料夾:

<add name="RavenDB" connectionString="DataDir = ~\App_Data\Ravendb" />

使用者存儲類包含代碼中的 IUserStore 和 IUserPasswordStore 介面的方法。這些讓應用程式管理使用者和相關的密碼。圖 5 顯示存儲實現。

圖 5 微工作使用者存儲基於 RavenDB

public class RavenDbUserStore<TUser> :
  IUserStore<TUser>, IUserPasswordStore<TUser>
  where TUser : TypicalUser
{
  private IDocumentSession DocumentSession { get; set; }
  public RavenDbUserStore()
  {
    DocumentSession = RavenDbConfig.Instance.OpenAsyncSession();
  }
  public Task CreateAsync(TUser user)
  {
    if (user == null)
      throw new ArgumentNullException();
    DocumentSession.Store(user);
    return Task.FromResult<Object>(null);
  }
  public Task<TUser> FindByIdAsync(String id)
  {
    if (String.IsNullOrEmpty(id))
      throw new ArgumentException();
    var user = DocumentSession.Load<TUser>(id);
    return Task.FromResult<TUser>(user);
  }
  public Task<TUser> FindByNameAsync(String userName)
  {
    if (string.IsNullOrEmpty(userName))
      throw new ArgumentException("Missing user name");
    var user = DocumentSession.Query<TUser>()
      .FirstOrDefault(u => u.UserName == userName);
    return Task.FromResult<TUser>(user);
  }
  public Task UpdateAsync(TUser user)
  {
    if (user != null)
      DocumentSession.Store(user);
    return Task.FromResult<Object>(null);
  }
  public Task DeleteAsync(TUser user)
  {
    if (user != null)
      DocumentSession.Delete(user);
    return Task.FromResult<Object>(null);
  }
  public void Dispose()
  {
    if (DocumentSession == null)
      return;
    DocumentSession.SaveChanges();
    DocumentSession.Dispose();
  }
  public Task SetPasswordHashAsync(TUser user, String passwordHash)
  {
    user.Password = passwordHash;
    return Task.FromResult<Object>(null);
  }
  public Task<String> GetPasswordHashAsync(TUser user)
  {
    var passwordHash = user.Password;
    return Task.FromResult<string>(passwordHash);
  }
  public Task<Boolean> HasPasswordAsync(TUser user)
  {
    var hasPassword = String.IsNullOrEmpty(user.Password);
    return Task.FromResult<Boolean>(hasPassword);
  }
 }
}

與 RavenDB 的任何交互通過在開始和結束文檔存儲會話。在建構函式中的 RavenDbUserStore,你打開的會話和解雇中存儲物件的 Dispose 方法。在罷免前屆會議上,雖然,調用 SaveChanges 方法來堅持所有暫止的變更,根據單元的工作模式:

public void Dispose()
{
  if (DocumentSession == null)
    return;
  DocumentSession.SaveChanges();
  DocumentSession.Dispose();
}

要使用 RavenDB 資料庫的 API 是相當簡單的。這裡是您將需要創建一個新使用者的代碼:

public Task CreateAsync(TUser user)
{
  if (user == null)
    throw new ArgumentNullException();
  DocumentSession.Store(user);
  return Task.FromResult<Object>(null);
}

若要檢索給定的使用者,對該文檔使用查詢方法­會話物件:

var user = DocumentSession.Load<TUser>(id);

RavenDB 假定你堅持下去的任何類都具有一個 Id 屬性。如果不是,它將隱式地創建此類屬性所以你可以總是使用 Load 方法來檢索任何物件的 Id,無論是你自己的 ID 或系統生成的 id。 要按名稱檢索的使用者,請執行一經典的查詢,使用LINQ語法:

var user = DocumentSession
              .Query<TUser>()
              .FirstOrDefault(u => u.UserName == userName);

使用可以選擇各種物件並將它們存儲到一個易於管理的清單。密碼的處理也同樣簡單。RavenDB 將密碼存儲在雜湊格式,但是雜湊 RavenDB 模組外部管理。SetPasswordHashAsync 方法,事實上,已經收到使用者提供的密碼雜湊值。

圖 5 是完整的原始程式碼相容ASP.NET身份設立一個 RavenDB 使用者存儲區。它是不足以登錄使用者和在外,當你有安裝的 RavenDB 的嵌入式的版本。對於更複雜的功能,例如外部登錄和帳戶管理,您需要實現所有ASP.NET身份有關使用者介面或意義­明顯返工的帳戶中的代碼­你從腳手架的控制器。

結語

ASP.NET身份允許您完全拔下Entity Framework-相反,基於存儲基礎結構和使用 RavenDB 檔和架構免費資料庫。你可以安裝 RavenDB 作為 Windows 服務,IIS 應用程式或者嵌入像我一樣在這裡。只是編寫一個實現幾個ASP.NET標識介面的類,這個新的存儲類注入 UserManager 基礎設施。更改使用者類型架構是容易的甚至在基於 EF 的預設配置。如果您使用 RavenDB,雖然,你擺脫任何移徙問題應使用者的格式更改。


Dino Esposito 合著的"Microsoft.NET:構建企業應用程式"(微軟出版社,2014年) 和"程式設計ASP.NETMVC 5"(微軟出版社,2014年)。Microsoft.NET 框架和 Android 平臺,它也會經常在世界各地的業內活動中發表演講的技術傳教士,埃斯波西托分享他視覺軟體 software2cents.wordpress.com 和在 Twitter 上 twitter.com/despos

感謝以下技術專家對本文的審閱:毛羅 · Servienti