ASP.NET Core 用 SignalR 内で MessagePack ハブ プロトコルを使用する

この記事では、読者が「ASP.NET Core SignalR の概要」に記載されているトピックを理解していることを前提としています。

MessagePack とは

MessagePack は、高速でコンパクトなバイナリ シリアル化形式です。 これにより JSON よりもサイズの小さいメッセージが作成されるため、パフォーマンスと帯域幅が問題になる場合に役立ちます。 ネットワーク トレースとログを参照する場合、バイナリ メッセージは、MessagePack パーサーを通じてバイトが渡されない限り読み取ることができません。 SignalR には MessagePack 形式のサポートが組み込まれており、クライアントとサーバーが使用できる API が用意されています。

サーバー上で MessagePack を構成する

サーバー上で MessagePack ハブ プロトコルを有効にするには、アプリに Microsoft.AspNetCore.SignalR.Protocols.MessagePack パッケージをインストールします。 Startup.ConfigureServices メソッド内で、AddMessagePackProtocolAddSignalR 呼び出しに追加して、サーバー上で MessagePack のサポートを有効にします。

services.AddSignalR()
    .AddMessagePackProtocol();

Note

JSON は既定で有効になります。 MessagePack を追加すると、JSON と MessagePack クライアントの両方のサポートが有効になります。

MessagePack によってデータが書式設定される方法をカスタマイズするには、AddMessagePackProtocol でオプションを構成するためのデリゲートを受け取ります。 このデリゲートでは、SerializerOptions プロパティを使用して MessagePack のシリアル化オプションを構成します。 リゾルバーの動作の詳細については、MessagePack-CSharp にある MessagePack ライブラリをご覧ください。 シリアル化するオブジェクトに対して属性を使用して、それらをどのように処理するかを定義できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(new CustomResolver())
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

警告

CVE-2020-5234 を確認し、お勧めのパッチを適用することを強くお勧めします。 たとえば、SerializerOptions を置換するときに .WithSecurity(MessagePackSecurity.UntrustedData) を呼び出します。

クライアント上で MessagePack を構成する

Note

JSON は、サポートされるクライアントに対して既定で有効になっています。 クライアントでサポートできるプロトコルは 1 つのみです。 MessagePack サポートを追加すると、以前に構成したプロトコルが置き換えられます。

.NET クライアント

.NET クライアント内で MessagePack を有効にするには、Microsoft.AspNetCore.SignalR.Protocols.MessagePackパッケージをインストールし、HubConnectionBuilderAddMessagePackProtocol を呼び出します。

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Note

この AddMessagePackProtocol 呼び出しは、サーバーと同じように、オプションを構成するためのデリゲートを受け取ります。

JavaScript クライアント

JavaScript クライアントに対する MessagePack のサポートは、@microsoft/signalr-protocol-msgpack npm パッケージで提供されます。 コマンド シェルで次のコマンドを実行して、パッケージをインストールします。

npm install @microsoft/signalr-protocol-msgpack

npm パッケージをインストールした後、モジュールは JavaScript モジュール ローダーを介して直接使用することも、次のファイルを参照してブラウザーにインポートすることもできます。

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

次の必須の javaScript ファイルは、以下に示す順序で参照する必要があります。

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

.withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())HubConnectionBuilder に追加すると、サーバーに接続するときに MessagePack プロトコルを使用するようにクライアントが構成されます。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

現時点では、JavaScript クライアントの MessagePack プロトコルの構成オプションはありません。

Java クライアント

Java で MessagePack を有効にするには、com.microsoft.signalr.messagepack パッケージをインストールします。 Gradle を使用する場合は、build.gradle ファイルの dependencies セクションに次の行を追加します。

implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.0'

Maven を使用する場合は、pom.xml ファイルの <dependencies> 要素内に次の行を追加します。

<dependency>
    <groupId>com.microsoft.signalr.messagepack</groupId>
    <artifactId>signalr</artifactId>
    <version>5.0.0</version>
