2018 年 8 月

第 33 卷 8

本文章是由機器翻譯。

技術最前線-ASP.NET Core SignalR 使用的身分證樣式通知

藉由Dino Esposito |2018 年 8 月

Dino Esposito社交網路和行動作業系統所做的快顯,氣球樣式通知非常受歡迎且研究主流,但 Microsoft Windows 2000 可能是廣泛使用的第一個軟體。球形文字說明通知會使您能夠通訊使用者值得注意的事件,而不需要立即採取行動,並注意 — 有別於傳統的快顯視窗。這些提示氣球樣式通知的關鍵在於會將訊息傳遞即時項目,由右至開啟的視窗中的使用者使用的儲存基礎結構。

在本文中,您會看到如何使用 ASP.NET Core SignalR 產生快顯通知。本文示範的範例應用程式會追蹤記錄的使用者並讓每個建置及維護的朋友網路有機會。例如在社交網路案例中,任何登入的使用者可能新增或移除從 friend 清單在任何時間。當發生這種情況的範例應用程式中時,登入的使用者會收到的即時線上的通知。

驗證應用程式使用者

氣球樣式通知不是純文字的廣播的通知傳送至任何使用者用來接聽以 SignalR Web 通訊端通道上的訊息。相反地,它們會被傳送到特定的使用者,登入應用程式。開啟,並接聽一般的 Web 通訊端通道是一個好的方法,解決此問題,但 ASP.NET Core SignalR 只要提供詳細的抽象程式設計介面,並提供 Websocket 以外的替代網路通訊協定的支援。

建置應用程式的第一個步驟加入圖層進行使用者驗證。使用者會顯示標準的登入表單,並提供其認證。一旦正確辨識為有效的使用者的系統,她會收到驗證 cookie 封裝以數字的宣告,如下所示:

var claims = new[]
{
  new Claim(ClaimTypes.Name, input.UserName),
  new Claim(ClaimTypes.Role, actualRole)
};
var identity = new ClaimsIdentity(claims,
  CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
  CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(identity));

範例程式碼示範如何建立臨機操作的 IPrincipal 物件在 ASP.NET Core 中,括住的使用者名稱之後成功驗證認證。其金鑰記下才能正常運作,在 ASP.NET Core 中的驗證,當用於搭配最新的 SignalR 程式庫,您應該啟用在 Configure 方法中的驗證啟動類別相當早期 (而且在任何情況下,早於完成SignalR 的路由設定)。我將在這個端點上稍後;同時,讓我們檢閱在範例應用程式的夥伴關係的基礎結構。

