Search code examples
c#asp.net-web-apihttprequestswagger-2.0swashbuckle

How can I tell Swashbuckle that the body content is required?


I have a WebAPI controller that accepts binary packages and stores them somewhere. As these packages can become quite large, I don't want to load them into memory by adding a byte array parameter but rather pass along a stream.

I found a way to do that in this answer:

[HttpPost]
[Route("Store/{projectId}")]
public async Task Store(string projectId)
{
    using (var stream = await this.Request.Content.ReadAsStreamAsync())
    {
        await this.packageManager.StorePackageAsync(projectId, stream);
    }
}

This works, I can send files to the controller using Postman. However, I now want to generate swagger documentation with Swashbuckle and of course, the required body content is not mentioned there.

Is there a way to get a stream of the request's content so that Swashbuckle knows about it? Or is there an attribute I can use to tell it about the required content?


Solution

  • To achieve this you have to do a couple of things.

    First you have to tell Swagger there's a parameter in the body that contains binary data. Next you have to tell Swagger that the end point consumes binary data (e.g. application/octet-stream).

    Swashbuckle does not support this out of the box. But you can create custom filters to extend the functionality of Swashbuckle. What I usually do is create a custom attribute to decorate a method and then create a custom filter to act upon that attribute.

    In your case this would do the trick:

    The custom attribute

    public class BinaryPayloadAttribute : Attribute
    {
        public BinaryPayloadAttribute()
        {
            ParameterName = "payload";
            Required = true;
            MediaType = "application/octet-stream";
            Format = "binary";
        }
    
        public string Format { get; set; }
    
        public string MediaType { get; set; }
    
        public bool Required { get; set; }
    
        public string ParameterName { get; set; }
    }
    

    The custom filter

    public class BinaryPayloadFilter : IOperationFilter
    {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            var attribute = apiDescription.GetControllerAndActionAttributes<BinaryPayloadAttribute>().FirstOrDefault();
            if (attribute == null)
            {
                return;
            }
    
            operation.consumes.Clear();
            operation.consumes.Add(attribute.MediaType);
    
            operation.parameters.Add(new Parameter
            {
                name = attribute.ParameterName,
                @in = "body", 
                required = attribute.Required,
                type = "string", 
                format = attribute.Format
            });
        }
    }
    

    Add the filter to the Swashbuckle configuration

    GlobalConfiguration.Configuration 
        .EnableSwagger(c => 
            {
                // other configuration setting removed for brevity
                c.OperationFilter<BinaryPayloadFilter>();
            });
    

    Apply the attribute to your method

    [HttpPost]
    [BinaryPayload]
    [Route("Store/{projectId}")]
    public async Task Store(string projectId)
    {
        ...
    }
    

    In Swagger UI you then get:

    Swagger UI