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?
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:
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>();
});
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)
{
...
}