Andrew
Andrew

Reputation: 5430

Using FormatFilter attribute within mvc core 2.1.3 API

I'm facing som difficulties getting the [FormatFilter] to work in my MVC Core 2.1.3 API. I want my endpoint to support JSON and XML, so I wrote this code:

Startup class, which inherits from a StartupCore class:

public class Startup : StartupCore
{
    protected override void OnConfigure(
        IApplicationBuilder app,
        IHostingEnvironment env,
        ILoggerFactory loggerFactory,
        IApplicationLifetime appLifetime) => AutoMappings.Initialize();
}

And (partially) this StartupCore class

//....
services
    .AddCors()
    .AddMvcCore()
    .AddApiExplorer()
    .AddJsonFormatters()
    .AddXmlSerializerFormatters() //With or without this line; no luck
    .AddJsonOptions(options =>
    {
        options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
    })
    .AddMvcOptions(options =>
    {
        options.InputFormatters.Add(new PlainTextInputFormatter());
        options.OutputFormatters.Add(new CsvOutputFormatter());
        options.FormatterMappings.SetMediaTypeMappingForFormat("csv", MediaTypeHeaderValue.Parse("text/csv"));
        options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
        options.FormatterMappings.SetMediaTypeMappingForFormat("xml", MediaTypeHeaderValue.Parse("application/xml"));
    })
//.......

When I use the FormatFilter attribute on my controller like

[HttpGet]
[FormatFilter]
[Route("/public/feed/{format}")]
public async Task<IActionResult> CreateFeed(string format)
{
    //
}

I'm getting the error: Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'Microsoft.AspNetCore.Mvc.Formatters.FormatFilter' has not been registered.

However when I use the Produces attribute it gives me XML data.

[HttpGet]
[Produces("application/xml")]
[Route("/public/feed/{format}")]
public async Task<IActionResult> CreateFeed(string format)
{
    //
}

I could end up with two endpoints; one for JSON and one for XML but I rather have one endpoint with the FormatFilter.

So what am I missing here?

WORKAROUND: For now I'm using the Produces attribute [Produces("application/json", "application/xml"]

Source used: https://andrewlock.net/formatting-response-data-as-xml-or-json-based-on-the-url-in-asp-net-core/

Upvotes: 2

Views: 4581

Answers (3)

MarkSci
MarkSci

Reputation: 181

I also had this problem with my .Net Core 2.1 WebAPI project and discovered that I needed to explicitly set the "Accept" Request Header as part of my Ajax call. Within my original .Net 4.7 Web API project setting the dataType property to either 'json' or 'xml' was enough for the correct content type to be returned but it appears not for .Net Core. My Ajax call is as per below:

$.ajax({
    type: 'GET',
    beforeSend: function (request)
    {
        request.setRequestHeader("Accept", "application/xml");
        // or for json request.setRequestHeader("Accept", "application/json");
    },
    traditional: true,
    url: uri,
    contentType: 'application/json; charset=utf-8',
    dataType: 'xml',
    // or for json dataType: 'json',
    success: function (data)
    {
        ...
    },
    error: function (req, status, err)
    {
        ...
    }
});

I also needed to prevent caching to ensure the correct Accept header was passed to my controller method each time by setting the NoStore property within the ResponseCache decorator:

    [ResponseCache(NoStore = true)]
    public ActionResult Get(string customerName)
    {
       ...
    }

Upvotes: 0

Igor Pchelko
Igor Pchelko

Reputation: 318

If endpoint or controller uses FormatFilter attribute then the corresponding service should be registered via AddFormatterMappings:

services.AddMvcCore()
...
.AddFormatterMappings()

Upvotes: 2

Chris Pratt
Chris Pratt

Reputation: 239290

In your StartupCore, you're relying on the OnMvcCoreServiceConfiguration event to add your XML output formatter. That is going to trigger on the line where you you call AddMvcCore. Then, later, you call AddMvcOptions again, which is going to override the previous call you made. In that second call, you don't add the XML formatter, so it never actually gets added.

You need to pay attention to the asynchronous nature of what's happening. You're passing an action that will be called at some point in the app startup, so the first time time you call AddMvcOptions, nothing is actually happening with that yet. When you later call it again, you're setting a new action that it will use eventually, replacing the one you set previously.

Upvotes: 1

Related Questions