</dependency>

HubConnectionBuilderwithHubProtocol(new MessagePackHubProtocol()) を呼び出します。

HubConnection messagePackConnection = HubConnectionBuilder.create("YOUR HUB URL HERE")
    .withHubProtocol(new MessagePackHubProtocol())
    .build();

MessagePack に関する考慮事項

MessagePack ハブ プロトコルを使用する場合に注意が必要な問題がいくつかあります。

MessagePack では大文字と小文字が区別される

MessagePack プロトコルでは大文字と小文字が区別されます。 たとえば、次の C# クラスについて考えてみます。

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

JavaScript クライアントから送信する場合は、大文字と小文字が C# クラスと正確に一致する必要があるため、PascalCased プロパティ名を使用する必要があります。 次に例を示します。

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

camelCased の名前を使用すると、C# クラスに適切にバインドされません。 この問題を回避するには、Key 属性を使用して、MessagePack プロパティに別の名前を指定します。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。

シリアル化/逆シリアル化時には DateTime.Kind が保持されない

MessagePack プロトコルには、DateTimeKind 値をエンコードする方法が用意されていません。 その結果、日付を逆シリアル化する際に、DateTime.KindDateTimeKind.Local の場合は MessagePack ハブ プロトコルによって UTC 形式に変換されます。それ以外の場合、時刻は修正されずにそのまま渡されます。 DateTime 値を操作する場合は、送信する前に UTC に変換することをお勧めします。 これらを受信時に UTC からローカル時刻に変換します。

"ahead-of-time" コンパイル環境での MessagePack のサポート

.NET クライアントとサーバーによって使用される MessagePack-CSharp ライブラリでは、コード生成を使用してシリアル化が最適化されます。 その結果、"ahead-of-time" コンパイル (Xamarin iOS や Unity など) を使用する環境では、既定でサポートされません。 これらの環境では、シリアライザー/逆シリアライザー コードを "事前に生成する" ことで MessagePack を使用できます。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。 シリアライザーを事前に生成したら、AddMessagePackProtocol に渡された構成デリゲートを使用してそれらを登録できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(StaticCompositeResolver.Instance)
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

MessagePack での型チェックはより厳しい

JSON ハブ プロトコルでは、逆シリアル化中に型変換が実行されます。 たとえば、受信したオブジェクトのプロパティ値が数値 ({ foo: 42 }) であっても、.NET クラスのプロパティの型が string であれば、値が変換されます。 一方、MessagePack ではこの変換は実行されず、サーバー側のログ内 (およびコンソール内) で確認できる例外がスローされます。

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

この制限の詳細については、GitHub イシュー aspnet/SignalR#2937 をご覧ください。

Java での文字と文字列

Java クライアントでは、char オブジェクトは 1 文字の String オブジェクトとしてシリアル化されます。 これは、short オブジェクトとしてシリアル化する C# および JavaScript クライアントとは対照的です。 MessagePack の仕様自体では char オブジェクトの動作が定義されていないため、ライブラリの作成者がシリアル化の方法を決定します。 クライアント間の動作の違いは、実装に使用したライブラリの結果です。

その他のリソース

この記事では、読者が「ASP.NET Core SignalR の概要」に記載されているトピックを理解していることを前提としています。

MessagePack とは

MessagePack は、高速でコンパクトなバイナリ シリアル化形式です。 これにより JSON に比べてサイズの小さいメッセージが作成されるため、パフォーマンスと帯域幅が問題になる場合に役立ちます。 ネットワーク トレースとログを参照する場合、バイナリ メッセージは、MessagePack パーサーを通じてバイトが渡されない限り読み取ることができません。 SignalR には MessagePack 形式のサポートが組み込まれており、クライアントとサーバーが使用できる API が用意されています。

サーバー上で MessagePack を構成する

