使用Swagger直接上传文件的方法

经常使用swagger,可以通过设置[ProducesResponseType]标记接口的返回信息;swagger也能通过接口的参数列表,自动获得发送的数据结构信息。

不过有一个例外,就是上传文件的时候,设置了[Consumes]的内容为multi-part/form-data,但是swagger并不能正常感知是上传文件的。代码是这个样子的:

关于文件上传的细节,可以看多年前我写过一篇有关通过WEBAPI上传文件的文章

[Consumes("multipart/form-data")]
[ODataRoute]
[HttpPost]
public async Task<ActionResult> Post(IFormCollection collection)
{
    var file = collection.Files[0];
    if(file != null)
    {
        var filename = DateTime.Now.ToString("yyyyMMddHHmmss") + file.FileName;
        var path = Path.Combine(_webHostEnvironment.WebRootPath,"Files",filename);
        using FileStream fileStream = new FileStream(path,FileMode.Create);
        await file.CopyToAsync(fileStream);
        var uri = "Files/" + filename;
        var fileEntity = new Models.File { Url = uri,LastModified = DateTime.Now };
        _homeworkDataContext.Files.Add(fileEntity);
        await _homeworkDataContext.SaveChangesAsync();
        return Created(WebUtility.UrlEncode(uri),fileEntity);
    }
    return BadRequest();
}

实际上,swagger一直提示,上传的内容是一个array类型,当然API是没有问题的,可以通过POSTMAN进行发送,不过不能在网页上直接操作,总觉得心里有点不太舒服。

方法

搜索了一下办法,比较靠谱的,就是通过增加一个IOperationFilter来实现目的。

// CODE FROM https://www.talkingdotnet.com/how-to-upload-file-via-swagger-in-asp-net-core-web-api/
public class FileUploadOperation : IOperationFilter
{
    public void Apply(Operation operation,OperationFilterContext context)
    {
        if (operation.OperationId.ToLower() == "apivaluesuploadpost")
        {
            operation.Parameters.Clear();
            operation.Parameters.Add(new NonBodyParameter
            {
                Name = "uploadedFile",In = "formData",Description = "Upload File",Required = true,Type = "file"
            });
            operation.Consumes.Add("multipart/form-data");
        }
    }
}

然后,在services.ConfigureSwaggerGen()参数中,添加

options.OperationFilter<FileUploadOperation>(); 

方法的原理是通过重写操作某个特定API的的过滤器,来实现对返回内容的操作。

此方法适用于OAS2,实质上是实现了这里的规范要求。

我已经用上.NET 5.0了,自带了swagger都支持的是OpenAPI 3,这个方法不好用了。不过思想应该相同,首先看看OpenAPI 3的规范,文件上传需要定义为:

requestBody:
  content:
    multipart/form-data:
      schema:
        type: object
        properties:
          fileName:
            type: string
            format: binary

这个套路和OpenAPI 2完全不一样,需要重新设置requestBody才行。我们按照要求改造代码。

public class FileUploadOperation : IOperationFilter
{
    public void Apply(OpenApiOperation operation,OperationFilterContext context)
    {
        //判断上传文件的类型,只有上传的类型是IFormCollection的才进行重写。
        if (context.ApiDescription.ActionDescriptor.Parameters.Any(w => w.ParameterType == typeof(IFormCollection)))
        {
            Dictionary<string,OpenApiSchema> schema = new Dictionary<string,OpenApiSchema>();
            schema["fileName"] = new OpenApiSchema { Description = "Select file",Type = "string",Format = "binary" };
            Dictionary<string,OpenApiMediaType> content = new Dictionary<string,OpenApiMediaType>();
            content["multipart/form-data"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object",Properties = schema } };
            operation.RequestBody = new OpenApiRequestBody() { Content = content };
        }
    }
}

执行之后,swagger已经可以正常识别了,通过选择文件即可上传,效果如下:

参考资料

相关文章

在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示...
上文已经介绍了Identity Service的实现过程。今天我们继续,...
最近我为我自己的应用开发框架Apworks设计了一套案例应用程序...
HAL(Hypertext Application Language,超文本应用语言)是一...
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事...
HAL,全称为Hypertext Application Language,它是一种简单的...