How can I tell Swashbuckle that the body content i

2019-03-28 06:07发布

问题:

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?

回答1:

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:



回答2:

In Swashbuckle 4.0 the syntax has changed a little:

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; }
}

public class BinaryPayloadFilter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        var attribute = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
            .Union(context.MethodInfo.GetCustomAttributes(true))
            .OfType<BinaryPayloadAttribute>().FirstOrDefault();
        if (attribute == null)
        {
            return;
        }

        operation.Consumes.Clear();
        operation.Consumes.Add(attribute.MediaType);

        operation.Parameters.Add(new BodyParameter
        {
            Name = attribute.ParameterName,
            @In = "body",
            Required = attribute.Required,                                
            Schema = new Schema
            {
                Type = "string",
                Format = attribute.Format
            }
        });
    }
}

And:

services.AddSwaggerGen(c =>
{
    c.OperationFilter<BinaryPayloadFilter>();
});


回答3:

Here's an updated version of @venerik's answer. This works on Swashbuckle 2.3.0:

The custom attribute:

/// <summary>
/// Represents controller actions that accept a binary payload.
/// </summary>
public class BinaryPayloadAttribute : Attribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="BinaryPayloadAttribute"/> class.
    /// </summary>
    public BinaryPayloadAttribute()
    {
        ParameterName = "payload";
        Required = true;
        MediaType = "application/octet-stream";
        Format = "binary";
    }

    /// <summary>
    /// Gets or sets the payload format.
    /// </summary>
    public string Format { get; set; }

    /// <summary>
    /// Gets or sets the payload media type.
    /// </summary>
    public string MediaType { get; set; }

    /// <summary>
    /// Gets or sets a required flag.
    /// </summary>
    public bool Required { get; set; }

    /// <summary>
    /// Gets or sets a parameter name.
    /// </summary>
    public string ParameterName { get; set; }
}

The custom filter:

/// <summary>
/// Filter for a controller action that accept a binary payload.
/// </summary>
public class BinaryPayloadFilter : IOperationFilter
{
    /// <summary>
    /// Applies the specified operation.
    /// </summary>
    /// <param name="operation">The operation.</param>
    /// <param name="context">The context.</param>
    public void Apply(Operation operation, OperationFilterContext context)
    {
        BinaryPayloadAttribute attribute = context.ApiDescription.ActionAttributes().FirstOrDefault(x => x is BinaryPayloadAttribute) as BinaryPayloadAttribute;
        if (attribute == null)
        {
            return;
        }

        operation.Consumes.Clear();
        operation.Consumes.Add(attribute.MediaType);

        operation.Parameters.Add(new BodyParameter
        {
            Name = attribute.ParameterName,
            Required = attribute.Required
        });
    }
}

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)
{
    ...
}