如何在 .NET 核心 Web api 中添加上传按钮以 swagger UI?

How can I add an upload button to swagger UI in .NET core web api?

我有一个 ASP.net 核心网络 API swagger(使用 swashbuckle)。

Web 中的一项操作 API 是文件上传操作:

[Produces("application/json")]
[Route("[controller]")]
public class FilesController : Controller
{
    [HttpPost]
    public void Post(IFormFile file)
    {
        ...
    }
}

当我大摇大摆地查看那个动作时 UI 它让我填写 IFormFile 的所有字段,这不是我想要测试我的 API .

那么如何向 Swagger 添加上传按钮UI?

首先添加一个使用多部分表单数据的操作过滤器。

public class FileUploadOperation : IOperationFilter
{
    private readonly IEnumerable<string> _actionsWithUpload = new []
    {
        //add your upload actions here!
        NamingHelpers.GetOperationId<FilesController>(nameof(FilesController.Post))
    };

    public void Apply(Operation operation, OperationFilterContext context)
    {
        if (_actionsWithUpload.Contains(operation.OperationId) )
        {
            operation.Parameters.Clear();
            operation.Parameters.Add(new NonBodyParameter
            {
                Name = "file",
                In = "formData",
                Description = "Upload File",
                Required = true,
                Type = "file"
            });
            operation.Consumes.Add("multipart/form-data");
        }
    }
}

    /// <summary>
    /// Refatoring friendly helper to get names of controllers and operation ids
    /// </summary>
    public class NamingHelpers
    {
        public static string GetOperationId<T>(string actionName) where T : Controller => $"{GetControllerName<T>()}{actionName}";

        public static string GetControllerName<T>() where T : Controller => typeof(T).Name.Replace(nameof(Controller), string.Empty);
    }

现在您应该将您的操作添加到 _actionWithUpload 数组中! 请注意,我添加扩展只是为了有一个重构友好的过滤器。

最后但同样重要的是,确保将操作过滤器添加到 swagger 的选项中。因此,将 options.OperationFilter<FileUploadOperation>(); 添加到您的 swagger 选项中并完成。

完整示例:

       services.AddSwaggerGen(options =>
        {
            options.SwaggerDoc(Version, new Info
                {
                    Title = Title,
                    Version = Version                        
                }                
            );
            var filePath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, $"{_webApiAssemblyName}.xml");
            options.IncludeXmlComments(filePath);
            options.DescribeAllEnumsAsStrings();
//this is the step where we add the operation filter
            options.OperationFilter<FileUploadOperation>();
        });

除了@Nick 的回答之外,我还必须对 AspNet 核心 2 进行 2 处更改。

1] 更新了 GetOperationId()

现在所有的 operationIds 都包含 API 前缀以及后缀中的 Method。所以我在 ActionName.

中静态添加了 API & Post
public static string GetOperationId<T>(string actionName) where T : ControllerBase => $"Api{GetControllerName<T>()}{actionName}Post";

2] 仅删除文件参数

我不想删除该操作的所有参数,而是只想删除文件参数。

var fileParameter = operation.Parameters.FirstOrDefault(x => x.Name == "file" && x.In == "body");
if (fileParameter != null)
{
    operation.Parameters.Remove(fileParameter);
    ...
}

致所有寻求 开放 api 实施的人

/// <summary>
/// Add extra parameters for uploading files in swagger.
/// </summary>
public class FileUploadOperation : IOperationFilter
{
    /// <summary>
    /// Applies the specified operation.
    /// </summary>
    /// <param name="operation">The operation.</param>
    /// <param name="context">The context.</param>
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {

        var isFileUploadOperation =
            context.MethodInfo.CustomAttributes.Any(a => a.AttributeType == typeof(FileContentType));

        if (!isFileUploadOperation) return;

        operation.Parameters.Clear();
   
        var uploadFileMediaType = new OpenApiMediaType()
        {
            Schema = new OpenApiSchema()
            {
                Type = "object",
                Properties =
                {
                    ["uploadedFile"] = new OpenApiSchema()
                    {
                        Description = "Upload File",
                        Type = "file",
                        Format = "formData"
                    }
                },
                Required = new HashSet<string>(){  "uploadedFile"  }
            }
        };

        operation.RequestBody = new OpenApiRequestBody
        {
            Content = {  ["multipart/form-data"] = uploadFileMediaType   }
        };
    }
    