サーバー上で MessagePack ハブ プロトコルを有効にするには、アプリに Microsoft.AspNetCore.SignalR.Protocols.MessagePack パッケージをインストールします。 Startup.ConfigureServices メソッド内で、AddMessagePackProtocolAddSignalR 呼び出しに追加して、サーバー上で MessagePack のサポートを有効にします。

Note

JSON は既定で有効になります。 MessagePack を追加すると、JSON と MessagePack クライアントの両方のサポートが有効になります。

services.AddSignalR()
    .AddMessagePackProtocol();

MessagePack によってデータが書式設定される方法をカスタマイズするには、AddMessagePackProtocol でオプションを構成するためのデリゲートを使用します。 このデリゲートでは、SerializerOptions プロパティを使用して MessagePack のシリアル化オプションを構成できます。 リゾルバーの動作の詳細については、MessagePack-CSharp にある MessagePack ライブラリをご覧ください。 シリアル化するオブジェクトに対して属性を使用して、それらをどのように処理するかを定義できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(new CustomResolver())
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

警告

CVE-2020-5234 を確認し、お勧めのパッチを適用することを強くお勧めします。 たとえば、SerializerOptions を置換するときに .WithSecurity(MessagePackSecurity.UntrustedData) を呼び出します。

クライアント上で MessagePack を構成する

Note

JSON は、サポートされるクライアントに対して既定で有効になっています。 クライアントでサポートできるプロトコルは 1 つのみです。 MessagePack サポートを追加すると、以前に構成したプロトコルが置き換えられます。

.NET クライアント

.NET クライアント内で MessagePack を有効にするには、Microsoft.AspNetCore.SignalR.Protocols.MessagePackパッケージをインストールし、HubConnectionBuilderAddMessagePackProtocol を呼び出します。

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Note

この AddMessagePackProtocol 呼び出しは、サーバーと同じように、オプションを構成するためのデリゲートを受け取ります。

JavaScript クライアント

JavaScript クライアントに対する MessagePack のサポートは、@microsoft/signalr-protocol-msgpack npm パッケージで提供されます。 コマンド シェルで次のコマンドを実行して、パッケージをインストールします。

npm install @microsoft/signalr-protocol-msgpack

npm パッケージをインストールした後、モジュールは JavaScript モジュール ローダーを介して直接使用することも、次のファイルを参照してブラウザーにインポートすることもできます。

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

ブラウザーで、msgpack5 ライブラリも参照する必要があります。 <script> タグを使用して参照を作成します。 ライブラリは、node_modules\msgpack5\dist\msgpack5.js にあります。

Note

<script> 要素を使用する場合は、順序が重要です。 signalr-protocol-msgpack.jsmsgpack5.js より前に参照されている場合、MessagePack に接続しようとするとエラーが発生します。 signalr.jssignalr-protocol-msgpack.js の前にも必要です。

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

.withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())HubConnectionBuilder に追加すると、サーバーに接続するときに MessagePack プロトコルを使用するようにクライアントが構成されます。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

Note

現時点では、JavaScript クライアントの MessagePack プロトコルの構成オプションはありません。

Java クライアント

Java で MessagePack を有効にするには、com.microsoft.signalr.messagepack パッケージをインストールします。 Gradle を使用する場合は、build.gradle ファイルの dependencies セクションに次の行を追加します。

implementation 'com.microsoft.signalr.messagepack:signalr-messagepack:5.0.0'

Maven を使用する場合は、pom.xml ファイルの <dependencies> 要素内に次の行を追加します。

<dependency>
    <groupId>com.microsoft.signalr.messagepack</groupId>
    <artifactId>signalr</artifactId>
    <version>5.0.0</version>
</dependency>

HubConnectionBuilderwithHubProtocol(new MessagePackHubProtocol()) を呼び出します。

HubConnection messagePackConnection = HubConnectionBuilder.create("YOUR HUB URL HERE")
    .withHubProtocol(new MessagePackHubProtocol())
    .build();

MessagePack に関する考慮事項

MessagePack ハブ プロトコルを使用する場合に注意が必要な問題がいくつかあります。

