ASP.NET Core Using Protobuf Formatters

This article shows how to use and implement Protobuf as a custom InputFormatter and a custom OutputFormatter in ASP.NET Core. Protobuf can then be used in the HTTP requests and responses for serialization. Before ASP.NET Core, this was implemented in Web API using MediaFormatters. In ASP.NET Core, this has been split into IInputFormatter and IOutputFormatter. The InputFormatter and OutputFormatter classes can be used to implement custom formatters.

Code: https://github.com/damienbod/AspNetCoreMvcProtobufFormatters

Posts in this series:

2019-01-30 Updated to ASP.NET Core 2.2
2017-08-17 Updated ASP.NET Core 2.0
2017-03-04 Updated to csproj, VS2017, protobuf-net 2.1.0
2016-07-01 Updated to ASP.NET Core 1.0 RTM
2016-05-22 Updated to ASP.NET Core 1.0 RC2
2015-11-18 Updated to ASP.NET Core 1.0 RC1
2015-10-20 Updated to ASP.NET Core 1.0 beta 8
2015-09-20 Updated to ASP.NET Core 1.0 beta 7

To support HTTP GET requests with Protobuf content, an OutputFormatter can be implemented as follows:
(The code has been ported from WebApiContrib.Formatting.ProtoBuf)

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using ProtoBuf.Meta;

namespace AspNetCoreProtobuf.Formatters
{
    public class ProtobufOutputFormatter :  OutputFormatter
    {
        private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel);

        public string ContentType { get; private set; }

        public static RuntimeTypeModel Model
        {
            get { return model.Value; }
        }

        public ProtobufOutputFormatter()
        {
            ContentType = "application/x-protobuf";
            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/x-protobuf"));

            //SupportedEncodings.Add(Encoding.GetEncoding("utf-8"));
        }

        private static RuntimeTypeModel CreateTypeModel()
        {
            var typeModel = TypeModel.Create();
            typeModel.UseImplicitZeroDefaults = false;
            return typeModel;
        }

        public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
        {
            var response = context.HttpContext.Response;
    
            Model.Serialize(response.Body, context.Object);
            return Task.FromResult(response);
        }
    }
}

The InputFormatter can be implemented to support POST, DELETE or PUT requests. The following InputFormattter uses Protobuf for serialization.
(The code has been ported from WebApiContrib.Formatting.ProtoBuf)

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using ProtoBuf.Meta;

namespace AspNetCoreProtobuf.Formatters
{
    public class ProtobufInputFormatter : InputFormatter
    {
        private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel);

        public static RuntimeTypeModel Model
        {
            get { return model.Value; }
        }

        public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            var type = context.ModelType;
            var request = context.HttpContext.Request;
            MediaTypeHeaderValue requestContentType = null;
            MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType);


            object result = Model.Deserialize(context.HttpContext.Request.Body, null, type);
            return InputFormatterResult.SuccessAsync(result);
        }

        public override bool CanRead(InputFormatterContext context)
        {
            return true;
        }


        private static RuntimeTypeModel CreateTypeModel()
        {
            var typeModel = TypeModel.Create();
            typeModel.UseImplicitZeroDefaults = false;
            return typeModel;
        }
    }
}

Here’s the full csproj file. The protobuf-net package was added using NuGet.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <PreserveCompilationContext>true</PreserveCompilationContext>
    <UserSecretsId>aspnet-WebApp_Auth-96d521e3-30ac-4a8f-967f-abc243174dce</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="wwwroot\index.html" />
  </ItemGroup>

  <ItemGroup>
    <Content Update="wwwroot\**\*;Views;appsettings.json;web.config">
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
    <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink.Loader" Version="14.1.0" />
    <PackageReference Include="protobuf-net" Version="2.1.0" />
  </ItemGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
    <Reference Include="System" />
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>

</Project>

Now the custom formatters can be added to the pipeline. This is done in the startup class in the ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
                options.InputFormatters.Add(new ProtobufInputFormatter());
                options.OutputFormatters.Add(new ProtobufOutputFormatter());
                options.FormatterMappings.SetMediaTypeMappingForFormat("protobuf",  MediaTypeHeaderValue.Parse("application/x-protobuf"));
    });
}

The ProtobufModelDto class is used for the model. This DTO contains the Protobuf configurations.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ProtoBuf;

namespace AspNetCoreProtobuf.Model
{
    [ProtoContract]
    public class ProtobufModelDto
    {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public string Name { get; set; }
        [ProtoMember(3)]
        public string StringValue { get; set; }

    }
}

