Swashbuckle.webapi: Response headers not being displayed in UI

Created on 16 Feb 2016  路  7Comments  路  Source: domaindrivendev/Swashbuckle.WebApi

I wrote a custom SwaggerResponseHeader attribute to document response headers. I also wrote a custom AddResponseHeadersFilter to add the documentation to the swagger output. I then used the attribute on one of the controller actions. All works fine. The swagger output contains the specified response header and the editor at editor.swagger.io nicely displays the response header.

My API's swagger UI however does not display the response header. Is there a way to fix that?

Attribute

public class SwaggerResponseHeaderAttribute : Attribute
{
    public SwaggerResponseHeaderAttribute(HttpStatusCode statusCode, string name, string type, string description)
    {
        StatusCode = statusCode;
        Name = name;
        Type = type;
        Description = description;
    }

    public HttpStatusCode StatusCode { get; set; }
    public string Name { get; set; }
    public string Type { get; set; }
    public string Description { get; set; }

}

Filter

public class AddResponseHeadersFilter : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        var responseAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerResponseHeaderAttribute>();

        foreach (var attr in responseAttributes)
        {
            var response = operation.responses.FirstOrDefault(x => x.Key == ((int)attr.StatusCode).ToString(CultureInfo.InvariantCulture)).Value;

            if (response != null)
            {
                if (response.headers == null)
                {
                    response.headers = new Dictionary<string, Header>();
                }

                response.headers.Add(attr.Name, new Header {description = attr.Description, type = attr.Type});
            }
        }
    }
}

Controller

[SwaggerResponseRemoveDefaults]
[SwaggerResponse(HttpStatusCode.Created, "Created", typeof(Person))]
[SwaggerResponseHeader(HttpStatusCode.Created, "Location", "string", "Location of the newly created resource")]
public IHttpActionResult Post(Person person)
{
    //...
}        

Documentation (part)

{
    "responses":{
        "201":{
            "description":"Created",
            "schema":{
                "$ref":"#/definitions/Person"
            },
            "headers":{
                "Location":{
                    "description":"Location of the newly created resource.",
                    "type":"string"
                }
            }
        }
    }
}

Most helpful comment

@WhitWaldo yep, I ported it to OpenApi almost a year ago.

https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters/blob/master/src/Swashbuckle.AspNetCore.Filters/ResponseHeaders/AddResponseHeadersFilter.cs

Or you can get it from my NuGet (Swashbuckle.AspNetCore.Filters).

All 7 comments

Unfortunately the swagger editor and swagger ui are separate products. According to the devs doing swagger-ui - some of the editor features might get implemented into swagger-ui.

This is, unfortunately not a swashbuckle issue :)

@VisualBean is correct. If there's not a similar issue logged already, you should submit this here: https://github.com/swagger-api/swagger-ui

Thanks venerik! I updated this to work with Swashbuckle 6.0. Note the attribute name change to match the way 6.0 uses ProducesResponseType instead of SwaggerResponse and ints to describe the status code.

Attribute:

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class ProducesResponseTypeHeader : Attribute
    {
        public ProducesResponseTypeHeader(int statusCode, string name, string type, string description)
        {
            StatusCode = statusCode;
            Name = name;
            Type = type;
            Description = description;
        }

        public int StatusCode { get; }
        public string Name { get; }
        public string Type { get; }
        public string Description { get; }

    }
}

Filter:

 public class AddResponseHeadersFilter : IOperationFilter
    {

        public void Apply(Operation operation, OperationFilterContext context)
        {
            var responseAttributes = context.ApiDescription.GetActionAttributes().OfType<ProducesResponseTypeHeader>();

            foreach (var attr in responseAttributes)
            {
                var response = operation.Responses.FirstOrDefault(x => x.Key == ((int)attr.StatusCode).ToString(CultureInfo.InvariantCulture)).Value;

                if (response != null)
                {
                    if (response.Headers == null)
                    {
                        response.Headers = new Dictionary<string, Header>();
                    }

                    response.Headers.Add(attr.Name, new Header { Description = attr.Description, Type = attr.Type });
                }
            }
        }
    }

Controller:

        [HttpGet("{id}")]
        [ProducesResponseTypeHeader(200, "X-Total-Count", "int", "Total Records in result")]
        [ProducesResponseTypeHeader(200, "Other Stuff", "string", "Other Stuff Description")]
        [ProducesResponseType(typeof(SampleResult[]), 200)]
        [ProducesResponseTypeHeader(400, "More Stuff", "string", "More Stuff Description")]
        [ProducesResponseType(typeof(string),400)]       
        public async Task<IActionResult> Get(int id) ...

FWIW @venerik's code above seems to work fine 2 years later, with Swashbuckle 5.6.0.

image

I'm currently adding this functionality to my existing NuGet packages, for those too lazy to copy and paste code.

https://www.nuget.org/packages/Swashbuckle.Examples/
https://www.nuget.org/packages/Swashbuckle.AspNetCore.Examples/

Just a small recommendation, the GetCustomAttributes has been depreciated and the code should now look like this:

public class AddResponseHeadersFilter  : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        var responseAttributes = context.MethodInfo.GetCustomAttributes<ProducesResponseTypeHeader>();

        foreach (var attr in responseAttributes)
        {
            var response = operation.Responses.FirstOrDefault(
                x => x.Key == ((int)attr.StatusCode).ToString(CultureInfo.InvariantCulture)).Value;

            if (response != null)
            {
                if (response.Headers == null)
                    response.Headers = new Dictionary<string, Header>();

                response.Headers.Add(attr.Name, new Header { 
                    Description = attr.Description, Type = attr.Type });
            }
        }

    }
}

Small tweak to @rocklan 's response to accommodate 5.0.0-rc5 (and the shift to OpenApi):

public class AddResponseHeadersFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var responseAttributes = context.MethodInfo.GetCustomAttributes();

    foreach (var attr in responseAttributes)
    {
        var response = operation.Responses.FirstOrDefault(
            x => x.Key == ((int)attr.StatusCode).ToString(CultureInfo.InvariantCulture)).Value;

        if (response != null)
        {
            if (response.Headers == null)
                response.Headers = new Dictionary<string, OpenApiHeader>();

            response.Headers.Add(attr.Name, new Header { 
                Description = attr.Description,
                Content = new Dictionary<string, OpenApiMediaType> { 
                      [attr.Type] = new OpenApiMediaType()
                }
            });
        }
    }

}

}

@WhitWaldo yep, I ported it to OpenApi almost a year ago.

https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters/blob/master/src/Swashbuckle.AspNetCore.Filters/ResponseHeaders/AddResponseHeadersFilter.cs

Or you can get it from my NuGet (Swashbuckle.AspNetCore.Filters).

Was this page helpful?
0 / 5 - 0 ratings