MessagePack では大文字と小文字が区別される

MessagePack プロトコルでは大文字と小文字が区別されます。 たとえば、次の C# クラスについて考えてみます。

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

JavaScript クライアントから送信する場合は、大文字と小文字が C# クラスと正確に一致する必要があるため、PascalCased プロパティ名を使用する必要があります。 次に例を示します。

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

camelCased の名前を使用すると、C# クラスに適切にバインドされません。 この問題を回避するには、Key 属性を使用して、MessagePack プロパティに別の名前を指定します。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。

シリアル化/逆シリアル化時には DateTime.Kind が保持されない

MessagePack プロトコルには、DateTimeKind 値をエンコードする方法が用意されていません。 その結果、日付を逆シリアル化する際に、DateTime.KindDateTimeKind.Local の場合は MessagePack ハブ プロトコルによって UTC 形式に変換されます。それ以外の場合、時刻は修正されずにそのまま渡されます。 DateTime 値を操作する場合は、送信する前に UTC に変換することをお勧めします。 これらを受信時に UTC からローカル時刻に変換します。

DateTime.MinValue は、JavaScript の MessagePack ではサポートされていない

SignalR JavaScript クライアントで使用される msgpack5 ライブラリでは、MessagePack の timestamp96 型はサポートされません。 この型は、とても大きな日付値 (遠い過去または遠い未来) をエンコードするために使用されます。 DateTime.MinValue の値は January 1, 0001 です。これは、timestamp96 値でエンコードする必要があります。 このため、JavaScript クライアントへの DateTime.MinValue の送信はサポートされません。 DateTime.MinValue が JavaScript クライアントによって受信された場合は、次のエラーがスローされます。

Uncaught Error: unable to find ext type 255 at decoder.js:427

通常、DateTime.MinValue は "欠落" または null 値をエンコードするために使用されます。 MessagePack でその値をエンコードする必要がある場合は、Null 許容の DateTime 値 (DateTime?) を使用するか、日付が存在するかどうかを示す別の bool 値をエンコードします。

この制限の詳細については、GitHub イシュー aspnet/SignalR#2228 をご覧ください。

"ahead-of-time" コンパイル環境での MessagePack のサポート

.NET クライアントとサーバーによって使用される MessagePack-CSharp ライブラリでは、コード生成を使用してシリアル化が最適化されます。 その結果、"ahead-of-time" コンパイル (Xamarin iOS や Unity など) を使用する環境では、既定でサポートされません。 これらの環境では、シリアライザー/逆シリアライザー コードを "事前に生成する" ことで MessagePack を使用できます。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。 シリアライザーを事前に生成したら、AddMessagePackProtocol に渡された構成デリゲートを使用してそれらを登録できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(StaticCompositeResolver.Instance)
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

MessagePack での型チェックはより厳しい

JSON ハブ プロトコルでは、逆シリアル化中に型変換が実行されます。 たとえば、受信したオブジェクトのプロパティ値が数値 ({ foo: 42 }) であっても、.NET クラスのプロパティの型が string であれば、値が変換されます。 一方、MessagePack ではこの変換は実行されず、サーバー側のログ内 (およびコンソール内) で確認できる例外がスローされます。

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

この制限の詳細については、GitHub イシュー aspnet/SignalR#2937 をご覧ください。

Java での文字と文字列

Java クライアントでは、char オブジェクトは 1 文字の String オブジェクトとしてシリアル化されます。 これは、short オブジェクトとしてシリアル化する C# および JavaScript クライアントとは対照的です。 MessagePack の仕様自体では char オブジェクトの動作が定義されていないため、ライブラリの作成者がシリアル化の方法を決定します。 クライアント間の動作の違いは、実装に使用したライブラリの結果です。

その他のリソース

この記事では、読者が「ASP.NET Core SignalR の概要」に記載されているトピックを理解していることを前提としています。

MessagePack とは

MessagePack は、高速でコンパクトなバイナリ シリアル化形式です。 これにより JSON に比べてサイズの小さいメッセージが作成されるため、パフォーマンスと帯域幅が問題になる場合に役立ちます。 ネットワーク トレースとログを参照する場合、バイナリ メッセージは、MessagePack パーサーを通じてバイトが渡されない限り読み取ることができません。 SignalR には MessagePack 形式のサポートが組み込まれており、クライアントとサーバーが使用できる API が用意されています。

サーバー上で MessagePack を構成する

サーバー上で MessagePack ハブ プロトコルを有効にするには、アプリに Microsoft.AspNetCore.SignalR.Protocols.MessagePack パッケージをインストールします。 Startup.ConfigureServices メソッド内で、AddMessagePackProtocolAddSignalR 呼び出しに追加して、サーバー上で MessagePack のサポートを有効にします。

Note

JSON は既定で有効になります。 MessagePack を追加すると、JSON と MessagePack クライアントの両方のサポートが有効になります。

services.AddSignalR()
    .AddMessagePackProtocol();

MessagePack によってデータが書式設定される方法をカスタマイズするには、AddMessagePackProtocol でオプションを構成するためのデリゲートを使用します。 このデリゲートでは、FormatterResolvers プロパティを使用して MessagePack のシリアル化オプションを構成できます。 リゾルバーの動作の詳細については、MessagePack-CSharp にある MessagePack ライブラリをご覧ください。 シリアル化するオブジェクトに対して属性を使用して、それらをどのように処理するかを定義できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

警告

CVE-2020-5234 を確認し、お勧めのパッチを適用することを強くお勧めします。 たとえば、MessagePackSecurity.Active 静的プロパティを MessagePackSecurity.UntrustedData に設定します。 MessagePackSecurity.Active を設定するには、1.9.x バージョンの MessagePack を手動でインストールする必要があります。 MessagePack 1.9.x をインストールすると、SignalR で使用されるバージョンがアップグレードされます。 MessagePack バージョン 2.x では破壊的変更が導入され、SignalR バージョン 3.1 以前と互換性がありません。 MessagePackSecurity.ActiveMessagePackSecurity.UntrustedData に設定されていない場合、悪意のあるクライアントによってサービス拒否が発生する可能性があります。 次のコードに示すように、Program.MainMessagePackSecurity.Active を設定します。

using MessagePack;

public static void Main(string[] args)
{
  MessagePackSecurity.Active = MessagePackSecurity.UntrustedData;

  CreateHostBuilder(args).Build().Run();
}

クライアント上で MessagePack を構成する

Note

JSON は、サポートされるクライアントに対して既定で有効になっています。 クライアントでサポートできるプロトコルは 1 つのみです。 MessagePack サポートを追加すると、以前に構成したプロトコルが置き換えられます。

.NET クライアント

.NET クライアント内で MessagePack を有効にするには、Microsoft.AspNetCore.SignalR.Protocols.MessagePackパッケージをインストールし、HubConnectionBuilderAddMessagePackProtocol を呼び出します。

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Note

この AddMessagePackProtocol 呼び出しは、サーバーと同じように、オプションを構成するためのデリゲートを受け取ります。

JavaScript クライアント

JavaScript クライアントに対する MessagePack のサポートは、@microsoft/signalr-protocol-msgpack npm パッケージで提供されます。 コマンド シェルで次のコマンドを実行して、パッケージをインストールします。

npm install @microsoft/signalr-protocol-msgpack

npm パッケージをインストールした後、モジュールは JavaScript モジュール ローダーを介して直接使用することも、次のファイルを参照してブラウザーにインポートすることもできます。

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

ブラウザーで、msgpack5 ライブラリも参照する必要があります。 <script> タグを使用して参照を作成します。 ライブラリは、node_modules\msgpack5\dist\msgpack5.js にあります。

Note

