摇摇欲坠地将IFormFile参数列为“对象”类型

时间:2018-10-30 06:52:49

标签: asp.net-core swagger openapi

我有一个控制器,该控制器请求包含IFormFile作为其属性之一的模型。对于请求描述,Swagger UI(我正在使用Swashbuckle和.NET Core的OpenApi 3.0)将文件属性的类型作为类型对象列出。有什么方法可以使Swagger UI表示确切的类型,它是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; }       
}

2 个答案:

答案 0 :(得分:1)

以下github issue/thread已解决了此问题。

此改进已合并到Swashbuckle.AspNetCore母版中(根据2018年10月30日),但我不希望很快将其作为软件包提供。

如果仅将IFormFile作为参数,则有简单的解决方案。

public async Task UploadFile(IFormFile filePayload){}

为简单起见,您可以查看以下answer

对于诸如集装箱箱这样的复杂箱,您可以查看以下answer

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

答案 1 :(得分:0)

我们今天一直在探索这个问题。如果将以下内容添加到启动中,它将IFormFile转换为正确的类型

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

另请参阅以下有关.net core中文件上传的文章 https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1