ASP.NET Core 1.0 MVC 6 Custom Protobuf Formatters

This article shows how to use and implement Protobuf as a custom InputFormatter and a custom OutputFormatter in MVC 6. Protobuf can then be used in the HTTP requests and responses for serialization. Before MVC 6, this was implemented in Web API using MediaFormatters. In MVC 6, 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/AspNetMvc6ProtobufFormatters

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;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using ProtoBuf.Meta;

namespace AspNetMvc6Protobuf.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 AspNetMvc6Protobuf.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;
        }
    }
}

Because the Formatters are dependent on the Protobuf .NET NuGet package, the application runs only with dnx451. Remove the core from the project.json file.

{
  "userSecretsId": "aspnet-WebApp_Auth-96d521e3-30ac-4a8f-967f-abc243174dce",

    "dependencies": {
        "Microsoft.AspNetCore.Diagnostics": "1.0.0",
        "Microsoft.AspNetCore.Mvc": "1.0.0",
        "Microsoft.AspNetCore.Razor.Tools": {
            "version": "1.0.0-preview2-final",
            "type": "build"
        },
        "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
        "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
        "Microsoft.AspNetCore.StaticFiles": "1.0.0",
        "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
        "Microsoft.Extensions.Configuration.Json": "1.0.0",
        "Microsoft.Extensions.Logging": "1.0.0",
        "Microsoft.Extensions.Logging.Console": "1.0.0",
        "Microsoft.Extensions.Logging.Debug": "1.0.0",
        "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
        "protobuf-net": "2.0.0.668"
    },

  "tools": {
    "Microsoft.AspNetCore.Razor.Tools": {
      "version": "1.0.0-preview2-final",
      "imports": "portable-net45+win8+dnxcore50"
    },
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": {
      "version": "1.0.0-preview2-final",
      "imports": "portable-net45+win8+dnxcore50"
    }
  },

  "frameworks": {
    "net461": {}
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "gcServer": true
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "Views",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "prepublish": [ "npm install", "bower install", "gulp clean", "gulp min" ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

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 AspNetMvc6Protobuf.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 MVC 6 controller.

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

namespace AspNetMvc6Protobuf.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 MVC 6 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 MVC 6 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 MVC 6. The InputFormatter and the OutputFormatter classes provide most of the functionality required for this.

Links:

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/

7 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 […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: