插上腾飞的翅膀:为asp.net core添加protobuf支持

时间:2022-12-14 18:18:19

没时间解释了,快上车。

通过NuGet获取Zaabee.AspNetCoreProtobuf

Install-Package Zaabee.AspNetCoreProtobuf

在Startup.cs文件中修改ConfigureServices方法

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options => { options.AddProtobufSupport(); });
}

搞掂……这时候你就可以通过application/x-protobuf的content-type来让asp.net core使用protobuf来进行序列化/反序列化。

测试代码

在asp.net core项目中添加以下DTO

[ProtoContract]
public class TestDto
{
    [ProtoMember(1)] public Guid Id { get; set; }
    [ProtoMember(2)] public string Name { get; set; }
    [ProtoMember(3)] public DateTime CreateTime { get; set; }
    [ProtoMember(4)] public List<TestDto> Kids { get; set; }
    [ProtoMember(5)] public long Tag { get; set; }
    [ProtoMember(6)] public TestEnum Enum { get; set; }
}

public enum TestEnum
{
    Apple,
    Banana,
    Pear
}

新建一个XUnit项目,通过Nuget引用Microsoft.AspNetCore.TestHost,建立一个测试类

public class AspNetCoreProtobufTest
{
    private readonly TestServer _server;
    private readonly HttpClient _client;

    public AspNetCoreProtobufTest()
    {
        _server = new TestServer(
            new WebHostBuilder()
                .UseKestrel()
                .UseStartup<Startup>());
        _client = _server.CreateClient();
    }

    [Fact]
    public void Test()
    {
        // HTTP Post with Protobuf Response Body
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));

        var dtos = GetDtos();
        var stream = new MemoryStream();
        ProtoBuf.Serializer.Serialize(stream, dtos);

        HttpContent httpContent = new StreamContent(stream);

        // HTTP POST with Protobuf Request Body
        var responseForPost = _client.PostAsync("api/Values", httpContent);

        var result = ProtoBuf.Serializer.Deserialize<List<TestDto>>(
            responseForPost.Result.Content.ReadAsStreamAsync().Result);

        Assert.True(CompareDtos(dtos,result));
    }

    private static bool CompareDtos(List<TestDto> lstOne, List<TestDto> lstTwo)
    {
        lstOne = lstOne ?? new List<TestDto>();
        lstTwo = lstTwo ?? new List<TestDto>();

        if (lstOne.Count != lstTwo.Count) return false;

        for (var i = 0; i < lstOne.Count; i++)
        {
            var dtoOne = lstOne[i];
            var dtoTwo = lstTwo[i];
            if (dtoOne.Id != dtoTwo.Id || dtoOne.CreateTime != dtoTwo.CreateTime || dtoOne.Enum != dtoTwo.Enum ||
                dtoOne.Name != dtoTwo.Name || dtoOne.Tag != dtoTwo.Tag || !CompareDtos(dtoOne.Kids, dtoTwo.Kids))
                return false;
        }

        return true;
    }

    private static List<TestDto> GetDtos()
    {
        return new List<TestDto>
        {
            new TestDto
            {
                Id = Guid.NewGuid(),
                Tag = long.MaxValue,
                CreateTime = DateTime.Now,
                Name = "0",
                Enum = TestEnum.Apple,
                Kids = new List<TestDto>
                {
                    new TestDto
                    {
                        Id = Guid.NewGuid(),
                        Tag = long.MaxValue - 1,
                        CreateTime = DateTime.Now,
                        Name = "00",
                        Enum = TestEnum.Banana
                    },
                    new TestDto
                    {
                        Id = Guid.NewGuid(),
                        Tag = long.MaxValue - 2,
                        CreateTime = DateTime.Now,
                        Name = "01",
                        Enum = TestEnum.Pear
                    }
                }
            },
            new TestDto
            {
                Id = Guid.NewGuid(),
                Tag = long.MaxValue - 3,
                CreateTime = DateTime.Now,
                Name = "1",
                Enum = TestEnum.Apple,
                Kids = new List<TestDto>
                {
                    new TestDto
                    {
                        Id = Guid.NewGuid(),
                        Tag = long.MaxValue - 4,
                        CreateTime = DateTime.Now,
                        Name = "10",
                 https://i.cnblogs.com/EditCategories.aspx?catid=1       Enum = TestEnum.Banana
                    },
                    new TestDto
                    {
                        Id = Guid.NewGuid(),
                        Tag = long.MaxValue - 5,
                        CreateTime = DateTime.Now,
                        Name = "11",
                        Enum = TestEnum.Pear
                    }
                }
            }
        };
    }
}

为什么要用protobuf?

因为快……在我们这边使用业务数据的测试中,protobuf的序列化/反序列化性能大概是Json.net的三倍,序列化后的体积大概只有Json的二分之一,这可以在相当程度上提高webapi的吞吐性能。

protobuf的缺点

DTO层必须引用protobuf-net来添加特性,这在一定程度上导致了代码的侵入。基本上DTO属于POCO,依赖第三方包的话总觉得有点不贞洁……另外就是protobuf序列化后的数据不具有可视化,因此如果是使用消息队列或者请求监控的地方,就要综合考虑protobuf是否适合使用场景。

原理

asp.net core是基于中间件方式来实现,其自带默认的JsonFormater(基于Json.net),asp.net core会根据content type来选择对应的Formater来处理对象的序列化,当中包括InputFormatter(反序列化)和OutputFormatter(序列化)。因此除了protobuf,我们还可以添加或者替换其它的序列化方式,例如使用Jil来代替Json.net来提高Json性能。

以上实现以及Demo和测试的源代码已放到GitHub上。

最后给大家拜个晚年,祝大家新年快乐~