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.
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";
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.