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
メソッド内で、AddMessagePackProtocol
を AddSignalR
呼び出しに追加して、サーバー上で 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
パッケージをインストールし、HubConnectionBuilder
で AddMessagePackProtocol
を呼び出します。
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>
HubConnectionBuilder
で withHubProtocol(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 プロトコルには、DateTime
の Kind
値をエンコードする方法が用意されていません。 その結果、日付を逆シリアル化する際に、DateTime.Kind
が DateTimeKind.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
メソッド内で、AddMessagePackProtocol
を AddSignalR
呼び出しに追加して、サーバー上で 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
パッケージをインストールし、HubConnectionBuilder
で AddMessagePackProtocol
を呼び出します。
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.js
が msgpack5.js
より前に参照されている場合、MessagePack に接続しようとするとエラーが発生します。 signalr.js
は signalr-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>
HubConnectionBuilder
で withHubProtocol(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 プロトコルには、DateTime
の Kind
値をエンコードする方法が用意されていません。 その結果、日付を逆シリアル化する際に、DateTime.Kind
が DateTimeKind.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
メソッド内で、AddMessagePackProtocol
を AddSignalR
呼び出しに追加して、サーバー上で 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.Active
が MessagePackSecurity.UntrustedData
に設定されていない場合、悪意のあるクライアントによってサービス拒否が発生する可能性があります。 次のコードに示すように、Program.Main
に MessagePackSecurity.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
パッケージをインストールし、HubConnectionBuilder
で AddMessagePackProtocol
を呼び出します。
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.js
が msgpack5.js
より前に参照されている場合、MessagePack に接続しようとするとエラーが発生します。 signalr.js
は signalr-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 プロトコルには、DateTime
の Kind
値をエンコードする方法が用意されていません。 その結果、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
メソッド内で、AddMessagePackProtocol
を AddSignalR
呼び出しに追加して、サーバー上で 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.Active
が MessagePackSecurity.UntrustedData
に設定されていない場合、悪意のあるクライアントによってサービス拒否が発生する可能性があります。 次のコードに示すように、Program.Main
に MessagePackSecurity.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
パッケージをインストールし、HubConnectionBuilder
で AddMessagePackProtocol
を呼び出します。
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.js
が msgpack5.js
より前に参照されている場合、MessagePack に接続しようとするとエラーが発生します。 signalr.js
は signalr-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 プロトコルには、DateTime
の Kind
値をエンコードする方法が用意されていません。 その結果、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