<script> 要素を使用する場合は、順序が重要です。 signalr-protocol-msgpack.jsmsgpack5.js より前に参照されている場合、MessagePack に接続しようとするとエラーが発生します。 signalr.jssignalr-protocol-msgpack.js の前にも必要です。

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

.withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())HubConnectionBuilder に追加すると、サーバーに接続するときに MessagePack プロトコルを使用するようにクライアントが構成されます。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

Note

現時点では、JavaScript クライアントの MessagePack プロトコルの構成オプションはありません。

MessagePack に関する考慮事項

MessagePack ハブ プロトコルを使用する場合に注意が必要な問題がいくつかあります。

MessagePack では大文字と小文字が区別される

MessagePack プロトコルでは大文字と小文字が区別されます。 たとえば、次の C# クラスについて考えてみます。

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

JavaScript クライアントから送信する場合は、大文字と小文字が C# クラスと正確に一致する必要があるため、PascalCased プロパティ名を使用する必要があります。 次に例を示します。

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

camelCased の名前を使用すると、C# クラスに適切にバインドされません。 この問題を回避するには、Key 属性を使用して、MessagePack プロパティに別の名前を指定します。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。

シリアル化/逆シリアル化時には DateTime.Kind が保持されない

MessagePack プロトコルには、DateTimeKind 値をエンコードする方法が用意されていません。 その結果、MessagePack ハブ プロトコルでは、日付を逆シリアル化するときに、受信日が UTC 形式であると見なされます。 DateTime 値をローカル時刻で操作する場合は、送信する前に UTC に変換することをお勧めします。 これらを受信時に UTC からローカル時刻に変換します。

この制限の詳細については、GitHub イシュー aspnet/SignalR#2632 をご覧ください。

DateTime.MinValue は、JavaScript の MessagePack ではサポートされていない

SignalR JavaScript クライアントで使用される msgpack5 ライブラリでは、MessagePack の timestamp96 型はサポートされません。 この型は、とても大きな日付値 (遠い過去または遠い未来) をエンコードするために使用されます。 DateTime.MinValue の値は January 1, 0001 です。これは、timestamp96 値でエンコードする必要があります。 このため、JavaScript クライアントへの DateTime.MinValue の送信はサポートされません。 DateTime.MinValue が JavaScript クライアントによって受信された場合は、次のエラーがスローされます。

Uncaught Error: unable to find ext type 255 at decoder.js:427

通常、DateTime.MinValue は "欠落" または null 値をエンコードするために使用されます。 MessagePack でその値をエンコードする必要がある場合は、Null 許容の DateTime 値 (DateTime?) を使用するか、日付が存在するかどうかを示す別の bool 値をエンコードします。

この制限の詳細については、GitHub イシュー aspnet/SignalR#2228 をご覧ください。

"ahead-of-time" コンパイル環境での MessagePack のサポート

.NET クライアントとサーバーによって使用される MessagePack-CSharp ライブラリでは、コード生成を使用してシリアル化が最適化されます。 その結果、"ahead-of-time" コンパイル (Xamarin iOS や Unity など) を使用する環境では、既定でサポートされません。 これらの環境では、シリアライザー/逆シリアライザー コードを "事前に生成する" ことで MessagePack を使用できます。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。 シリアライザーを事前に生成したら、AddMessagePackProtocol に渡された構成デリゲートを使用してそれらを登録できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

MessagePack での型チェックはより厳しい

JSON ハブ プロトコルでは、逆シリアル化中に型変換が実行されます。 たとえば、受信したオブジェクトのプロパティ値が数値 ({ foo: 42 }) であっても、.NET クラスのプロパティの型が string であれば、値が変換されます。 一方、MessagePack ではこの変換は実行されず、サーバー側のログ内 (およびコンソール内) で確認できる例外がスローされます。

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

この制限の詳細については、GitHub イシュー aspnet/SignalR#2937 をご覧ください。

その他のリソース

この記事では、読者が「ASP.NET Core SignalR の概要」に記載されているトピックを理解していることを前提としています。

