I think it's safe to say that most ASP.NET Core applications that use a Web API return data as JSON. What with JavaScript in the browser, and JSON parsers everywhere you look, this makes perfect sense. Consequently, ASP.NET Core is very much geared towards JSON, but it is perfectly possible to return data in other formats (for example Damien Bowden recently added a Protobuf formatter to the WebApiContrib.Core project).

In this post, I'm going to focus on a very specific scenario. You want to be able to return data from a Web API action method in one of two different formats - JSON or XML, and you want to control which format is used by the extension of the URL. For example /api/Values.xml should format the result as XML, while /api/Values.json should format the result as JSON.

Using the FormatFilterAttribute to read the format from the URL

Out of the box, if you use the standard MVC service configuration by calling services.AddMvc(), the JSON formatters are configured for your application by default. All that you need to do is tell your action method to read the format from the URL using the FormatFilterAttribute.

You can add the [FormatFilter] attribute to your action methods, to indicate that the output format will be defined by the URL. The FormatFilter looks for a route parameter called format in the RouteData for the request, or in the querystring. If you want to use the .json approach I described earlier, you should make sure the route template for your actions includes a .{format} parameter:

public class ValuesController : Controller  
{
    // GET api/values
    [HttpGet("api/values.{format}"), FormatFilter]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

Note: You can make the .format suffix optional using the syntax .{format?}, but you need to make sure the . follows a route parameter, e.g. api/values/{id}.{format?}. If you try to make the format optional in the example above (api/values.{format?}) you'll get a server error. A bit odd, but there you go…

With the route template updated, and the [FormatFilter] applied to the method, we can now test our JSON formatters:

Json result formatted

Success - we have returned JSON when requested! Let's give it a try with the xml suffix:

Requesting unsupported format

Doh, no such luck. As I mentioned earlier, the JSON formatters are registered by default; if we want to return XML then we'll need to configure the XML formatters too.

Adding the XML formatters

In ASP.NET Core, everything is highly modular, so you only add the functionality you need to your application. Consequently, there's a separate NuGet package for the XML formatters that you need to add to your .csproj file - Microsoft.AspNetCore.Mvc.Formatters.Xml

<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Xml" Version="1.1.3" />  

Note: If you're using ASP.NET Core 2.0, this package is included by default as part of the Microsoft.AspNetCore.All metapackage.

Adding the package to your project lights up an extension method on the IMvcBuilder instance returned by the call to services.AddMvc(). The AddXmlSerializerFormatters() method adds both input and output formatters, so you can serialise objects to and from XML.

public void ConfigureServices(IServiceCollection services)  
{
    services.AddMvc()
        .AddXmlSerializerFormatters();
}

Alternatively, if you only want to be able to format results as XML, but don't need to be able to read XML from a request body, you can just add the output formatter instead:

public void ConfigureServices(IServiceCollection services)  
{
    services.AddMvc(options =>
    {
        options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
    });
}

By adding this output formatter we can now format objects as XML. However, if you test the XML URL again at this point, you'll still get the same 404 response as we did before. What gives?

Registering a type mapping for the format suffix

By registering the XML formatters, we now have the ability to format XML. However, the FormatFilter doesn't know how to handle the .xml suffix we're using in the request URL. To make this work, we need to tell the filter that the xml suffix maps to the application/xml MIME type.

You can register new type mappings by configuring the FormatterMappings options, when you call AddMvc(). These define the mappings between the {format} parmeter and the MIME type that the FormatFilter will use. For example:

public void ConfigureServices(IServiceCollection services)  
{
    services.AddMvc(options =>
    {
        options.FormatterMappings.SetMediaTypeMappingForFormat
            ("xml", MediaTypeHeaderValue.Parse("application/xml"));
        options.FormatterMappings.SetMediaTypeMappingForFormat
            ("config", MediaTypeHeaderValue.Parse("application/xml"));
        options.FormatterMappings.SetMediaTypeMappingForFormat
            ("js", MediaTypeHeaderValue.Parse("application/json"));
    })
        .AddXmlSerializerFormatters();

The FormatterMappings property contains a dictionary of all the suffix to MIME type mappings. You can add new ones using the SetMediaTypeMappingForFormat, passing the suffix as the key and the MIME type as the value.

In the example above I've actually registered three new mappings. I've added the xml and config files as XML, and added a new js suffix that maps to JSON, just to demonstrate that JSON isn't actually anything special here!

With this last piece of configuration in place, we can now finally request XML by using the .xml or .config suffix in our URLs:

XML

Summary

In this post you saw how to use the FormatFilter to specify the desired output format by using a file-type suffix on your URLs. To do so, there were four steps:

  1. Add the [FormatFilter] attribute to your action method
  2. Ensure the route to the action contains a {format} route parameter (or pass it in the querystirng e.g. ?format=xml)
  3. Register the output formatters you wish to support with MVC. To add both input and output XML formatters, use the AddXmlSerializerFormatters() extensions method
  4. Register a new type mapping between a format suffix and a MIME type on the MvcOptions object. For example, you could add XML using:
options.FormatterMappings.SetMediaTypeMappingForFormat(  
    "xml", MediaTypeHeaderValue.Parse("application/xml"));

If a type mapping is not configured for a suffix, then you'll get a 404 Not Found response when calling the action.