Как я могу сказать Swashbuckle, что требуется содержимое тела?

У меня есть контроллер WebAPI, который принимает двоичные пакеты и где-то их хранит. Поскольку эти пакеты могут стать довольно большими, я не хочу загружать их в память, добавляя параметр байтового массива, а скорее передавать их по потоку.

Я нашел способ сделать это в этом ответе:

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

Это работает, я могу отправлять файлы на контроллер с помощью Postman. Однако теперь я хочу сгенерировать документацию Swagger с помощью Swashbuckle, и, конечно же, требуемое содержимое тела там не упоминается.

Есть ли способ получить поток содержимого запроса, чтобы Swashbuckle знал об этом? Или есть атрибут, который я могу использовать, чтобы сообщить ему о необходимом содержании?


person Daniel Sklenitzka    schedule 14.12.2016    source источник


Ответы (4)


Для этого вам нужно сделать несколько вещей.

Сначала вы должны сообщить Swagger, что в теле есть параметр, содержащий двоичные данные. Затем вы должны сообщить Swagger, что конечная точка потребляет двоичные данные (например, приложение / поток октетов).

Swashbuckle не поддерживает это из коробки. Но вы можете создавать собственные фильтры, чтобы расширить функциональность Swashbuckle. Обычно я создаю настраиваемый атрибут для украшения метода, а затем создаю настраиваемый фильтр для воздействия на этот атрибут.

В вашем случае это поможет:

Настраиваемый атрибут

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

Добавьте фильтр в конфигурацию Swashbuckle

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

Примените атрибут к своему методу

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

В пользовательском интерфейсе Swagger вы получите:

Swagger UI

person venerik    schedule 16.12.2016
comment
Можно ли добавить описание к параметрам в BinaryPayloadAttribute? - person Dot Net Dev; 25.03.2019

Очередное обновление. Вот решение, которое я использовал ASP.NET Core 3.1 и Swashbuckle.AspNetCore.Swagger 5.0.0:

public class BinaryContentAttribute : Attribute
{
}
public class BinaryContentFilter : IOperationFilter
{
    /// <summary>
    /// Configures operations decorated with the <see cref="BinaryContentAttribute" />.
    /// </summary>
    /// <param name="operation">The operation.</param>
    /// <param name="context">The context.</param>
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var attribute = context.MethodInfo.GetCustomAttributes(typeof(BinaryContentAttribute), false).FirstOrDefault();
        if (attribute == null)
        {
            return;
        }

        operation.RequestBody = new OpenApiRequestBody() { Required = true };
        operation.RequestBody.Content.Add("application/octet-stream", new OpenApiMediaType()
        {
            Schema = new OpenApiSchema()
            {
                Type = "string",
                Format = "binary",
            },
        });
    }
}

ConfigureServices in Startup.cs:

services.AddSwaggerGen(o =>
{
    o.OperationFilter<BinaryContentFilter>();
});
person Daniel Sklenitzka    schedule 27.02.2020
comment
как я могу это сделать, но пусть чванство знает определенный тип тела? - person Ryan Langton; 03.06.2020

В Swashbuckle 4.0 синтаксис немного изменился:

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

А также:

services.AddSwaggerGen(c =>
{
    c.OperationFilter<BinaryPayloadFilter>();
});
person toralux    schedule 29.01.2019

Вот обновленная версия ответа @venerik. Это работает в Swashbuckle 2.3.0:

Настраиваемый атрибут:

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

Пользовательский фильтр:

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

Добавьте фильтр в конфигурацию Swashbuckle:

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

Примените атрибут к своему методу:

[HttpPost]
[BinaryPayload]
[Route("Store/{projectId}")]
public async Task Store(string projectId)
{
    ...
}
person redcalx    schedule 23.03.2018
comment
Вы дважды разместили фильтр :-) Есть ли шанс увидеть атрибут? - person CJe; 21.09.2018