MessagePack とは

MessagePack は、高速でコンパクトなバイナリ シリアル化形式です。 これにより JSON に比べてサイズの小さいメッセージが作成されるため、パフォーマンスと帯域幅が問題になる場合に役立ちます。 ネットワーク トレースとログを参照する場合、バイナリ メッセージは、MessagePack パーサーを通じてバイトが渡されない限り読み取ることができません。 SignalR には MessagePack 形式のサポートが組み込まれており、クライアントとサーバーが使用できる API が用意されています。

サーバー上で MessagePack を構成する

サーバー上で MessagePack ハブ プロトコルを有効にするには、アプリに Microsoft.AspNetCore.SignalR.Protocols.MessagePack パッケージをインストールします。 Startup.ConfigureServices メソッド内で、AddMessagePackProtocolAddSignalR 呼び出しに追加して、サーバー上で MessagePack のサポートを有効にします。

Note

JSON は既定で有効になります。 MessagePack を追加すると、JSON と MessagePack クライアントの両方のサポートが有効になります。

services.AddSignalR()
    .AddMessagePackProtocol();

MessagePack によってデータが書式設定される方法をカスタマイズするには、AddMessagePackProtocol でオプションを構成するためのデリゲートを使用します。 このデリゲートでは、FormatterResolvers プロパティを使用して MessagePack のシリアル化オプションを構成できます。 リゾルバーの動作の詳細については、MessagePack-CSharp にある MessagePack ライブラリをご覧ください。 シリアル化するオブジェクトに対して属性を使用して、それらをどのように処理するかを定義できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

警告

CVE-2020-5234 を確認し、お勧めのパッチを適用することを強くお勧めします。 たとえば、MessagePackSecurity.Active 静的プロパティを MessagePackSecurity.UntrustedData に設定します。 MessagePackSecurity.Active を設定するには、1.9.x バージョンの MessagePack を手動でインストールする必要があります。 MessagePack 1.9.x をインストールすると、SignalR で使用されるバージョンがアップグレードされます。 MessagePackSecurity.ActiveMessagePackSecurity.UntrustedData に設定されていない場合、悪意のあるクライアントによってサービス拒否が発生する可能性があります。 次のコードに示すように、Program.MainMessagePackSecurity.Active を設定します。

using MessagePack;

public static void Main(string[] args)
{
  MessagePackSecurity.Active = MessagePackSecurity.UntrustedData;

  CreateHostBuilder(args).Build().Run();
}

クライアント上で MessagePack を構成する

Note

JSON は、サポートされるクライアントに対して既定で有効になっています。 クライアントでサポートできるプロトコルは 1 つのみです。 MessagePack サポートを追加すると、以前に構成したプロトコルが置き換えられます。

.NET クライアント

.NET クライアント内で MessagePack を有効にするには、Microsoft.AspNetCore.SignalR.Protocols.MessagePackパッケージをインストールし、HubConnectionBuilderAddMessagePackProtocol を呼び出します。

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chathub")
                        .AddMessagePackProtocol()
                        .Build();

Note

この AddMessagePackProtocol 呼び出しは、サーバーと同じように、オプションを構成するためのデリゲートを受け取ります。

JavaScript クライアント

JavaScript クライアントに対する MessagePack のサポートは、@aspnet/signalr-protocol-msgpack npm パッケージで提供されます。 コマンド シェルで次のコマンドを実行して、パッケージをインストールします。

npm install @aspnet/signalr-protocol-msgpack

npm パッケージをインストールした後、モジュールは JavaScript モジュール ローダーを介して直接使用することも、次のファイルを参照してブラウザーにインポートすることもできます。

node_modules\@aspnet\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

ブラウザーで、msgpack5 ライブラリも参照する必要があります。 <script> タグを使用して参照を作成します。 ライブラリは、node_modules\msgpack5\dist\msgpack5.js にあります。

Note

