Introduction
Protocol buffers, or Protobuf, are a binary format created by Google to serialize data between different services. Google made this protocol open source and now it provides support, out of the box, to the most common languages, like JavaScript, Java, C#, Ruby and others.
Protocol buffers, usually referred as Protobuf, are a protocol developed by Google to allow serialization and deserialization of structured data. In this article, we will demonstrate to use protocol buffer with Ajax.
Define a protocol buffer file
We will be using latest version of protocol buffer (proto3) for this demo. Here, we have created a proto file ‘person.proto’.
syntax="proto3"; package personpackage; message Person { Â Â Â Â Â string name = 1; Â Â Â Â Â int32 id = 2; Â Â Â Â Â string email = 3; }
We have defined the version of protocol buffers as proto3 through
syntax=proto3;
In given proto file, we have created a Person message with three field: name, id and email.
Creating batch file to compile protocol buffers file
In next step, we will compile ‘person.proto’ into javascript file. Following batch code is used to compile the proto file.
setlocal set PROTOC=%UserProfile%\.nuget\packages\Google.Protobuf.Tools\3.6.1\tools\windows_x64\protoc.exe set PersonPath=person.proto set outputPath=./ %PROTOC% -I.\ --js_out %outputPath% %PersonPath%
PROTOC variable refers to the location of protoc.exe, which is generated when we install Grpc.Tools library through nuget package manager. Similary, PersonPath refers to the path where proto file is located. And outputPath is a path where generated output file is saved. Note that since we want the output in JavaScript output (for Ajax request.), we have used –js_out instead of csharp_out as we used in previous articles. For more details of compiling protobuf file, you can visit this URLÂ .
Project Setup
Create a new ASP.NET Core Web Application with MVC template.
Once installed, we will need to install two nuget packages
- Protobuf (C# runtime library for protocol buffers)
- Protobuf-net (Provides a default set of APIs for building an ASP.NET Core application)
For client side, we will need to ensure that following libraries are available.
We will save these entire JavaScript file including ‘person.js’ in wwwroot/js path of the project. For simplicity, we will load these entire JavaScript file in _ViewStart.cshtml.
And lastly, we will be using a protobuf extension formatter to enable protobuf request and response through Web API in project.
In Startup.cs, we will have to enable the extension with addition of AddProtobufFormatters() in ConfigureService.
Decorate classes and members
In order to bind data of proto request sent from Ajax, we will need to decorate a class similar to proto file. We will create a class PersonPoco.cs in Models directory.
using System; using ProtoBuf; namespace ProtoBinding.Models {  [ProtoContract, Serializable]  public class PersonPoco  {    [ProtoMember(2)]    public int Id { get; set; }    [ProtoMember(1)]    public string Name { get; set; }    [ProtoMember(3)]    public string Email { get; set; }  } }
A class name should be decorated with ProtoContract attribute. It indicates that a type is defined for protocol-buffer serialization.
Similarly, all properties should be decorated with ProtoMember attribute. Note that a tag in its parameter must match with a proto file.
syntax="proto3"; package personpackage; message Person { Â Â Â Â Â string name = 1; Â Â Â Â Â int32 id = 2; Â Â Â Â Â string email = 3; }
POST request with Ajax
Let’ s start by creating an API Controller named as ProtoController.
namespace ProtoBinding.Controllers {  [ApiController]  [Route("/api/{controller}/{action}")]  public class ProtoController : Controller  {    [HttpPost]    public IActionResult PostProto([FromBody] PersonPoco person)    {      return Ok(person);    }  } }
Here PostProto method will receive an object of protocol buffer which is deserialized and then wired with PersonPoco class. In Views directory, we will add a new folder ‘Proto’ and add new .cshtml file ‘ProtoForm’ with following code.
@model PersonPoco @{ Â Â Â ViewData["Title"] = "ProtoForm"; } <h1>ProtoForm</h1> <hr /> <div class="row"> Â Â Â <div class="col-md-4"> Â Â Â Â Â Â Â <form asp-action="ProtoForm" id="protoForm"> Â Â Â Â Â Â Â Â Â Â Â <div asp-validation-summary="ModelOnly" class="text-danger"></div> Â Â Â Â Â Â Â Â Â Â Â <div class="form-group"> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <label asp-for="Id" class="control-label"></label> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <input asp-for="Id" class="form-control" /> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <span asp-validation-for="Id" class="text-danger"></span> Â Â Â Â Â Â Â Â Â Â Â </div> Â Â Â Â Â Â Â Â Â Â Â <div class="form-group"> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <label asp-for="Name" class="control-label"></label> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <input asp-for="Name" class="form-control" /> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <span asp-validation-for="Name" class="text-danger"></span> Â Â Â Â Â Â Â Â Â Â Â </div> Â Â Â Â Â Â Â Â Â Â Â <div class="form-group"> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <label asp-for="Email" class="control-label"></label> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <input asp-for="Email" class="form-control" /> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <span asp-validation-for="Email" class="text-danger"></span> Â Â Â Â Â Â Â Â Â Â Â </div> Â Â Â Â Â Â Â Â Â Â Â <div class="form-group"> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â <input onclick="protoSubmit()" value="Create" class="btn btn-primary" /> Â Â Â Â Â Â Â Â Â Â Â </div> Â Â Â Â Â Â Â </form> Â Â Â </div> </div>
Above markup will create a form as displayed below.
Then in same file, we will add a script to call Ajax request based on field of above form.
<script>        function protoSubmit() {            var isValid = $("#protoForm").valid();            if (isValid) {                var id = $("#Id").val();                var name = $("#Name").val();                var email = $("#Email").val();                var person = createPerson(id, name, email);                postPerson(person);            }        }        function createPerson(id, name, email) {            var person = new window.proto.personpackage.Person();            person.setId(id);            person.setName(name);            person.setEmail(email);            return person;        }        function postPerson(person) {            $.ajax({                url: 'postproto',                type: 'POST',                data: person.serializeBinary(),                contentType: "application/x-protobuf;",                success: function (data) {                    console.log(data);                    alert('success');                },                error: function (err) {                    console.log(err);                    alert(`Error`);                },                processData: false            });        }    </script>
Once we fill up a form and click a Create button, protoSubmit() function will be triggered. Here, we will firstly validate a form. If valid, then we will retrieve all three values from field and then trigger createPerson() function.
In createPerson() function, we will create an object of Person() which defined in person.js. Then, we will set its values using setId(), setName, and setEmail(). Lastly, it will return the created object.
And with returned object from createPerson(), we will trigger postPerson() function, which will implement Ajax to post given object.
Ajax properties to post protocol buffers
url: API’s action name
type: ‘POST’ since we are posting a request
data: We will pass a binary serialized content. The content will be deserialized and wired in server side at ReadRequestBodyAsync() method of ProtobufInputFormatter.cs
processData: false is necessary to avoid jQuery serializing the data in the background.
contentType: It can be either of “application/x-protobuf”, “application/protobuf”, “application/x-google-protobuf” as defined in SupportedContentTypes in ProtobufFormatterOptions.cs
Once we click ‘Create’ button, then serialize binary data will be deserialized and wired with the desired model type (PersonPoco in this case as defined in PostProto method’s parameter), we will get following output. As we can see, the protocol buffer data is now bound with PersonPoco class.
GET Request with Protocol Buffers using Ajax
Firstly, let’s create a method with HttpGet attribute in our ProtoController.
[HttpGet]    public IActionResult ProtoDetail(int id)    {      var person = new PersonPoco      {        Id = id,        Name = "Susen",        Email = "susen.maharjan@javra.com"      };      return Ok(person);    }
Firstly, we will test HTTP GET method by passing simple integer as parameter by requesting from Postman.
You can see that output is being handled by custom output formatter and response is serialized to Protobuf instead of JSON format. For output, we are going to request protobuf with JSON accept headers.
In the case if we want to wire Protocol buffers in Ajax with Protobuf.js, then we will need to auto-generate proto file into C# format. The syntaxes to create batch file is similar to the previous one used to create a JavaScript protocol buffer file. The only difference is that we will use –csharp_out instead of —js_out.
setlocal set PROTOC=%UserProfile%\.nuget\packages\Google.Protobuf.Tools\3.6.1\tools\windows_x64\protoc.exe set PersonPath=person.proto set outputPath=./ %PROTOC% -I.\ --csharp_out %outputPath% %PersonPath%
This batch file will auto-generate a c-sharp file for a provided person.proto.
So let’s create another HTTP GET method in out ProtoController.
[HttpGet]    public IActionResult GetProto()    {      var person = new Person      {        Id = 1001,        Name = "Susen",        Email = "susen.maharjan@javra.com"      };      return Ok(person.ToByteArray());    }
Here, we have created an object of person and sent response of its byte array. In ProtoForm.cshtml, we will add few HTML tags as follow.
<div> Â Â Â <a onclick="getPerson()" href="#"> GET Request</a> </div> <div id="personList"> Â Â Â <ul class="card-header-tabs"></ul> </div>
And we will add new function in our script to call out HTTP GET method.
function getPerson() { Â Â Â Â Â Â Â Â Â Â Â $.get({ Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â headers: { Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Accept: 'application/json' Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â }, Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â url: 'GetProto', Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â contentType: 'application/x-protobuf;', Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â success: function(data) { Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â console.log(`before deserializing`); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â console.log(data); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â console.log(`after deserializing`); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â var person = window.proto.personpackage.Person.deserializeBinary(data); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â console.log(person); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â var personId = person.getId(); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â var personName = person.getName(); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â var personEmail = person.getEmail(); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â $("#personList ul").append(`<li>${personId} ${personName} ${personEmail}</li>`); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â }, Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â error: function(result) { Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â alert('error'); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â console.log(result); Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â } Â Â Â Â Â Â Â Â Â Â Â }); Â Â Â Â Â Â Â }
Note that we are accepting JSON as accept header since auto-generated c-sharp cod of person.js will not have a ProtoContract attribute (because it is attribute of protobuf-net library).
In the above HTML tags and script function, we have created a div where a list of person details will be appended. Firstly, when we click ‘Get Request’ button, it will trigger GetProto() function in out ProtoController. Here a new object of person will be created and will return its ByteArray (ToByteArray() is an prebuilt extension of Google.Protobuf).
Then in our client side, we will deserialize the received byte array into Person object of Protobuf.js and we will get its values and append it on personList.
When to use Protobuf over JSON
- Fast serialization and deserialization
- Prevents schema violation
- Guarantees type-safety
When to use JSON over Protobuf
- We need or want data to be human readable
- Data from the service is directly consumed by a web browser
All the codes used for this article can be accessed from provided Github URLÂ .
References
- https://dejanstojanovic.net/aspnet/2018/september/custom-input-and-output-serializers-in-aspnet-core/
- https://dejanstojanovic.net/aspnet/2018/june/how-to-boost-application-performance-by-choosing-the-right-serialization/
- https://www.codeproject.com/Articles/642677/Protobuf-net-the-unofficial-manual
- https://developers.google.com/protocol-buffers/docs/reference/javascript-generated