Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restrict accepted Media Types in ASP.NET Core Controller action

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" };
    }
like image 270
Alfredo A. Avatar asked Jul 03 '18 16:07

Alfredo A.


People also ask

What is the use of ApiController attribute?

The [ApiController] attribute applies inference rules for the default data sources of action parameters. These rules save you from having to identify binding sources manually by applying attributes to the action parameters.

What is the difference between controller and ControllerBase?

Controller derives from ControllerBase and adds support for views, so it's for handling web pages, not web API requests. There's an exception to this rule: if you plan to use the same controller for both views and web APIs, derive it from Controller.

What is OkObjectResult?

OkObjectResult. An ObjectResult, when executed, performs content negotiation, formats the entity body, and will produce a Status200OK response if negotiation and formatting succeed. public OkObjectResult OkObjectResult() { return new OkObjectResult(new { Message="Hello World !"

What is ProducesResponseType?

This attribute produces more descriptive response details for web API help pages generated by tools like Swagger. [ProducesResponseType] indicates the known types and HTTP status codes to be returned by the action.


1 Answers

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));
})
like image 128
Morilon Avatar answered Oct 05 '22 14:10

Morilon