<script> 要素を使用する場合は、順序が重要です。 signalr-protocol-msgpack.jsmsgpack5.js より前に参照されている場合、MessagePack に接続しようとするとエラーが発生します。 signalr.jssignalr-protocol-msgpack.js の前にも必要です。

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

.withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())HubConnectionBuilder に追加すると、サーバーに接続するときに MessagePack プロトコルを使用するようにクライアントが構成されます。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

Note

現時点では、JavaScript クライアントの MessagePack プロトコルの構成オプションはありません。

MessagePack に関する考慮事項

MessagePack ハブ プロトコルを使用する場合に注意が必要な問題がいくつかあります。

MessagePack では大文字と小文字が区別される

MessagePack プロトコルでは大文字と小文字が区別されます。 たとえば、次の C# クラスについて考えてみます。

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

JavaScript クライアントから送信する場合は、大文字と小文字が C# クラスと正確に一致する必要があるため、PascalCased プロパティ名を使用する必要があります。 次に例を示します。

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

camelCased の名前を使用すると、C# クラスに適切にバインドされません。 この問題を回避するには、Key 属性を使用して、MessagePack プロパティに別の名前を指定します。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。

シリアル化/逆シリアル化時には DateTime.Kind が保持されない

MessagePack プロトコルには、DateTimeKind 値をエンコードする方法が用意されていません。 その結果、MessagePack ハブ プロトコルでは、日付を逆シリアル化するときに、受信日が UTC 形式であると見なされます。 DateTime 値をローカル時刻で操作する場合は、送信する前に UTC に変換することをお勧めします。 これらを受信時に UTC からローカル時刻に変換します。

この制限の詳細については、GitHub イシュー aspnet/SignalR#2632 をご覧ください。

DateTime.MinValue は、JavaScript の MessagePack ではサポートされていない

SignalR JavaScript クライアントで使用される msgpack5 ライブラリでは、MessagePack の timestamp96 型はサポートされません。 この型は、とても大きな日付値 (遠い過去または遠い未来) をエンコードするために使用されます。 DateTime.MinValue の値は January 1, 0001 です。これは、timestamp96 値でエンコードする必要があります。 このため、JavaScript クライアントへの DateTime.MinValue の送信はサポートされません。 DateTime.MinValue が JavaScript クライアントによって受信された場合は、次のエラーがスローされます。

Uncaught Error: unable to find ext type 255 at decoder.js:427

通常、DateTime.MinValue は "欠落" または null 値をエンコードするために使用されます。 MessagePack でその値をエンコードする必要がある場合は、Null 許容の DateTime 値 (DateTime?) を使用するか、日付が存在するかどうかを示す別の bool 値をエンコードします。

この制限の詳細については、GitHub イシュー aspnet/SignalR#2228 をご覧ください。

"ahead-of-time" コンパイル環境での MessagePack のサポート

.NET クライアントとサーバーによって使用される MessagePack-CSharp ライブラリでは、コード生成を使用してシリアル化が最適化されます。 その結果、"ahead-of-time" コンパイル (Xamarin iOS や Unity など) を使用する環境では、既定でサポートされません。 これらの環境では、シリアライザー/逆シリアライザー コードを "事前に生成する" ことで MessagePack を使用できます。 詳しくは、MessagePack-CSharp に関するドキュメントをご覧ください。 シリアライザーを事前に生成したら、AddMessagePackProtocol に渡された構成デリゲートを使用してそれらを登録できます。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

MessagePack での型チェックはより厳しい

JSON ハブ プロトコルでは、逆シリアル化中に型変換が実行されます。 たとえば、受信したオブジェクトのプロパティ値が数値 ({ foo: 42 }) であっても、.NET クラスのプロパティの型が string であれば、値が変換されます。 一方、MessagePack ではこの変換は実行されず、サーバー側のログ内 (およびコンソール内) で確認できる例外がスローされます。

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

この制限の詳細については、GitHub イシュー aspnet/SignalR#2937 をご覧ください。

その他のリソース