    /// <summary>
    /// Indicates swashbuckle should consider the parameter as a file upload
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public class FileContentType : Attribute
    {
       
    }
}

使用 FileContentType 属性装饰控制器

[HttpPost]
[Route("PostFile")]
[FileUploadOperation.FileContentType]
public IActionResult PostFile(IFormFile uploadedFile)

文件上传应该像下面这样在请求正文中生成..

对于拥有多个端点需要上传文件并希望为其文件上传参数使用更通用/描述性/个性化名称的任何人:

public class SwaggerFileOperationFilter : IOperationFilter 
{
  public void Apply(OpenApiOperation operation, OperationFilterContext context)
  {
    var fileParams = context.MethodInfo.GetParameters().Where(p => p.ParameterType.FullName?.Equals(typeof(Microsoft.AspNetCore.Http.IFormFile).FullName) == true);

    if (fileParams.Any() && fileParams.Count() == 1)
    {
      var title = "The file to be uploaded";
      var description = "The file to be uploaded";
      int? maxLength = 5_242_880;
      bool required = true;

      var descriptionAttribute = fileParams.First().CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(FormFileDescriptorAttribute));
      if (descriptionAttribute?.ConstructorArguments.Count > 3)
      {
        title = descriptionAttribute.ConstructorArguments[0].Value.ToString();
        description = descriptionAttribute.ConstructorArguments[1].Value.ToString();
        required = (bool)descriptionAttribute.ConstructorArguments[2].Value;
        maxLength = (int)descriptionAttribute.ConstructorArguments[3].Value;
      }

      var uploadFileMediaType = new OpenApiMediaType()
      {
        Schema = new OpenApiSchema()
        {
          Type = "object",
          Properties =
            {
              [fileParams.First().Name] = new OpenApiSchema()
              {
                  Description = description,
                  Type = "file",
                  Format = "binary",
                  Title = title,
                  MaxLength = maxLength
              }
            }
        }
      };

      if (required)
      {
        uploadFileMediaType.Schema.Required = new HashSet<string>() { fileParams.First().Name };
      }

      operation.RequestBody = new OpenApiRequestBody
      {
        Content = { ["multipart/form-data"] = uploadFileMediaType }
      };
    }
  }
}

我创建了一个属性来为我的文件上传添加更多描述:

public class FormFileDescriptorAttribute : Attribute 
{
  public FormFileDescriptorAttribute(string title, string description, bool required, int maxLength)
  {
    Title = title;
    Description = description;
    Required = required;
    MaxLength = maxLength;
  }

  public string Title { get; set; }

  public string Description { get; set; }

  public int MaxLength { get; set; }

  public bool Required { get; set; }
}

然后我就这样使用它:

public async Task<IActionResult> CreateFromFileAsync(
  [FromForm, FormFileDescriptor("Project JSON file", "The project as a JSON file", true, 52_428_800)] IFormFile projectJsonFileFile,
  CancellationToken cancellationToken)
{
controller:
[HttpPost]
        public async Task<string> Post([FromForm] ImageUploadVW imageUpload)
        {
            if(imageUpload.Image.Length > 0)
            {
                string path = _webHostEnvironment.WebRootPath + "\uploads\";
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                using (FileStream fileStream = System.IO.File.Create(path + 
              imageUpload.Image.FileName))
                {
                    imageUpload.Image.CopyTo(fileStream);
                    fileStream.Flush();
                    return "Upload Done";
                }
            }
            else
            {
                return "failed to Upload Image";
            }

     public class ImageUploadVW
    {
        public IFormFile Image { get; set; }
    }