Introduction
Quite recently, I got myself 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 described them briefly.
But let’s not talk too much about formatters rather 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 the 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 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.
‘Nuff 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
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();
}
}
}
}
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:
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);
}
}
In case you are wondering, from where the MediaTypeHeaderValues
came from? It's 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)
{
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
:
The formatter mapper is a slick option which can come in very handy when calling the actions from a browser client with a 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.
[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 }
};
}
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.
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.
History
- 22nd March, 2017: Initial version