MessagePack-CSharp

MessagePack 是一种高效的二进制序列化格式。它允许你在像JSON这样的多种语言之间交换数据。但它更快,更小。小整数被编码成单个字节,典型的短字符串除了字符串本身外,只需要一个额外的字节。
MessagePack-CSharp是MessagePack的C#语言实现,它比MsgPack-Cli快10倍,支持LZ4压缩。本文将探索用MessagePack替代.net System.Text.Json,提高API效率。

基本用法

  1. 在项目添加Messagepack

    1
    Install-Package MessagePack
  2. 创建测试类

    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
    [MessagePackObject]
    public class MyClass2
    {

    [Key(2)]//速度更快,序列化后数组更小
    public int testInt { get; set; }

    [Key(1)]
    public double[] testArray { get; set; }

    [Key(0)]
    public string testStr { get; set; }
    }

    [MessagePackObject]
    public class MyClass
    {
    [Key("age")]//html object友好
    public int Age { get; set; }

    [Key("firstName")]
    public string FirstName { get; set; }

    [Key("lastName")]
    public string LastName { get; set; }

    // All fields or properties that should not be serialized must be annotated with [IgnoreMember].
    [IgnoreMember]
    public string FullName { get { return FirstName + LastName; } }
    }

    说明:MessagePack序列化的对象的每一个属性都需要有Key属性进行约束。Key可以是int或者string。其中int速度更快,序列化后的二进制数组大小也会相对string更小。
    string序列化后数组虽然更大,但在前端html里反序列化成object后更方便的使用。

  3. 序列化及反序列化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    using MessagePack;

    static void Main(string[] args)
    {
    MyClass mc1 = new MyClass() { Age = 77, FirstName = "ddd", LastName = "aaa" };
    byte[] bytes = MessagePackSerializer.Serialize(mc1);//序列化
    var obj = MessagePackSerializer.Deserialize<MyClass>(bytes);//反序列化
    MyClass2 mc2 = new MyClass2()
    {
    testArray = new double[] { 123.456, 2.2345, 3.31357, 4, 5.5, 6.6, 7.7,8,9,10,11,2 },
    testInt = 12345,
    testStr = "asdfxzcbv",
    };
    bytes = MessagePackSerializer.Serialize(mc2);//序列化
    var obj2=MessagePackSerializer.Deserialize<MyClass2>(bytes);//反序列化
    Console.WriteLine("Hello, World!");
    }

服务端用MessagePack替代System.Text.Json

  1. 新建Asp.Net Core Web应用

  2. Nuget添加MessagePack和MessagePack.AspNetCoreMvcFormatter

    1
    2
    Install-Package MessagePack
    Install-Package MessagePack.AspNetCoreMvcFormatter
  3. 修改Program.cs,添加MessagePack序列化控制器支持和跨域的支持

    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
    using MessagePack.AspNetCoreMvcFormatter;
    using MessagePack;
    ......
    // 添加控制器支持
    builder.Services.AddControllers(options =>
    {
    // 添加 MessagePack 输出格式化器
    options.OutputFormatters.Add(new MessagePackOutputFormatter(MessagePackSerializerOptions.Standard));
    // 添加 MessagePack 输入格式化器
    options.InputFormatters.Add(new MessagePackInputFormatter(MessagePackSerializerOptions.Standard));
    // 设置 MessagePack 为默认内容类型
    options.ReturnHttpNotAcceptable = true; // 强制客户端接受 MessagePack
    options.FormatterMappings.SetMediaTypeMappingForFormat("msgpack", "application/x-msgpack");
    }).AddControllersAsServices();
    ......
    // 配置 MessagePack 序列化选项(例如启用压缩)
    builder.Services.Configure<MessagePackSerializerOptions>(options =>
    {
    options = options.WithCompression(MessagePackCompression.Lz4Block);
    });
    ......
    //配置跨域请求
    builder.Services.AddCors(options =>
    {
    options.AddPolicy(name: "MyPolicy",
    policy =>
    {
    policy.WithOrigins(
    "https://localhost:5069",
    "https://localhost:5001")
    .AllowAnyOrigin().AllowAnyHeader();
    });
    });
    ......
    app.UseCors("MyPolicy");//启用
    app.MapControllers();

  4. 新建API控制器,配置接口

    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

    [Route("api/[action]")]
    [ApiController]
    public class TestAPIController : ControllerBase
    {
    MyClass mc = new MyClass
    {
    Age = 99,
    FirstName = "hoge",
    LastName = "huga",
    };

    //[HttpGet]
    //[Consumes("application/x-msgpack")]
    public MyClass GetObj4()
    {
    return mc;
    }

    public MyClass2 GetObj3()
    {
    MyClass2 mc2 = new MyClass2() { testArray = new double[] { 1, 2, 3, 4, 5, 6.6, 7, 8.8 }, testInt = 123, testStr = "zasdfgsdd" };
    return mc2;
    }
    }

  5. 启动程序,测试效果

