简体   繁体   中英

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

I have an ASP.net core web API with swagger (using swashbuckle).

One of the actions in the Web API is a file upload action:

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

When I look up that action in the swagger UI it let's me fill in all the fields of IFormFile , which is not what I want to do to test my API.

So how can I add an upload button to the Swagger UI?

For anyone looking for an open api implementation

/// <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
    {
       
    }
}

Decorate controller using FileContentType attribute

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

File upload should be generated in the Request body like below..

在此处输入图像描述

First add a operation filter that consumes the multipart formdata.

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);
    }

Now you should add your actions to the _actionWithUpload array! Note that I added the extesnions only to have a refactoring friendly filter.

Last but not least, make sure the operation filter is added to the options of swagger. So add options.OperationFilter<FileUploadOperation>(); to your swagger options and done.

Full example:

       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>();
        });

In addition to @Nick's answer, I have to make 2 changes for AspNet core 2.

1] Updated GetOperationId()

Now all operationIds contain API prefix as well as Method in the suffix. So I have statically added API & Post in ActionName.

public static string GetOperationId<T>(string actionName) where T : ControllerBase => $"Api{GetControllerName<T>()}{actionName}Post";

2] Remove only file parameter

Instead of removing all parameters for that action, I want to remove only the file parameter.

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

For anyone who has more than one endpoint that needs to upload files and want to use more generic / descriptive / individual names for their file upload parameters:

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 }
      };
    }
  }
}

I created an attribute to add more description for my file upload:

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; }
}

And then I use it as such:

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; }
    }
I replace only file type parameters and not use clear all parameters.

/// <summary>
/// Add extra parameters for uploading files in swagger.
/// </summary>
public class FileUploadSawggerOperationFilter : 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 fileTypeParameters = context.MethodInfo.GetParameters().Where(c => c.ParameterType == typeof(IFormFile));
        if (!fileTypeParameters.Any()) return;

        var uploadFileMediaType = new OpenApiMediaType()
        {
            Schema = new OpenApiSchema()
            {
                Type = "object",
            }
        };

        foreach (var fileTypeParameter in fileTypeParameters)
        {
            var operationParameter = operation.Parameters.SingleOrDefault(c => c.Name == fileTypeParameter.Name);
            if (operationParameter is not null) operation.Parameters.Remove(operationParameter);

            uploadFileMediaType.Schema.Properties.Add(fileTypeParameter.Name, new OpenApiSchema()
            {
                Description = "Upload File",
                Type = "file",
                Format = "formData"
            });
        }

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM