Building Custom Formatters For .Net Core (Yaml Formatters)

Quite recently I got introduced to .net core’s formatters. In short, what formatters do is format your response and request in your preferred data formats. For example, Json formatters, if used, would format your response (returned value from controller's action) and request (value passed as a parameter to the controller) in Json. Same goes for the XML and other formatters. .Net Core gives you some formatters out of the box. This official documentation link describes them briefly

https://docs.microsoft.com/en-us/aspnet/core/mvc/models/formatting

But let’s not talk too much about formatters;  rather let's see how we can make our own custom formatters. I think that’s what we are here for right? Yeah! Let’s get started.

So, we have two abstract classes provided by the framework, InputFormmeter and OutputFormatter. Basically you would want to use these classes to make your own formatters. But there are other two abstract classes that extend from those two formatters. TextInputFormatter and TextOuputFormatter can work with response that are simple string representations of data formats (data can be in form of binary too). For example, Json and XML formatters extend these classes. We are going to build two Yaml formatters, one for input and the other one for output formatting.

Now the question is what is Yaml? Here is the definition for it directly scraped out of Wikipedia,

YAML IS A HUMAN-READABLE DATA SERIALIZATION LANGUAGE. IT IS COMMONLY USED FOR CONFIGURATION FILES, BUT COULD BE USED IN MANY APPLICATIONS WHERE DATA IS BEING STORED (E.G. DEBUGGING OUTPUT) OR TRANSMITTED (E.G. DOCUMENT HEADERS). YAML TARGETS MANY OF THE SAME COMMUNICATIONS APPLICATIONS AS XML, BUT HAS TAKEN A MORE MINIMAL APPROACH WHICH INTENTIONALLY BREAKS COMPATIBILITY WITH SGML. YAML IS A SUPERSET OF JSON, ANOTHER MINIMALIST DATA SERIALIZATION FORMAT WHERE BRACES AND BRACKETS ARE USED INSTEAD OF INDENTATION.

The idea is very simple. When using the Yaml output formatter you would get the response (returned value of the controller's action) out of the current HttpContext and Serialize them into raw Yaml response text and send them back to the client. Pretty much same goes for the input formatter. In this case, you would Deserialize the Yaml content from the client's request and use them in a generic form. Another important thing is, you have to explicitly set media type header for these formatters. Doing that will activate these formatters whenever a client define a Acceptheader (for output) and Content-Type (for input) with that specific media type format (application/x-yaml).

If you don’t want to use those headers while calling your controller’s actions you can explicitly define the type of formatter that should be used while getting or posting content. For example, the [Produces(application/x-yaml)] will return the response in Yaml format whether you define a Acceptheader or not. Again using the [Consumes(application/x-yaml)] attribute would only accept Yaml content whether you define the Content-Type or not.

Enough history lessons. Here goes the input formatter for Yaml. By the way I’m using the YamlDotNet library from Antoine Aubry (@antoineaubry) for Yaml’s serializing and desirializing process.

YamlInputFormatter.cs

  1. public class YamlInputFormatter : TextInputFormatter  
  2. {  
  3.     private readonly Deserializer _deserializer;  
  4.   
  5.     public YamlInputFormatter(Deserializer deserializer)  
  6.     {  
  7.         _deserializer = deserializer;  
  8.   
  9.         SupportedEncodings.Add(Encoding.UTF8);  
  10.         SupportedEncodings.Add(Encoding.Unicode);  
  11.         SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationYaml);  
  12.         SupportedMediaTypes.Add(MediaTypeHeaderValues.TextYaml);  
  13.     }  
  14.   
  15.     public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)  
  16.     {  
  17.         if (context == null)  
  18.         {  
  19.             throw new ArgumentNullException(nameof(context));  
  20.         }  
  21.   
  22.         if (encoding == null)  
  23.         {  
  24.             throw new ArgumentNullException(nameof(encoding));  
  25.         }  
  26.   
  27.         var request = context.HttpContext.Request;  
  28.   
  29.         using (var streamReader = context.ReaderFactory(request.Body, encoding))  
  30.         {  
  31.             var type = context.ModelType;  
  32.   
  33.             try  
  34.             {  
  35.                 var model = _deserializer.Deserialize(streamReader, type);  
  36.                 return InputFormatterResult.SuccessAsync(model);  
  37.             }  
  38.             catch (Exception)  
  39.             {  
  40.                 return InputFormatterResult.FailureAsync();  
  41.             }  
  42.         }  
  43.     }  
  44. }   

