Skip to content

Latest commit

 

History

History
158 lines (122 loc) · 7.37 KB

README.md

File metadata and controls

158 lines (122 loc) · 7.37 KB

.NET Core Custom Formatters

This repository used to contain custom formatters for pdf and excel. They are removed from main branch as the libraries are deprecated. Please check previous commits if you are interested in those.

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

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

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.

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 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

alt text

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

alt text

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

[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 }
    };
}

alt text

Dependencies

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