Swagger перечисляет параметр IFormFile как тип "объект"

У меня есть контроллер, который запрашивает модель, содержащую IFormFile в качестве одного из свойств. Для описания запроса пользовательский интерфейс Swagger (я использую Swashbuckle и OpenApi 3.0 для .NET Core) перечисляет тип свойства файла как объект типа. Есть ли способ заставить пользовательский интерфейс Swagger обозначать точный тип и его представление в формате JSON, чтобы помочь клиенту?

Контроллер, запрашивающий модель, выглядит следующим образом.

[HttpPost]
[Consumes("multipart/form-data")
public async Task<IActionResult> CreateSomethingAndUploadFile ([FromForm]RequestModel model)
{
    // do something
}

Модель определяется следующим образом:

public class AssetCreationModel
{
    [Required}
    public string Filename { get; set; }

    [Required]
    public IFormFile File { get; set; }       
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
5 480
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Эта проблема уже решалась в следующем проблема / поток github.

Это улучшение уже было включено в мастер Swashbuckle.AspNetCore (по состоянию на 30.10.2018), но я не ожидаю, что он скоро будет доступен в виде пакета.

Существуют простые решения, если в качестве параметра используется только IFormFile.

public async Task UploadFile(IFormFile filePayload){}

Для простого случая вы можете взглянуть на следующий отвечать.

Для сложных случаев, таких как контейнеры, вы можете взглянуть на следующий отвечать.

internal class FormFileOperationFilter : IOperationFilter
{
    private struct ContainerParameterData
    {
        public readonly ParameterDescriptor Parameter;
        public readonly PropertyInfo Property;

        public string FullName => $"{Parameter.Name}.{Property.Name}";
        public string Name => Property.Name;

        public ContainerParameterData(ParameterDescriptor parameter, PropertyInfo property)
        {
            Parameter = parameter;
            Property = property;
        }
    }

    private static readonly ImmutableArray<string> iFormFilePropertyNames =
        typeof(IFormFile).GetTypeInfo().DeclaredProperties.Select(p => p.Name).ToImmutableArray();

    public void Apply(Operation operation, OperationFilterContext context)
    {
        var parameters = operation.Parameters;
        if (parameters == null)
            return;

        var @params = context.ApiDescription.ActionDescriptor.Parameters;
        if (parameters.Count == @params.Count)
            return;

        var formFileParams =
            (from parameter in @params
                where parameter.ParameterType.IsAssignableFrom(typeof(IFormFile))
                select parameter).ToArray();

        var iFormFileType = typeof(IFormFile).GetTypeInfo();
        var containerParams =
            @params.Select(p => new KeyValuePair<ParameterDescriptor, PropertyInfo[]>(
                p, p.ParameterType.GetProperties()))
            .Where(pp => pp.Value.Any(p => iFormFileType.IsAssignableFrom(p.PropertyType)))
            .SelectMany(p => p.Value.Select(pp => new ContainerParameterData(p.Key, pp)))
            .ToImmutableArray();

        if (!(formFileParams.Any() || containerParams.Any()))
            return;

        var consumes = operation.Consumes;
        consumes.Clear();
        consumes.Add("application/form-data");

        if (!containerParams.Any())
        {
            var nonIFormFileProperties =
                parameters.Where(p =>
                    !(iFormFilePropertyNames.Contains(p.Name)
                    && string.Compare(p.In, "formData", StringComparison.OrdinalIgnoreCase) == 0))
                    .ToImmutableArray();

            parameters.Clear();
            foreach (var parameter in nonIFormFileProperties) parameters.Add(parameter);

            foreach (var parameter in formFileParams)
            {
                parameters.Add(new NonBodyParameter
                {
                    Name = parameter.Name,
                    //Required = , // TODO: find a way to determine
                    Type = "file"
                });
            }
        }
        else
        {
            var paramsToRemove = new List<IParameter>();
            foreach (var parameter in containerParams)
            {
                var parameterFilter = parameter.Property.Name + ".";
                paramsToRemove.AddRange(from p in parameters
                                        where p.Name.StartsWith(parameterFilter)
                                        select p);
            }
            paramsToRemove.ForEach(x => parameters.Remove(x));

            foreach (var parameter in containerParams)
            {
                if (iFormFileType.IsAssignableFrom(parameter.Property.PropertyType))
                {
                    var originalParameter = parameters.FirstOrDefault(param => param.Name == parameter.Name);
                    parameters.Remove(originalParameter);

                    parameters.Add(new NonBodyParameter
                    {
                        Name = parameter.Name,
                        Required = originalParameter.Required,
                        Type = "file",
                        In = "formData"
                    });
                }
            }
        }
    }
}

Вам нужно подумать, как можно добавить / OperationFilter, который подходит для вашего случая.

Сегодня мы изучаем эту проблему. Если вы добавите следующее в свой запуск, он преобразует IFormFile в правильный тип

services.AddSwaggerGen(c => {
   c.SchemaRegistryOptions.CustomTypeMappings.Add(typeof(IFormFile), () => new Schema() { Type = "file", Format = "binary"});
});

Также см. Следующую статью о загрузке файлов в ядро ​​.NET. https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1

Другие вопросы по теме