.NET 6.0 の Windowsフォームアプリから、MagicOnion NuGetパッケージを使い、gRPC通信処理を行う 前回作成したサンプル は、通信用Modelクラスのメンバに使っている型によっては、Exceptionが発生したり、Colorの値がサーバ側に渡らなかったりする問題が発生していました。
今回作成したサンプルは、クライアント側とサーバ側のアプリ起動時に Resolverを設定することで、それらの問題を解消しています。
ソースコードは GitHub で公開しています。
発生していた問題
通信時に発生していたException
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Status(StatusCode="Internal", Detail="Error starting gRPC call. MessagePackSerializationException: Failed to serialize MagicOnion.DynamicArgumentTuple`2[[ClassLibrary1.Model.ModelClassA, ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[ClassLibrary1.Model.ModelClassA, ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] value. FormatterNotRegisteredException: System.Drawing.Color is not registered in resolver: MessagePack.Resolvers.StandardResolver", DebugException="MessagePack.MessagePackSerializationException: Failed to serialize MagicOnion.DynamicArgumentTuple`2[[ClassLibrary1.Model.ModelClassA, ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[ClassLibrary1.Model.ModelClassA, ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] value. ---> MessagePack.FormatterNotRegisteredException: System.Drawing.Color is not registered in resolver: MessagePack.Resolvers.StandardResolver at MessagePack.FormatterResolverExtensions.Throw(Type t, IFormatterResolver resolver) at MessagePack.Formatters.ClassLibrary1_Model_ModelClassAFormatter3.Serialize(MessagePackWriter& writer, ModelClassA value, MessagePackSerializerOptions options) at MessagePack.Formatters.MagicOnion_DynamicArgumentTuple`2\[\[ClassLibrary1_Model_ModelClassA\, ClassLibrary1\]\,\[ClassLibrary1_Model_ModelClassA\, ClassLibrary1\]\]Formatter2.Serialize(MessagePackWriter& writer, DynamicArgumentTuple`2 value, MessagePackSerializerOptions options) at MessagePack.MessagePackSerializer.Serialize[T](MessagePackWriter& writer, T value, MessagePackSerializerOptions options) --- End of inner exception stack trace --- at MessagePack.MessagePackSerializer.Serialize[T](MessagePackWriter& writer, T value, MessagePackSerializerOptions options) at MessagePack.MessagePackSerializer.Serialize[T](IBufferWriter`1 writer, T value, MessagePackSerializerOptions options, CancellationToken cancellationToken) at MagicOnion.Serialization.MessagePackMagicOnionSerializerProvider.MessagePackMagicOnionSerializer.Serialize[T](IBufferWriter`1 writer, T& value) at MagicOnion.GrpcMethodHelper.<>c__DisplayClass9_0`1.<CreateBoxedMarshaller>b__0(Box`1 obj, SerializationContext ctx) at Grpc.Net.Client.StreamExtensions.WriteMessageAsync[TMessage](Stream stream, GrpcCall call, TMessage message, Action`2 serializer, CallOptions callOptions) at Grpc.Net.Client.Internal.PushUnaryContent`2.WriteMessageCore(ValueTask writeMessageTask) at System.Net.Http.Http2Connection.Http2Stream.SendRequestBodyAsync(CancellationToken cancellationToken) at System.Net.Http.Http2Connection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at Grpc.Net.Client.Internal.GrpcCall`2.RunCall(HttpRequestMessage request, Nullable`1 timeout)") 場所 MagicOnion.Client.ResponseContextRaw`2.<FromRawResponseToResponseAsync>d__20.MoveNext() 場所 MagicOnion.UnaryResult`1.<UnwrapResponse>d__11.MoveNext() |
Resolverを追加することで解消
クライアント側
サーバ側
通信時にColorの値がサーバ側に渡らない
ColorFormatter/ColorResolverクラスを作成することで解消
ライブラリ側
クライアント側
サーバ側
MagicOnion ライブラリ側
ソースコード構成
MessagePack内部の ColorFormatter/ColorResolverを使うと、サーバからクライアントへは値が渡っても、クライアントからサーバへは値が渡らない、バグがあるので自作しています。
ソースコード変更内容を解説
/Formatter/ColorFormatter.cs
Color型を処理できるFormatterを追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using MessagePack; using MessagePack.Formatters; using System.Drawing; namespace ClassLibrary1.Interface { public class ColorFormatter : IMessagePackFormatter<Color> { public static readonly IMessagePackFormatter<Color> Instance = new ColorFormatter(); public void Serialize(ref MessagePackWriter writer, Color value, MessagePackSerializerOptions options) { writer.WriteInt32(value.ToArgb()); } public Color Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { return Color.FromArgb(reader.ReadInt32()); } } } |
/Formatter/ColorResolver.cs
Color型を処理できるResolverを追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
using MessagePack; using MessagePack.Formatters; using System.Drawing; namespace ClassLibrary1.Interface { public class ColorResolver : IFormatterResolver { public static IFormatterResolver Instance = new ColorResolver(); public IMessagePackFormatter<T> GetFormatter<T>() { return FormatterCache<T>.formatter; } static class FormatterCache<T> { public static readonly IMessagePackFormatter<T> formatter; static FormatterCache() { if (typeof(T) == typeof(Color)) { formatter = (IMessagePackFormatter<T>)ColorFormatter.Instance; } else { formatter = null; } } } } } |
/Model/ModelClassA.cs
通信用Modelクラスに、Color型のメンバを追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using MessagePack; using System.Drawing; namespace ClassLibrary1.Model { [MessagePackObject] public class ModelClassA { [Key("IntA")] public int IntA { get; set; } [Key("StringA")] public string StringA { get; set; } [Key("ColorA")] public Color ColorA { get; set; } } } |
MagicOnion サーバ側
ソースコード構成
ソースコード変更内容を解説
/Program.cs
Resolverを設定する処理を追加。
※Resolverを設定する処理は、サーバ側とクライアント側の両方に追加する必要がある。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using ClassLibrary1.Interface; using MagicOnion; using MagicOnion.Server; using MessagePack.Resolvers; using MessagePack; var Resolver = CompositeResolver.Create( NativeDateTimeResolver.Instance, NativeDecimalResolver.Instance, DynamicGenericResolver.Instance, ColorResolver.Instance, ContractlessStandardResolver.Instance ); var options = ContractlessStandardResolver.Options.WithResolver(Resolver); MessagePackSerializer.DefaultOptions = options; var builder = WebApplication.CreateBuilder(args); builder.Services.AddGrpc(); builder.Services.AddMagicOnion(); var app = builder.Build(); app.MapMagicOnionService(); app.MapGet("/", () => "Hello World!"); app.Run(); |
/Services/MyFirstService.cs
Color型のテストデータを追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
using MagicOnion.Server; using MagicOnion; using ClassLibrary1.Interface; using ClassLibrary1.Model; using System.Drawing; namespace WebApplication1.Services { // Implements RPC service in the server project. // The implementation class must inherit `ServiceBase<IMyFirstService>` and `IMyFirstService` public class MyFirstService : ServiceBase<IMyFirstService>, IMyFirstService { // `UnaryResult<T>` allows the method to be treated as `async` method. public async UnaryResult<int> SumAsync(int x, int y) { Console.WriteLine($"Received:{x}, {y}"); return x + y; } public async UnaryResult<ModelClassA> Sum2Async(ModelClassA modelClassA1, ModelClassA modelClassA2) { var ModelClassA = new ModelClassA() { IntA = modelClassA1.IntA + modelClassA2.IntA, StringA = modelClassA1.StringA + modelClassA2.StringA, ColorA = Color.FromArgb(11, 22, 33) }; return ModelClassA; } } } |
MagicOnion クライアント側
ソースコード構成
ソースコード変更内容を解説
/Program.cs
Resolverを設定する処理を追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using ClassLibrary1.Interface; using MessagePack.Resolvers; using MessagePack; namespace WinFormsApp1 { internal static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { var Resolver = CompositeResolver.Create( NativeDateTimeResolver.Instance, NativeDecimalResolver.Instance, DynamicGenericResolver.Instance, ColorResolver.Instance, ContractlessStandardResolver.Instance ); var options = ContractlessStandardResolver.Options.WithResolver(Resolver); MessagePackSerializer.DefaultOptions = options; // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); Application.Run(new Form1()); } } } |
/Form1.cs
Color型のテストデータを追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using ClassLibrary1.Interface; using ClassLibrary1.Model; using Grpc.Net.Client; using MagicOnion.Client; namespace WinFormsApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async void button1_Click(object sender, EventArgs e) { // Connect to the server using gRPC channel. var channel = GrpcChannel.ForAddress("https://localhost:7061"); // Create a proxy to call the server transparently. var client = MagicOnionClient.Create<IMyFirstService>(channel); // Call the server-side method using the proxy. var result = (await client.SumAsync(123, 456)); textBox1.Text = result.ToString(); var modelClassA1 = new ModelClassA() { IntA = 1, StringA = "aa" ,ColorA = Color.FromArgb(10, 20, 30) }; var modelClassA2 = new ModelClassA() { IntA = 2, StringA = "bb" ,ColorA = Color.FromArgb(10, 20, 30) }; var resultModelClassA = (await client.Sum2Async(modelClassA1, modelClassA2)); textBox1.Text = resultModelClassA.IntA.ToString() + " " + resultModelClassA.StringA; } } } |
コメント