August 2018

VOLUME 33 NUMBER 8

Cutting Edge - ASP.NET Core SignalR を使用したソーシャル スタイルの通知

によってDino Esposito |2018 の年 8 月

Dino Espositoソーシャル ネットワークとモバイル Os のポップアップで行われた、非常に人気があり、メイン ストリーム、バルーン形式の通知が Microsoft Windows 2000 は、広範囲に使用する最初のソフトウェアでされた可能性があります。バルーン通知できるようなります。 早急な対応、注意を必要とせずに、ユーザーの注目すべきイベントを通信するために、従来のポップアップ ウィンドウとは異なりします。これらの通知のバルーン形式のキーは、ユーザーが作業しているウィンドウを開く権限をリアルタイムでメッセージを配信する基になるインフラストラクチャです。

この記事では、ASP.NET Core SignalR を使用して、ポップアップ通知を生成する方法を確認します。記事では、サンプル アプリケーションのログに記録されたユーザーを追跡し、それぞれに構築し、友人のネットワークを管理することです。ようなソーシャル ネットワークのシナリオで、ログインしたユーザーが追加またはいつでも、フレンド リストから削除します。サンプル アプリケーションではこの場合は、ログオンしているユーザーは、状況依存の通知を受け取ります。

アプリケーション ユーザーの認証

バルーン形式の通知は、すべてのユーザーがメッセージをリッスンする 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));

サンプル コードでは、資格情報の確認が完了した後のユーザー名を囲む、ASP.NET Core でのアドホック IPrincipal オブジェクトを作成する方法を示します。ASP.NET Core で適切に機能する認証の場合、最新の SignalR ライブラリと組み合わせて使用するとする必要があります有効にすること、Configure メソッドで、startup クラスのかなり早い段階に注意してくださいキー (いずれの場合も、完了するよりも前、。SignalR ルート初期化) します。現時点でこのポイントで返しますその一方で、サンプル アプリケーションでは、フレンド関係のインフラストラクチャを確認しましょう。

アプリケーションでのフレンドの定義

サンプル アプリケーションでの目的では、フレンド関係は、システムの 2 つのユーザーの間でリンクだけです。デモ アプリケーションがユーザーとの関係を保持する任意のデータベースを使用しないことに注意してください。いくつかのユーザーとフレンド関係はハードコードし、リセット、アプリケーションの再起動時または現在のビューが再読み込みします。フレンド関係を表すクラスを次に示します。

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

ように、ユーザーがログイン、友人のリストを提供する index ページ、彼が処理します。ページの ui ユーザー両方の新しい友人を追加および削除できます既存の (を参照してください図 1)。

ログオンしているユーザーのホーム ページ
図 1 ログオンしているユーザーのホーム ページ

ユーザーが新しい友人の名前を入力すると HTML フォーム ポストし、メモリ内に新しいフレンド関係を作成します。型指定された名前が既存のユーザーに一致しない場合、新しいユーザー オブジェクトを作成し、メモリ内のリストに追加された次のように。

[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 プログラミングでは、フレンド ハブが定義されて、startup クラスでのように図 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();
}

ハブ コンテキストの Clients プロパティがユーザー ID を取得するには、ユーザーをという名前のプロパティユーザー オブジェクトで呼び出され、SendAsync メソッドは、同じユーザー名の下のすべての現在接続されているブラウザー ウィンドウには、特定のメッセージを通知します。つまり、SignalR では、1 つのプールを自動的にすべて接続同じ認証されたユーザーからをグループ化する機能があります。このため、複数のデスクトップ ブラウザー、アプリ内の Web ビュー、モバイル ブラウザー、デスクトップ クライアントまたは他にどんなから来たかどうか、ユーザーに関連するすべてのウィンドウにメッセージをブロードキャストする能力のある SendAsync ユーザーから呼び出されます。コードは次のようになります。

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

同じユーザー名の下のすべての接続に refreshUI メッセージを送信する、すべての開かれている windows 追加フレンド関数に同期的になります。サンプルのソース コードで同じことが起こります現在ログイン ユーザー、リストから、友人を削除することがわかります。

ユーザーのクライアント プロキシを構成します。

Signalr では、ユーザー オブジェクトが HTTP コンテキストに関連付けられているユーザー オブジェクトとは無関係です。同じプロパティ名であるにもかかわらず、SignalR ユーザー オブジェクトは、クライアント プロキシと要求のコンテナーです。まだ、SignalR のユーザー固有のクライアント プロキシと、認証されたユーザーを表すオブジェクトの間に微妙なリレーションシップが存在します。気付き、ユーザーのクライアント プロキシ文字列パラメーターが必要です。コードで図 3、文字列パラメーターは、現在ログイン ユーザーの名前。文字列識別子だけでありで何もするように構成することができます。

既定では、ユーザーのクライアント プロキシによって認識されるユーザー ID は、NameIdentifier 要求の値です。認証されたユーザーのクレームのリストには、NameIdentifier が含まれていない場合、ブロードキャストを行う可能性はありません。したがって 2 つのオプションがあります。1 つは、認証クッキーを作成するときに、NameIdentifier 要求を追加して、独自 SignalR ユーザーの ID プロバイダーを作成するには、その他。NameIdentifier 要求を追加するには、ログイン プロセスでは、次のコードが必要です。

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

