swagger 接受文件沿着DTO对象的控制器方法

ymdaylpp  于 2023-10-18  发布在  其他
关注(0)|答案(1)|浏览(143)

我正在尝试创建一个控制器方法,能够在同一个请求中接受一个文件和一个DTO。我需要遵循这个确切的结构,以正确地生成具有嵌套类型的OpenAPI。(类似于https://swagger.io/docs/specification/describing-request-body/multipart-requests/)。
我简化了这个例子,但我希望这个想法是明确的。在真实的项目中,“SimpleDTO”类包含多个不同类型的属性,包括枚举和用户类型)

public class SimpleDTO
{
     public string Property1 {get;set;}

     public string Property2 {get;set;}
     ...
}

public class RequestWrapper 
{
    public IFormFile File {get;set;}

    public SimpleDTO? SimpleDto {get;set;}
}

[Post]
public async Task<IActionResult> TestMethod([FromForm]RequestWrapper dto)
{
    ...
    return Ok();
}

然后我用Postman测试了这段代码,得到了状态400。如果我用简单的东西替换“SimpleDto”属性类型(例如,int)它可以工作。
我尝试将自定义模型绑定器应用于“dto”控制器方法参数和SimpleDTO类,但没有结果。我想我做错了。
我希望得到以下OpenAPI规范(从规范生成的客户端应该使用上面提到的方法):

requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties: 
                file:
                    type: string
                    format: binary
                SimpleDto:
                  $ref: '#/components/schemas/SimpleDTO'
      responses:
        '200':
          description: Success

我有一个替代的解决方案,这是一个有点棘手,我想避免它,如果可能的话。这个想法是使用“SimpleDTO”作为方法参数,并从请求体中获取文件,然后使用OperationFiltres将文件信息添加到OpenAPI规范中。

x3naxklr

x3naxklr1#

因此,我为OpenApi添加了自定义的IOperationFilter,并去掉了RequestWrapper类。

public class SimpleDTO
{
    public string Property1 {get;set;}

    public string Property2 {get;set;}
}

控制器方法:

[Post]
public async Task<IActionResult> TestMethod(IFormFile file, [FromForm]SimpleDTO dto)
{
   ...
   return Ok();
}

在这里,我用我自己使用IOperationFilter的实现部分替换了OpenApi规范的生成:

public class OpenApiSpecificationFilter : IOperationFilter
{
   public void Apply(OpenApiOperation operation, OperationFilterContext context)
   {
       var formParameters = context.MethodInfo.GetParameters()
        .Where(p => p.GetCustomAttributes(typeof(FromFormAttribute), false).Length > 0
                    || p.ParameterType == typeof(IFormFile)
                    || p.ParameterType == typeof(IFormFileCollection)
                    || p.ParameterType == typeof(List<IFormFile>));
    
    if (!formParameters.Any())
        return;
    
    operation.RequestBody.Content.Clear();
    var schema = new OpenApiSchema();
    
    foreach (var p in formParameters)
    {
        if (p.ParameterType == typeof(IFormFile))
        {
            schema.Properties.Add(p.Name, new OpenApiSchema()
            {
                Type = "string",
                Format = "binary"
            });
        }
        else if (p.ParameterType == typeof(IFormCollection) || p.ParameterType == typeof(List<IFormFile>))
        {

            schema.Properties.Add(p.Name, new OpenApiSchema()
            {
                Type = "array",
                Items = new OpenApiSchema()
                {
                    Type = "string",
                    Format = "binary"
                }
                
            });
        }
        else
        {
            var reference = context.SchemaGenerator.GenerateSchema(p.ParameterType, context.SchemaRepository);
            schema.Properties.Add(p.Name, reference);
        }
    }
    
    operation.RequestBody.Content.Add("multipart/form-data", new OpenApiMediaType()
    {
        Schema = schema
    });
}
}

Program.cs:

builder.Services.AddSwaggerGen(options =>
{
   options.OperationFilter<OpenApiSpecificationFilter>();
}

测试要求:

curl --location 'https://localhost:[port]/testMethod' \
--form 'file=@"testFile.jpeg"' \
--form 'dto="{
    \"Property1\": \"test value 1\",
    \"Property2\": \"test value 2\",
  }"'

相关问题