在 [應用程式中定義的朋友

範例應用程式中,為了 friend 關聯性是只是兩個系統的使用者之間的連結。請注意,示範應用程式不使用任何資料庫來保存使用者和關聯性。少數的使用者和 friend 關聯性是硬式編碼,並重新載入應用程式重新啟動時重設或目前的檢視。以下是表示 friend 關聯性的類別:

public class FriendRelationship
{
  public FriendRelationship(string friend1, string friend2)
  {
    UserName1 = friend1;
    UserName2 = friend2;
  }
  public string UserName1 { get; set; }
  public string UserName2 { get; set; }
}

當使用者登入時,他有提供索引頁,提供的好友名單。透過頁面的 UI,使用者可以加入新朋友和移除現有的 (請參閱**[圖 1**)。

記錄使用者的 [首頁] 頁面
圖 1] 的登入使用者的 [首頁] 頁面

當使用者輸入新朋友的名稱時,HTML 表單張貼,並在記憶體中建立新的 friend 關聯性。如果具類型的名稱不符合現有的使用者,新的使用者物件已建立並加入至記憶體中清單中,就像這樣:

[HttpPost]
public IActionResult Add(string friend)
{
  var currentUser = User.Identity.Name;
  UserRepository.AddFriend(currentUser, friend);
  // More code here
  ...
  return new EmptyResult();
}

您可能會注意到,因為控制器方法會傳回空白的動作結果。事實上,它會假設 HTML 表單張貼透過 JavaScript 其內容。因此,請按一下 JavaScript 以程式設計的方式,如下所示的表單的 [提交] 按鈕附加處理常式是:

<form class="form-horizontal" id="add-form" method="post"
      action="@Url.Action("add", "friend")">
      <!-- More input controls here -->
      <!-- SUBMIT button -->
  <button id="add-form-submit-button"
       class="btn btn-danger" type="button">
       SAVE  </button></form>

張貼程式碼的 JavaScript 觸發伺服器端作業,並傳回。新的朋友清單是否為 JSON 陣列或 HTML 字串,可以是控制器方法所傳回並整合目前的頁面文件物件模型中的相同的 JavaScript 呼叫端程式碼。它的效果不錯,但沒有考量可能是問題,在某些情況下是故障。

假設使用者保留多個連線至相同的 [伺服器] 頁面。舉例來說,使用者可開啟相同的頁面上的多個瀏覽器視窗,並與其中一個這些頁面互動。在其中呼叫恢復的直接回應的情況下 (是否 JSON 或 HTML),從要求的起源頁面最後會更新。任何其他開啟的瀏覽器視窗會保持靜態,不會受到影響。若要解決此問題,您可以利用 ASP.NET Core SignalR,可讓您為相同的使用者帳戶相關的所有連線的廣播變更的功能。

使用者連線至廣播

ASP.NET MVC 控制器類別會接收呼叫,以新增或移除朋友納入 SignalR 中樞內容的參考。此程式碼示範如何完成:

[Authorize]
public class FriendController : Controller
{
  private readonly IHubContext<FriendHub> _friendHubContext;
  public FriendController(IHubContext<FriendHub> friendHubContext)
  {
    _friendHubContext = friendHubContext;
  }
  // Methods here
  ...
}

如往常般在 SignalR 程式設計 friend 中樞會定義於啟動類別中所示**[圖 2**。

定義中樞的 [圖 2

public void Configure(IApplicationBuilder app)
{
  // Enable security
  app.UseAuthentication();
  // Add MVC
  app.UseStaticFiles();
  app.UseMvcWithDefaultRoute();
  // SignalR (must go AFTER authentication)
  app.UseSignalR(routes =>
  {
    routes.MapHub<FriendHub>("/friendDemo");
  });
}

它的索引鍵呼叫 UseSignalR 遵循 UseAuthentication 呼叫。這可確保 SignalR 連線建立時指定的路由上有可用的登入的使用者和宣告的相關資訊。[圖 3提供更深入的了解當使用者加入至清單的新朋友,處理表單張貼的程式碼。

[圖 3 處理表單張貼

[HttpPost]
public IActionResult Add(string friend)
{
  var currentUser = User.Identity.Name;
  UserRepository.AddFriend(currentUser, friend);
  // Broadcast changes to all user-related windows
  _friendHubContext.Clients.User(currentUser).SendAsync("refreshUI");
  // More code here  ...
  return new EmptyResult();
}

中樞內容的用戶端屬性具有屬性,稱為使用者,會採用使用者識別碼。當使用者物件上叫用,SendAsync 方法會通知指定的訊息至相同的使用者名稱底下的所有目前連接的瀏覽器視窗。換句話說,SignalR 能夠將自動的所有連線來自相同的已驗證使用者的單一集區。這麼一來,從使用者叫用的 SendAsync 有訊息廣播到所有 windows 相關的使用者,其來自多個桌面瀏覽器、 應用程式內 Web 檢視、 行動瀏覽器、 桌面用戶端或其他任何是否。請看看下面這個程式碼:

_friendHubContext.Clients.User(currentUser).SendAsync("refreshUI");

將 refreshUI 訊息傳送至相同的使用者名稱底下的所有連線,可確保所有已開啟的視窗會以同步處理至新增 friend 函式的方式。範例在原始程式碼中,您會看到目前登入的使用者從其清單中移除朋友時會相同的動作。

設定使用者的用戶端 Proxy

SignalR 中使用者物件無關的 HTTP 內容相關聯的使用者物件。相同的屬性名稱,儘管 SignalR 使用者物件是用戶端 proxy 和容器的宣告。代表已驗證的使用者物件 SignalR 特定使用者的用戶端 proxy 之間存在的微妙的關聯性。您可能已經注意到,使用者用戶端代理程式要求的字串參數。在中的程式碼**[圖 3**,字串參數是目前登入的使用者名稱。是只是字串識別項,不過,而且可以是您要設定的任何項目。

根據預設,使用者用戶端 proxy 所辨識的使用者識別碼會是 NameIdentifier 宣告的值。如果已驗證使用者的宣告清單包含 NameIdentifier,沒有機會讓廣播可行。因此,您會有兩個選項:一個是建立驗證 cookie 時,將 NameIdentifier 宣告,另一個則是撰寫您自己的 SignalR 使用者識別碼提供者。若要將 NameIdentifier 宣告,您需要登入程序中的下列程式碼:

var claims = new[]
{
  new Claim(ClaimTypes.Name, input.UserName),
  new Claim(ClaimTypes.NameIdentifier, input.UserName),
  new Claim(ClaimTypes.Role, actualRole),
};

指派給 NameIdentifier 宣告的值並不重要,只要它是唯一給每位使用者。就內部而言,SignalR 使用 IUserIdProvider 元件,以符合連接群組的使用者識別碼根項目,為目前記錄的使用者,就像這樣:

public interface IUserIdProvider
{
  string GetUserId(HubConnectionContext connection);
}

IUserIdProvider 介面中的 DI 基礎結構,其預設實作。類別是 DefaultUserIdProvider,並且自動程式碼,如下所示:

public class DefaultUserIdProvider : IUserIdProvider
{
  public string GetUserId(HubConnectionContext connection)
  {
    var first = connection.User.FindFirst(ClaimTypes.NameIdentifier);
    if (first == null)
      return  null;
    return first.Value;
  }
}

如您所見,DefaultUserIdProvider 類別會使用 NameIdentifier 宣告的值來分組使用者特有的連接識別碼。名稱宣告是用來指出使用者的名稱,但不是一定能夠透過它來識別使用者在系統中的唯一識別碼。NameIdentifier 宣告,反而是設計用來保存唯一的值,是否為 GUID、 字串或整數。如果您切換到使用者而不是 NameIdentifer,確定是唯一每位使用者的指派名稱的值。

來自具有相符名稱識別碼的帳戶的所有連線會分組在一起,而且會自動在使用者用戶端 proxy 使用時才通知。若要切換為使用標準的名稱宣告,您需要自訂的 IUserIdProvider,執行下列,如下所示:

public class MyUserIdProvider : IUserIdProvider
{
  public string GetUserId(HubConnectionContext connection)
  {
    return connection.User.Identity.Name;
  }
}

不用說,此元件都必須向 DI 基礎結構在啟動階段。以下是要包含在 startup 類別的 ConfigureServices 方法中的程式碼:

services.AddSignalR();
services.AddSingleton(typeof(IUserIdProvider), typeof(MyUserIdProvider));

到目前為止,所有項目是設定讓所有相符的使用者 windows 上相同的狀態及檢視同步處理。如何提示氣球樣式通知?

最後一個步驟

新增和移除朋友會導致重新整理通知傳送至 [索引] 頁面目前的使用者所檢視的需要。如果指定的使用者有兩個瀏覽器視窗開啟相同的應用程式 (索引和其他頁面) 的不同頁面上,她會收到重新整理通知只會針對 [索引] 頁面。不過,加入和移除的朋友也會導致新增和移除通知傳送至已加入或從 friend 關聯性清單中移除的使用者。例如,如果使用者 Dino 決定從他的朋友的清單中移除使用者 Mary,Mary 的使用者也會收到移除通知。在理想情況下,移除 (或加入) 的通知應連線到要檢視的網頁,是否不論使用者或任何其他的索引。

若要達到此目的,有兩個選項:

  • 移至 [配置] 頁面,而且所有頁面,依據該配置然後都繼承連線設定中使用單一的 SignalR 中樞。
  • 使用兩個不同的中樞,一個用於新增或移除朋友之後重新整理 UI,以通知的一個新增或移除使用者。

如果您決定要使用不同的中樞,新增或移除通知中樞必須設定所有頁面中您想要顯示的通知,您最可能的版面配置頁面有應用程式中。

範例應用程式會使用單一中樞,在 [配置] 頁面中完成設定。請注意,JavaScript 物件,參考目前的連線全域共用內的用戶端,這表示 SignalR 的初始化程式碼更位於頂端的版面配置主體和 Razor RenderBody 區段之前。

讓我們看看最後的程式碼,在控制器中的 Add 方法。這個方法是在表單**[圖 1最終文章。此方法可讓後端儲存 friend 關聯性中的任何必要的變更,並接著會發出兩個 SignalR 訊息 — 一個用來以視覺方式重新整理的目前使用者執行作業,且第二個通知加入 (或移除) 使用者的好友名單。如果她目前連接至應用程式,並從接收和處理這些特定的通知設定頁面,實際上會通知使用者。[圖 4**會示範這點。

[圖 4 最終加入方法的程式碼

public IActionResult Add(string addedFriend)
{
  var currentUser = User.Identity.Name;
  // Save changes to the backend
  UserRepository.AddFriend(currentUser, addedFriend);
  // Refresh the calling page to reflect changes
  _friendHubContext.Clients.User(currentUser).SendAsync("refreshUI");
  // Notify the added user (if connected)
  _friendHubContext.Clients.User(addedFriend).SendAsync("added", currentUser);
  return new EmptyResult();
}

在 [配置] 頁面中,一些 JavaScript 程式碼會顯示氣球樣式通知 (或您想要的任何類型的 UI 調整)。在範例應用程式中,通知的形式為最多 10 秒,通知列上所顯示的訊息中的程式碼所示:

friendConnection.on("added", (user) => {
  $("#notifications").html("ADDED by <b>" + user + "</b>");
  window.setTimeout(function() {
            $("#notifications").html("NOTIFICATIONS");
    },
    10000);
});

結果會顯示在**[圖 5**。

跨使用者通知
[圖 5 跨使用者通知

SignalR 的群組上的文字

在本文中,我會涵蓋 SignalR 支援所選的通知傳送至一組相關的使用者。SignalR 提供幾種方法,使用者用戶端 proxy 和群組。差異很微妙:使用者用戶端 proxy 會以隱含方式產生群組,其中名稱取決於使用者識別碼,而成員是在相同的應用程式使用者開啟的所有連線的數目。群組是更一般性的機制,以程式設計方式將連線附加至的邏輯群組。連接和群組的名稱會以程式設計方式設定。

提示氣球樣式通知可能已在這兩種方法中實作,但此特定案例的使用者用戶端 proxy 是最自然的解決方案。原始程式碼位於bit.ly/2HVyLp5


Dino Esposito有 20 個以上的書籍和 1,000 的文章,他 25 年職涯中撰寫。作者的 「 休假中斷,「 電影樣式節目,Esposito 正在將擁有更為環保的世界的軟體撰寫成在 BaxEnergy 數位策略家。在 Twitter 上關注與他連絡: @despos


MSDN Magazine 論壇中的這篇文章的討論