A POST and a GET action method can then be added to the ASP.NET Core MVC controller.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AspNetMvc6Protobuf.Model;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreProtobuf.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET api/values/5
        [HttpGet("{id}")]
        public ProtobufModelDto Get(int id)
        {
            return new ProtobufModelDto() { Id = 1, Name = "HelloWorld", StringValue = "My first ASP.NET Core Protobuf service" };
        }

        [HttpPost]
        [Route("")]
        public void Post([FromBody]ProtobufModelDto value)
        {
            // Yes the value can be sent as a protobuf item.
            var myValue = value;
        }

    }
}

Now the ASP.NET Core MVC can handle HTTP requests with Protobuf serialization. The following request will return the object with the body serialized using Protobuf.

GET http://localhost:14717/api/Values/4 HTTP/1.1
Accept: application/x-protobuf
Host: localhost:14717
Connection: Keep-Alive

The Following HTTP POST sends the data to the server with the body serialized using Protobuf.
protobufPost_01

The following code is a .NET 4.5 HttpClient which sends a HTTP GET and a HTTP POST request using Protobuf to serialize or deserialize the data.

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using WebApiContrib.Formatting;
using WebAPiProtobuf.Models;

namespace WebClientProtobuf
{
	class Program
	{
		static void Main(string[] args)
		{
			// HTTP GET with Protobuf Response Body
			var client = new HttpClient { BaseAddress = new Uri("http://localhost.fiddler:14717/") };
			client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));

			HttpResponseMessage response = client.GetAsync("api/Values/4").Result; 
			
			if (response.IsSuccessStatusCode)
			{
				// Parse the response body. Blocking!
				var p = response.Content.ReadAsAsync<ProtobufModelDto>(new[] { new ProtoBufFormatter()}).Result;
				Console.WriteLine("{0}\t{1};\t{2}", p.Name, p.StringValue, p.Id);               
			}
			else
			{
				Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
			}

			Console.ReadLine();

			// HTTP POST with Protobuf Request Body
			var responseForPost = client.PostAsync( "api/Values", new ProtobufModelDto { Id = 1, Name = "test", StringValue = "todo" }, new ProtoBufFormatter()).Result;

			if (responseForPost.IsSuccessStatusCode)
			{
				Console.WriteLine("All ok");
			}
			else
			{
				Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
			}

			Console.ReadLine();

		}        
	}
}

It is simple to implement custom formatters in ASP.NET Core MVC. The InputFormatter and the OutputFormatter classes provide most of the functionality required for this.

Links:

https://github.com/mgravell/protobuf-net

http://teelahti.fi/using-google-proto3-with-aspnet-mvc/

https://github.com/damienpontifex/ProtobufFormatter/tree/master/src/ProtobufFormatter

http://www.strathweb.com/2014/11/formatters-asp-net-mvc-6/

http://blogs.msdn.com/b/webdev/archive/2014/11/24/content-negotiation-in-mvc-5-or-how-can-i-just-write-json.aspx

https://github.com/WebApiContrib/WebApiContrib.Formatting.ProtoBuf

https://damienbod.wordpress.com/2014/01/11/using-protobuf-net-media-formatter-with-web-api-2/

8 comments

  1. Why not Microsoft Bond? Or is there already a formatter for that?

    1. Hi Chris

      Thanks for your comment.

      Microsoft Bond seems good, I have never tried it out in a productive system, looks very promising. I will give a try. Thanks for the tip.

      I have tried and used Protobuf and its works great. I have no issues with Protobuf or no reasons to stop using it.

      Greetings Damien

  2. steveC · · Reply

    Why did you choose ProtoBuf at all for serialization? I have actually used it in a high volume production project, but I had my own technical reasons for doing it.

    1. HI Steve

      I use Protobuf for the same reasons as you, when high performance is required, or the size of the request must be as small as possible due to the client, or for data transfers, sync applications etc.

      Otherwise, I always use JSON.

      I only switch to Protobuf, if JSON serialization doesn’t fulfill the requirements.

      Greetings Damien

  3. Custom Protobuf Formatters. Ay updates on this?

  4. […] ASP.NET 5 MVC 6 Custom Protobuf Formatters // ASP.NET Articles of the Day […]

  5. […] ASP.NET Core 1.0 MVC 6 Custom Protobuf Formatters […]

  6. simon · · Reply

    I found this to be slower than the built-in JSON serializer by about 50%, is this the most efficient way to implement this?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.