MagicOnion 基于 MessagePack 的零注解 DTO 序列化方案
在 MagicOnion 框架中,底层数据交换默认依赖 MessagePack 进行二进制序列化。标准解析器(StandardResolver)通常要求数据契约类必须显式声明 [MessagePackObject] 及 [Key] 特性。若项目追求模型的简洁性,希望彻底剥离序列化注解,则需要在通信链路的两端统一切换至无契约解析策略。以下演示如何通过配置 ContractlessStandardResolverAllowPrivate 实现纯 POCO 类型的数据传输。
数据契约定义
定义请求与响应模型时,无需添加任何序列化特性标记,保持标准的属性声明即可。
public class CalcInput
{
public double OperandA { get; set; }
public double OperandB { get; set; }
}
public class CalcOutput
{
public long TraceId { get; set; }
public string RegionCode { get; set; }
public int ProcessingLevel { get; set; }
public DateTime ProcessedAt { get; set; }
public double FinalValue { get; set; }
public decimal ServiceFee { get; set; }
}
服务接口声明
接口需继承 MagicOnion.IService<T>,方法签名直接引用上述无注解模型。
public interface ICalcEngine : MagicOnion.IService<ICalcEngine>
{
MagicOnion.UnaryResult<CalcOutput> Execute(CalcInput request);
}
服务端逻辑实现
继承 ServiceBase<T> 实现具体业务逻辑。此处示例采用乘法计算与状态封装。
public class CalcEngineImpl : MagicOnion.Server.ServiceBase<ICalcEngine>, ICalcEngine
{
public MagicOnion.UnaryResult<CalcOutput> Execute(CalcInput request)
{
Console.WriteLine($"Processing operands: {request.OperandA}, {request.OperandB}");
return new MagicOnion.UnaryResult<CalcOutput>(new CalcOutput
{
TraceId = 5001L,
RegionCode = "AP-South",
ProcessingLevel = 3,
ProcessedAt = DateTime.UtcNow,
FinalValue = request.OperandA * request.OperandB,
ServiceFee = 24.75m
});
}
}
服务端初始化与解析器配置
关键步骤在于构建服务定义时,通过 MagicOnionOptions 注入自定义的 SerializerOptions。将默认解析器替换为允许访问私有成员的无契约标准解析器,即可解除对特性的依赖。
class Program
{
static void Main(string[] args)
{
try
{
var grpcOptions = new MagicOnionOptions(false)
{
MagicOnionLogger = new MagicOnionLogToGrpcLogger(),
SerializerOptions = MessagePackSerializerOptions.Standard.WithResolver(
MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance)
};
var serviceDefinitions = MagicOnionEngine.BuildServerServiceDefinition(
new Assembly[] { typeof(Program).Assembly }, grpcOptions);
var host = new Server
{
Services = { serviceDefinitions },
Ports = { new ServerPort("0.0.0.0", 9090, ServerCredentials.Insecure) }
};
host.Start();
Console.WriteLine("gRPC service initialized on port 9090");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine($"Initialization error: {ex.Message}");
}
}
}
客户端调用实现
客户端在创建代理实例时,必须传入与服务器完全一致的序列化选项配置。解析器策略不匹配将导致运行时反序列化失败。
class Program
{
static void Main(string[] args)
{
var channel = new Channel("localhost", 9090, ChannelCredentials.Insecure);
var client = MagicOnionClient.Create<ICalcEngine>(channel,
MessagePack.MessagePackSerializerOptions.Standard.WithResolver(
MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance));
var payload = new CalcInput { OperandA = 15.5, OperandB = 8.0 };
var result = client.Execute(payload).ResponseAsync.Result;
Console.WriteLine($"Computed Value: {result.FinalValue} | Fee: {result.ServiceFee}");
}
}
通过上述配置,MagicOnion 的通信链路完全基于字段名称与类型进行自动映射,DTO 类得以保持纯净的业务模型形态,同时确保跨平台 RPC 调用的数据一致性。