1
2
3
4
curl -X GET "http://localhost:5208/api/getobj3" -H "Accept: application/x-msgpack" --output 3_m.txt
curl -X GET "http://localhost:5208/api/getobj4" -H "Accept: application/x-msgpack" --output 4_m.txt
curl -X GET "http://localhost:5208/api/getobj4" --output 4.txt
curl -X GET "http://localhost:5208/api/getobj3" --output 3.txt

可以看到,服务端根据不同的请求返回不同的结果。

前端-Html

前端页面需要引用msgpack.min.js并且调用MessagePack.decodeAsync方法将api请求的数据进行反序列化。
关键代码如下:

Index.cshtml
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
46

@section Scripts{
<script crossorigin src="https://unpkg.com/@@msgpack/msgpack"></script>
<script>

const MSGPACK_TYPE = "application/x-msgpack";
(async () => {
// decode()
{
const response = await fetch('/api/GetObj3', {
method: 'GET',
headers: {
'Accept': MSGPACK_TYPE // 设置接受 MessagePack 格式
}
});
const contentType = response.headers.get("content-type");
if (contentType && contentType.startsWith(MSGPACK_TYPE) && response.body != null) {
const object = await MessagePack.decodeAsync(response.body);
console.log("decode:", object);
} else {
console.error("Something is wrong!");
}

}

// decodeAsync()
{
const response = await fetch('/api/GetObj4', {
method: 'GET',
headers: {
'Accept': MSGPACK_TYPE // 设置接受 MessagePack 格式
}
});
const contentType = response.headers.get("content-type");
if (contentType && contentType.startsWith(MSGPACK_TYPE) && response.body != null) {
const object = MessagePack.decode(await response.arrayBuffer());
console.log("decodeAsync:", object);
} else {
console.error("Something is wrong!");
}
}
})()
</script>
}


运行程序,查看浏览器控制台输出(变量键值如果是字符串,那么在html下反序列化后得到的object可以很方便的使用。反之反序列化后得到的是一个数组,用起来相对麻烦一些)。

前端-Blazor

  1. 新建”Blazor WebAssembly独立应用”,并添加MessagePack的Nuget引用。
  2. 检查Program.cs 添加HttpClient支持
    Program.cs
    1
    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
  3. 编辑Home.razor,添加测试代码
    Home.razor
    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
    46
    47
    48

    @page "/"
    @using System.Net.Http.Headers
    @using MessagePack
    @using TestModels
    @inject HttpClient Http

    <PageTitle>Home</PageTitle>

    <h1>Hello, world!</h1>

    Welcome to your new app.

    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

    <button class="btn btn-primary" @onclick="IncrementCount2">Click me2</button>


    @code {
    private async Task IncrementCount()
    {

    var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5208/api/getobj4");
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-msgpack"));

    var response = await Http.SendAsync(request);
    response.EnsureSuccessStatusCode();

    var content = await response.Content.ReadAsByteArrayAsync();
    var obj = MessagePackSerializer.Deserialize<MyClass>(content, MessagePackSerializerOptions.Standard);
    Console.WriteLine(obj.Age);
    }

    private async Task IncrementCount2()
    {

    var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5208/api/getobj3");
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-msgpack"));

    var response = await Http.SendAsync(request);
    response.EnsureSuccessStatusCode();

    var content = await response.Content.ReadAsByteArrayAsync();
    var obj = MessagePackSerializer.Deserialize<MyClass2>(content, MessagePackSerializerOptions.Standard);
    Console.WriteLine(obj.testStr);
    }
    }

    运行服务端程序和Blazor程序,点击Home页面下的按钮并在断点以及控制台查看效果。

源码下载