2

The API I'm building returns json almost everywhere, but I sometimes need to return xml with a custom formatter.

I created a custom formatter (I didn't copy the WriteResponseBodyAsync to keep it shorter)

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

    protected override bool CanWriteType(Type type)
    {
        return true; // I tried doing this to force it to work on any type, 
                     // but I don't think it's needed
    }

    public override bool CanWriteResult(OutputFormatterCanWriteContext context)
    {
        if (context == null) 
            throw new ArgumentNullException(nameof(context));

        return context.ContentType.ToString() == "application/xml";
    }
}

It works when I have

[Produces("application/xml")]

on a controller method, but when I don't use this and ask for xml in postman using the Accept:application/xml header it still returns json.

Postman

I added the formatter in Startup.cs like so:

services
    .AddMvc(options =>
    {
        options.OutputFormatters.Add(new XmlOutputFormatter());
    });

I know the formatter works the issue is that asp seems to ignore the Accept header and always returns json. I know I could simply force it to return xml, but I would prefer returning xml.

I know there is a default xml formatter, but it doesn't behave exactly like I need it to.

I'm using asp.net core 2.0 and visual studio professional 2017 15.3 preview 6

2 Answers2

1

This answer is a bit hasty... Hopefully it will cover enough to answer your question.

Check your Content-Type and Accept

You need to make sure that your Content-Type and Accept headers in your request are set properly. In your case (application/xml).

I have a full example in this answer. Note that I use AddMvcCore (see next section below)

Use AddMvcCore instead.

The key here is to not use the default AddMvc method and switch to AddMvcCore instead for a higher degree of control of the middleware.

The reason being, is that the order you add your options will greatly impact the way the MVC middleware will behave. In my experience with dealing with any API using .NET Core, I avoid AddMvc in order to get full control over the middleware.

There really is not much to fear here, as you can see that AddMvc is nothing more than a template that configures the options for AddMvcCore. You can see the source at the repository here.

I've grabbed a code snippet from my related answer (maybe want to take a read for a more in-depth response to how AddMvcCore works.

public void ConfigureServices(IServiceCollection services)
{
    // Build a customized MVC implementation, without using the default AddMvc(),
    // instead use AddMvcCore(). The repository link is below:
    // https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs

    services
        .AddMvcCore(options =>
        {
            options.RequireHttpsPermanent = true; // this does not affect api requests
            options.RespectBrowserAcceptHeader = true; // false by default
                                                       //options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();

            // these two are here to show you where to include custom formatters
            options.OutputFormatters.Add(new CustomOutputFormatter());
            options.InputFormatters.Add(new CustomInputFormatter());
        })
        //.AddApiExplorer()
        //.AddAuthorization()
        .AddFormatterMappings()
        //.AddCacheTagHelper()
        //.AddDataAnnotations()
        //.AddCors()
        .AddJsonFormatters();
}

Your Custom Formatter

Just a little bit of a nudge, but no real serious concerns, here is a cleaner implementation of your custom formatter which might get you a bit more mileage.

public class CustomOutputFormatter : OutputFormatter
{
    public string ContentType { get; private set; }

    public CustomOutputFormatter()
    {
        ContentType = "application/custom";
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/custom"));
    }

    protected override bool CanWriteType(Type type)
    {
        return true;
    }

    public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
    {
        var response = context.HttpContext.Response;

        // serialize it.
        byte[] serializedBytes = ...
        response.Body.WriteAsync(serializedBytes, 0, serializedBytes.Length).ConfigureAwait(false);

        return Task.FromResult(response);
    }
}
Svek
  • 10,726
  • 4
  • 32
  • 63
  • Thank you very much for the detailed answer I read it all and it seems like the right answer I will try it out tomorrow as soon as I have access to my computer. – Charles Giguere Jul 31 '17 at 23:11
  • 1
    I tried it and it works perfectly thank you. At first I had an error because I needed the .AddApiExplorer() middleware, but I'm not sure why I'll need to do some research. – Charles Giguere Aug 01 '17 at 13:20
-1

Try to add this to your Startup.cs file:

services.AddMvc(options =>
{
  options.RespectBrowserAcceptHeader = true; // false by default
}
anserk
  • 1,182
  • 1
  • 7
  • 15
  • I already tried that. This is not the same thing. This is to allow the default browser accept header instead of sending json everytim. This is not for custom request header. – Charles Giguere Jul 28 '17 at 13:30