NameIdentifier 要求に割り当てられた値は、各ユーザーに一意である限りかまいません。内部的には、SignalR の使用、現在ログインしているユーザーをルートとする、IUserIdProvider コンポーネント接続グループにユーザー ID と一致するようになります。

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 クラスは、ユーザーに固有の接続 Id をグループ化するのに NameIdentifier 要求の値を使用します。名前要求は、ユーザーの名前を示すには必ずしもシステム内のユーザーを識別する一意の識別子を提供する設計されています。代わりに、NameIdentifier 要求、は、GUID、文字列または整数かどうかは、一意の値を保持しています。NameIdentifer ではなくユーザーに切り替えた場合は、名前に割り当てられている任意の値はユーザーごとに一意を確認します。

一致する名前の識別子を持つアカウントから送信されるすべての接続は、グループ化とユーザーのクライアント プロキシを使用すると、自動的に通知されます。正規の名前クレームの使用に切り替えるには、必要があります、カスタムの IUserIdProvider とおり。

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

もちろん、このコンポーネントは、起動フェーズ中に DI インフラストラクチャを登録する必要があります。スタートアップ クラスの ConfigureServices メソッドを追加するコードを次に示します。

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

この時点では、一致するすべてのユーザー ウィンドウの同じ状態およびビューで同期させるすべて設定されます。バルーン形式通知いかがでしょうか。

最後の仕上げ

追加と削除の友人の現在のユーザーを表示する更新の通知は、インデックス ページに送信される必要があます。特定のユーザーは、同じアプリケーション (インデックスと別のページ) のさまざまなページで開かれた 2 つのブラウザー ウィンドウがある、彼女とインデックス ページに対してのみ更新通知が表示されます。ただし、追加し削除の友人も原因は追加し、追加またはフレンド関係のリストから削除されたユーザーに送信される通知を削除します。たとえば、ユーザー Dino 友人の一覧から、Mary のユーザーを削除する場合、Mary のユーザーは削除通知を受け取るも。削除 (または追加) の通知が、ページが表示されているかどうかに関係なく、ユーザーに到達する必要があります理想的には、インデックス、またはその他。

これを実現するには、2 つのオプションがあります。

  • レイアウト ページに移動し、そのレイアウトに基づくすべてのページに継承し、接続の設定では、1 つの SignalR ハブを使用します。
  • 2 つの異なるハブを使用して、追加または削除、友人の後に、UI の更新用に 1 つと通知する 1 つを追加またはユーザーを削除します。

個別のハブを使用する場合は、追加/削除の通知ハブようを設定するすべてのページに表示される通知先、ほとんどの場合、レイアウト ページにあるアプリケーション。

サンプル アプリケーションでは、レイアウト ページで完全に設定する 1 つのハブを使用します。現在の接続を参照する JavaScript オブジェクトがグローバルに SignalR の初期化コードが適切であるため、クライアント内で共有されることに注意してくださいでは、Razor の RenderBody セクションの前に、レイアウトの本文の上部に配置されます。

コント ローラーの追加メソッドの最後のコードを見てみましょう。このメソッドはどこでフォーム図 1最終的に投稿します。メソッドは、フレンド関係を保存するバック エンドで必要な変更を行ったし、SignalR の 2 つのメッセージを発行-視覚的に追加 (または削除された) ユーザーに通知して、操作を実行する現在のユーザーの友人のリストを更新する 1 つ。ユーザーには、彼女が現在接続されている場合、アプリケーションとを受け取り、そのような特定の通知を処理するように構成ページから実際に通知されます。図 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();
}

バルーン形式の通知 (またはようにする UI の調整の種類を問わず)、レイアウト ページ、JavaScript コードが表示されます。サンプル アプリケーションでは、通知は、コードをここに記載されている最大で 10 秒間、通知バーに表示されるメッセージの形式をとります。

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

結果を示している図 5します。

ユーザー間の通知
図 5 ユーザー間の通知

SignalR のグループについての注意

この記事では、関連するユーザーのグループに送信される、選択した通知の SignalR のサポートを取り上げました。SignalR は、いくつかの方法を提供しています — クライアント プロキシのユーザーとグループ。違いは微妙です。ユーザーのクライアント プロキシでは、グループ、名前、ユーザー ID によって決定され、メンバーは、同じアプリケーションのユーザーによって開かれているすべての接続数が暗黙的に生成します。グループは、プログラムで接続を論理グループに追加される一般的なメカニズムです。接続とグループの名前の両方がプログラムによって設定されます。

これら両方のアプローチにバルーン形式の通知が実装されている可能性がありますが、この特定のシナリオ、ユーザーのクライアント プロキシが最も自然なソリューションをでした。ソース コードをご覧bit.ly/2HVyLp5します。


Dino Esposito は、25 年におよぶキャリアの中で、20 冊以上の書籍と 1000 本を超える記事を執筆してきました。劇形式の作品『The Sabbatical Break』の著者である Esposito は、BaxEnergy のデジタル ストラテジストとして、より良い世界を構築するためにソフトウェアの記述に取り組んでいます。彼には Twitter (@despos、英語) から連絡できます。


この記事について MSDN マガジン フォーラムで議論する