The code is pretty much self-explanatory. Get the Yaml content from the request body and deserialize them into generic type and you are done.

Here goes the YamlOutputFormatter.cs

  1. public class YamlOutputFormatter : TextOutputFormatter  
  2.     {  
  3.         private readonly Serializer _serializer;  
  4.   
  5.         public YamlOutputFormatter(Serializer serializer)  
  6.         {  
  7.             _serializer = serializer;  
  8.   
  9.             SupportedEncodings.Add(Encoding.UTF8);  
  10.             SupportedEncodings.Add(Encoding.Unicode);  
  11.             SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationYaml);  
  12.             SupportedMediaTypes.Add(MediaTypeHeaderValues.TextYaml);  
  13.         }  
  14.   
  15.         public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)  
  16.         {  
  17.             if (context == null)  
  18.             {  
  19.                 throw new ArgumentNullException(nameof(context));  
  20.             }  
  21.   
  22.             if (selectedEncoding == null)  
  23.             {  
  24.                 throw new ArgumentNullException(nameof(selectedEncoding));  
  25.             }  
  26.   
  27.             var response = context.HttpContext.Response;  
  28.             using (var writer = context.WriterFactory(response.Body, selectedEncoding))  
  29.             {  
  30.                 WriteObject(writer, context.Object);  
  31.   
  32.                 await writer.FlushAsync();  
  33.             }  
  34.         }  
  35.   
  36.         private void WriteObject(TextWriter writer, object value)  
  37.         {  
  38.             if (writer == null)  
  39.             {  
  40.                 throw new ArgumentNullException(nameof(writer));  
  41.             }  
  42.   
  43.             _serializer.Serialize(writer, value);  
  44.         }  
  45.     }   

In case you are wondering where the MediaTypeHeaderValues came from, it's a simple class where I've setup all the media type headers for my application.

  1. internal class MediaTypeHeaderValues  
  2. {  
  3.     public static readonly MediaTypeHeaderValue ApplicationYaml  
  4.         = MediaTypeHeaderValue.Parse("application/x-yaml").CopyAsReadOnly();  
  5.   
  6.     public static readonly MediaTypeHeaderValue TextYaml  
  7.         = MediaTypeHeaderValue.Parse("text/yaml").CopyAsReadOnly();  
  8. }   

Notice that the YamlInputFormatter’s constructor is accepting a Deserializerwhere YamlOutputFormatter’s constructor is accepting a Serializer. We build the Serializer and Deserializer with some options tweaking while configuring the formatters in the Startup.cs’s ConfigureServices method.

  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     // Add framework services.  
  4.     services.AddMvc(options=>  
  5.     {  
  6.         options.InputFormatters.Add(new YamlInputFormatter(new DeserializerBuilder().WithNamingConvention(namingConvention: new CamelCaseNamingConvention()).Build()));  
  7.         options.OutputFormatters.Add(new YamlOutputFormatter(new SerializerBuilder().WithNamingConvention(namingConvention: new CamelCaseNamingConvention()).Build()));  
  8.         options.FormatterMappings.SetMediaTypeMappingForFormat("yaml", MediaTypeHeaderValues.ApplicationYaml);  
  9.     });  
  10. }   

A simple GET request with Accept header set to application/x-yaml

.NET Core

A simple POST request with Content-Type header set to application/x-yaml

.NET Core

The formatter mapper is a slick option which can become very handy when calling the actions from a browser client with specified format. For example, setting up a [HttpGet("/api/[controller].{format}")] attribute will return the action result in the format defined in the browser’s url. 

  1. [FormatFilter]  
  2. [HttpGet]  
  3. [HttpGet("/api/[controller].{format}")]  
  4. public IEnumerable<Geek> Get()  
  5. {  
  6.     return new List<Geek>()  
  7.     {  
  8.         new Geek() { Id = 1, Name = "Fiyaz", Expertise="Javascript", Rating = 3.0M },  
  9.         new Geek() { Id = 2, Name = "Rick", Expertise = ".Net", Rating = 5.0M }  
  10.     };  
  11. }   

You can call the action like this, http://appurl/geeks.yaml to get the response in Yaml format or you can call it like, http://appurl/geeks.json to get the response in Json format.

.NET Core

And that’s it. This is all I know about building custom formatters for .NET Core. You can find a bunch of other formatters from other awesome community members scattered around the web if you want or build your own. I’ve added two other output formatters in my solution provided in the github repository, one for Pdf and the other one for Xlsx format. Here is the link for the repo.


Similar Articles