Restrict accepted Media Types in ASP.NET Core Controller action
Asked Answered
P

2

19

I have an ASP.NET Core Service that produces both JSON and XML responses. However, I like to restrict the accepted Media Type for only one action, so Swagger can only list application/json as a valid response content type. How can I achieve this in ASP.Net Core?

Please, consider I am using ASP.Net Core (ASP.NET MVC 6), not ASP.NET WebAPI.

enter image description here

UPDATE

Ok, so I'll add the answer as part of the same question. Thanks to @Helen, I was able to add the required classes to achieve this in ASP.Net Core (ASP.Net MVC 6). The answer is based on this answer but modified to use ASP.NET Core classes.

Step 1. Create a custom action filter attribute so the pipeline reacts to a forbiden content type:

/// <summary>
/// SwaggerResponseContentTypeAttribute
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class SwaggerResponseContentTypeAttribute : ActionFilterAttribute
{
    /// <summary>
    /// SwaggerResponseContentTypeAttribute
    /// </summary>
    /// <param name="responseType"></param>
    public SwaggerResponseContentTypeAttribute(string responseType)
    {
        ResponseType = responseType;
    }
    /// <summary>
    /// Response Content Type
    /// </summary>
    public string ResponseType { get; private set; }

    /// <summary>
    /// Remove all other Response Content Types
    /// </summary>
    public bool Exclusive { get; set; }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var accept = context.HttpContext.Request.Headers["accept"];
        var accepted = accept.ToString().ToLower().Contains(ResponseType.ToLower());
        if (!accepted)
            context.Result = new StatusCodeResult((int)HttpStatusCode.NotAcceptable); 

    }

}

Step 2. Create a Swagger Operation Filter so the UI can reflect the restriction

public class ResponseContentTypeOperationFilter : IOperationFilter
{

    public void Apply(Swashbuckle.AspNetCore.Swagger.Operation operation, OperationFilterContext context)
    {
        var requestAttributes = context.ControllerActionDescriptor.GetControllerAndActionAttributes(true).Where(c=>c.GetType().IsAssignableFrom(typeof(SwaggerResponseContentTypeAttribute))).Select(c=> c as SwaggerResponseContentTypeAttribute).FirstOrDefault();

        if (requestAttributes != null)
        {
            if (requestAttributes.Exclusive)
                operation.Produces.Clear();

            operation.Produces.Add(requestAttributes.ResponseType);
        }
    }
}

Step 3. Configure Swagger UI service in Startup.cs, inside the method ConfigureServices, so it can use the newly created Operation Filter.

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        services.Configure<MvcOptions>(options =>
        {
            options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());

        });
        // Register the Swagger generator, defining 1 or more Swagger documents
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
            c.OperationFilter<ResponseContentTypeOperationFilter>();
        });
    }

Step 4. Annotate the action

    // GET api/values
    [HttpGet]
    [WebService.Utils.SwaggerResponseContentType(responseType: "application/json", Exclusive = true)]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
Predominant answered 3/7, 2018 at 16:8 Comment(6)
Possible duplicate of Swashbuckle Swagger - How to annotate content types? and swashbuckle mediatype application/octetstreamOctavie
@Octavie thanks for the link. The answer was related to ASP.Net Web API. However with some minor modifications, it can be migrated to ASP.Net Core. Still, that only solves the problem that Swagger will only show application/json, but in the background, I could still perform a request with Accept: application/xml and it will work. I want to actually restrict the accepted media types in the controller action.Predominant
Does this help?Octavie
What about options.OutputFormatters.RemoveType<XmlDataContractSerializerOutputFormatter>()?Brim
The key is that I want XML serializer available in general. I just want it not to be available for a particular action.Predominant
I want this too, why dont have have an attribute for this yet?Attica
O
38

You can use the annotations Consumes and Produces. These are also picked up by Swashbuckle. Like so:

[HttpGet]
[Consumes("application/xml")]
[Produces("application/xml")]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}
Outrun answered 24/1, 2019 at 8:39 Comment(0)
D
15

I know it is an old question but here I was trying to do that, so, in order to make it easier for other people I'll add to @joeystdio's answer with a "generic" way to add the same produce/consume attribute for all endpoints. (for MY use case, it's easier to setup things for all endpoints than going one by one.

.AddControllers(options  => 
{
    options.Filters.Add(new ProducesAttribute("application/json"));
    options.Filters.Add(new ConsumesAttribute("application/json"));

    // if you need a specific response type.
    options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApplicationNotification), StatusCodes.Status500InternalServerError));
})
Developing answered 9/9, 2021 at 0:28 Comment(3)
Not sure why this was downvoted, this fixes it across the board! Pro tip: you can use MediaTypeNames.Application.Json instead of hard coding the string.Mcmillan
Noice. I didn't know about the MediaTypeNames, that's pretty noice to keep consistency. I had a constant with a few constants and stuff like that. easier to use the proper class.Developing
Fan-Fan-Fantastic! All good stuff, but I would say this is the correct answer now.Counterintelligence

© 2022 - 2024 — McMap. All rights reserved.