Share via


ASP.NET Core Custom Request/Response Formatters (Yaml Formatters)

This article shows you how to make custom request/response formatter in ASP.NET Core

.Net Core gives you some formatters out of the box. This official documentation link described them briefly,

Introduction to formatting response data in ASP.NET Core MVC

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 TextOutputFormatter can work with response that are simple string representations of data formats.

When using Yaml the 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 defines a Accept header (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 Accept header or not. Again using the [Consumes(application/x-yaml)] attribute would only accept Yaml content whether you define the Content-Type or not.

Yaml Output Formatter

public class YamlOutputFormatter : TextOutputFormatter{  private readonly Serializer _serializer;   public YamlOutputFormatter(Serializer serializer)  {    _serializer = serializer;     SupportedEncodings.Add(Encoding.UTF8);    SupportedEncodings.Add(Encoding.Unicode);    SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationYaml);    SupportedMediaTypes.Add(MediaTypeHeaderValues.TextYaml);  }   public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)  {    if (context == null)    {        throw new ArgumentNullException(nameof(context));    }     if (selectedEncoding == null)    {        throw new ArgumentNullException(nameof(selectedEncoding));    }     var response = context.HttpContext.Response;    using (var writer = context.WriterFactory(response.Body, selectedEncoding))    {        WriteObject(writer, context.Object);         await writer.FlushAsync();    }  }   private void WriteObject(TextWriter writer, object value)  {    if (writer == null)    {        throw new ArgumentNullException(nameof(writer));    }     _serializer.Serialize(writer, value);  }}

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.

Yaml Input Formatter

public class YamlInputFormatter : TextInputFormatter
{
  private readonly Deserializer _deserializer;
 
  public YamlInputFormatter(Deserializer deserializer)
  {
    _deserializer = deserializer;
 
    SupportedEncodings.Add(Encoding.UTF8);
    SupportedEncodings.Add(Encoding.Unicode);
    SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationYaml);
    SupportedMediaTypes.Add(MediaTypeHeaderValues.TextYaml);
  }
 
  public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
  {
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }
 
    if (encoding == null)
    {
        throw new ArgumentNullException(nameof(encoding));
    }
 
    var request = context.HttpContext.Request;
 
    using (var streamReader = context.ReaderFactory(request.Body, encoding))
    {
        var type = context.ModelType;
 
        try
        {
            var model = _deserializer.Deserialize(streamReader, type);
            return InputFormatterResult.SuccessAsync(model);
        }
        catch (Exception)
        {
            return InputFormatterResult.FailureAsync();
        }
    }
  }
}
MediaTypeHeaderValues a simple class where I've setup all the media type headers for my application.
 
internal class MediaTypeHeaderValues
{
  public static readonly MediaTypeHeaderValue ApplicationYaml
      = MediaTypeHeaderValue.Parse("application/x-yaml").CopyAsReadOnly();
 
  public static readonly MediaTypeHeaderValue TextYaml
      = MediaTypeHeaderValue.Parse("text/yaml").CopyAsReadOnly();
}

Notice that the YamlInputFormatter’s constructor is accepting a Deserializer where 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.

public void ConfigureServices(IServiceCollection services){// Add framework services.  services.AddMvc(options=>  {    options.InputFormatters.Add(new YamlInputFormatter(new DeserializerBuilder().WithNamingConvention(namingConvention: new CamelCaseNamingConvention()).Build()));    options.OutputFormatters.Add(new YamlOutputFormatter(new SerializerBuilder().WithNamingConvention(namingConvention: new CamelCaseNamingConvention()).Build()));    options.FormatterMappings.SetMediaTypeMappingForFormat("yaml", MediaTypeHeaderValues.ApplicationYaml);  });}

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

https://3.bp.blogspot.com/-ZgvEUtst1Iw/WI4JDhcKODI/AAAAAAAAA3A/LlBArPwr1PY3VRN9SPHMXZv8_5AZeRDJwCLcB/s1600/1.png

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

https://4.bp.blogspot.com/-f-Sf-4kEEj4/WI4JDkS-uvI/AAAAAAAAA24/tk6iFT4BGQQ9p8l3vsifNsi5rOuN1bkbwCLcB/s1600/2.png

Formatter mapper lets you call an Action with a specific format directly through the url. Setting up [HttpGet("/api/[controller].{format}")] a attribute will return the action result in the format defined in the browser’s url.Action``[HttpGet("/api/[controller].{format}")]

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

https://1.bp.blogspot.com/-624h3svql1A/WI4JDu9dEDI/AAAAAAAAA28/6Ar7_IWcXjsamhafvbSCSqN1AQlCjzo1wCLcB/s1600/3.png

Dependencies

I’m using the YamlDotNet library from Antoine Aubry for Yaml’s serializing and desirializing process.

This repository contains some .NET Core Custom Formatters. Includes Yaml Input/Output Formatter, Pdf Output Formatter, Xlsx Output Formatter

Repository link: Custom Formatters for ASP.NET Core