Custom Formatter with Content Negotiation in .NET Core Web API

Introduction

The process of choosing the best resource for a response when there are several resource representations available is known as content negotiation. Although content negotiation has been a feature of HTTP for some time, it may not be used as much as it could be for various reasons.

To put it briefly, content negotiation allows you to select, or more accurately, "negotiate," the content that is returned in response to a REST API request.

Custom Formatters

Suppose we are developing a public REST API and we need to enable content negotiation for non-boxed types.

Custom formats can be created using ASP.NET Core. Their goal is to provide you with the freedom to design your own format for any kind of media that you must handle.

The following technique can be used to create the custom formatter.

  • Make a class for output formatting that is derived from TextOutputFormatter.
  • Make a TextInputformatter class-derived input formatted class.
  • As with the XML format, add input and output classes to the InputFormatters and OutputFormatters collections.

As an illustration, let's put in place a custom CSV output format.

Implementing a Custom Formatter

We only need to implement an output formatter because, for the purposes of this article, formatting responses is all that is relevant. Only in the event that a request body contained a corresponding type would we require an input format.

The goal is to format a response that will yield a CSV file containing a list of blogs and the lists of blog posts that correspond with them.

To our project, let's add a CsvOutputFormatter class.

public class CsvOutputFormatter : TextOutputFormatter
{
    public CsvOutputFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csv"));
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    protected override bool CanWriteType(Type? type)
    {
        return typeof(Emp).IsAssignableFrom(type)
             || typeof(IEnumerable<Emp>).IsAssignableFrom(type);
    }

    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        var response = context.HttpContext.Response;
        var buffer = new StringBuilder();

        if (context.Object is IEnumerable<Emp>)
        {
            foreach (var emp in (IEnumerable<Emp>)context.Object)
            {
                FormatCsv(buffer, emp);
            }
        }

        await response.WriteAsync(buffer.ToString(), selectedEncoding);
    }

    private static void FormatCsv(StringBuilder buffer, Emp emp)
    {
        buffer.AppendLine($"{emp.Id},\"{emp.Name},\"{emp.Mno},\"{emp.Salary},\"{emp.Type}\"");
    }
}

Here are some points to consider.

We specify the media type and encodings that this formatter should parse in the constructor.

The overridden CanWriteType method indicates whether or not this serializer can write the Blog type. The response is created using the WriteResponseBodyAsync method. Lastly, the FormatCsv method allows us to format a response exactly how we want it.

Implementing the class is fairly simple, and the FormatCsv method logic should be your primary focus.

All that's left to do is include the recently created CsvOutputFormatter in the AddMvcOptions() method's list of OutputFormatters.

builder.Services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true;
    options.ReturnHttpNotAcceptable = true;

}).AddXmlSerializerFormatters()
.AddMvcOptions(options => options.OutputFormatters.Add(new CsvOutputFormatter()));

Let's try this now and see if it functions as intended. This time, in a Postman request, the application/CSV will be used as the value for the Accept header.

Postman request

Emp

After configuring the custom formatter, let's try using the JSON API responses and check if that is working or not.

 JSON API responses

Let's try using the XML API responses and see if that is working or not.

XML API responses

We learned the new technique and evolved together.

Happy coding!