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.
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.
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
A simple POST
request with Content-Type
header set to application/x-yaml
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 }
};
}
I’m using the YamlDotNet library from Antoine Aubry for Yaml’s serializing and desirializing process.