I've been trying to figure this out for the last few days. I am writing an API that uploads a file (based it a upload file tutorial on the asp.net/web-api site) , but I cannot figure out how to list the file parameter in the signature so that swashbuckle can generate the appropriate parameter type for the swagger ui to render a "select a file" button instead of a text field. Is this a limitation to web api, swashbuckle or to .Net in general?
I'm assuming your file upload implementation is similar to this - http://www.asp.net/web-api/overview/advanced/sending-html-form-data,-part-2. Because all the "file upload" specifics are happening at run-time, there is no way for Swashbuckle to automatically generate a Parameter description with the relevant "file upload" details.
So, you need to tell it about this specific parameter. This can be easily done with a custom IOperationFilter which you can wire up as follows:
AddFileParamTypes.cs
public class AddFileParamTypes : IOperationFilter
{
public void Apply(Operation operation, DataTypeRegistry dataTypeRegistry, ApiDescription apiDescription)
{
if (operation.Nickname == "FileUpload_PostFormData") // controller and action name
{
operation.Consumes.Add("multipart/form-data");
operation.Parameters.Add(new Parameter
{
Name = "file",
Required = true,
Type = "file",
ParamType = "form"
}
);
}
}
}
SwaggerConfig.cs
SwaggerSpecConfig.Customize(c =>
{
c.OperationFilter<AddFileParamTypes>();
...
Looks like IOperationFilter changed since the comments above. Here's the code that worked for me:
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.operationId == "FileUpload_PostFormData") // controller and action name
{
operation.consumes.Add("multipart/form-data");
operation.parameters = new List<Parameter>
{
new Parameter
{
name = "file",
required = true,
type = "file",
}
};
}
}
The comment above is no longer valid. See #531 issue (and mentioned there #280)
Changing code to:
new Parameter
{
name = "file",
@in = "formData",
required = true,
type = "file"
}
will fix the issue.
It's worth to note that working FileUpload operation filter can be found here
Any ideas how to get an upload file button working in Ahoy (Swagger for ASP.NET Core). The above code does not do the trick. I raised the following issue https://github.com/domaindrivendev/Ahoy/issues/193
Using an attriubute so it can be added to more controllers
public class ImportFileParamType : IOperationFilter
{
[AttributeUsage(AttributeTargets.Method)]
public sealed class SwaggerFormAttribute : Attribute
{
public SwaggerFormAttribute(string name, string description)
{
Name =name;
Description = description;
}
public string Name { get; private set; }
public string Description { get; private set; }
}
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var requestAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerFormAttribute>();
foreach (var attr in requestAttributes)
{
operation.parameters = new List<Parameter>
{
new Parameter
{
description = attr.Description,
name = attr.Name,
@in = "formData",
required = true,
type = "file",
}
};
operation.consumes.Add("multipart/form-data");
}
}`
` [Route("ImageUpload")]
[HttpPost]
[ImportFileParamType.SwaggerFormAttribute("ImportImage", "Upload image file")]
public async Task
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}`
Thank you @glyons !
A fix for the case where you have other options besides the file, for example your url is /api/user/{id}/photo:
foreach (var attr in requestAttributes)
{
operation.parameters = operation.parameters ?? new List<Parameter>();
operation.parameters.Add(new Parameter
{
description = attr.Description,
name = attr.Name,
@in = "formData",
required = true,
type = "file",
});
operation.consumes.Add("multipart/form-data");
}
nice one @razonrus
It would be nicer to do this via attribute on the method, rather than in a central place
@AndrewBragdon I think one of the comments: https://github.com/domaindrivendev/Swashbuckle/issues/120#issuecomment-288651963 does exactly that
I'm attempting to do what @glyons describes, but the code never seems to be executed when I simply add it as an attribute to the controller method.
This is what my controller method is looking like
[Route("PostDecryptionKey"), HttpPost, Shared.Models.ImportFileParamType.SwaggerForm("DecryptionKey","Upload Decryption Key")]
public async Task
I ONLY created the attribute and decorated the controller with it, so if I was supposed to do something in the swaggerconfig, that may be what I'm missing.
When I refactored it to be closer to what domaindrivendev suggested (including the swaggerconfig modification) then it works perfectly, but then I have to keep hardcoding a dependency on the operationID which I'd prefer not to do that.
I appreciate anyone who can chime in, :)
@xerikai see my commit, that is exactly what @glyons describes + @razonrus suggestion.
the final product is:
http://swashbuckletest.azurewebsites.net/swagger/ui/index?filter=Png#/PngImage/PngImage_Post
Perfect, based on glyons I had put my sealed class swaggerformattribute inside the importfileparamtype class, after I looked at your checkin, I moved it out and everything started working. Thank you for the effort!
senhores boa noite
achei uma solu莽茫o que pode ajudar, neste caso abaixo, n茫o depende de nomes de controles ou de actions, basta chamar na swagger e usar o IFormFile normalmente
using Microsoft.AspNetCore.Http;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Linq;
public class FileUploadOperation : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null) return;
var result = from a in context.ApiDescription.ParameterDescriptions
join b in operation.Parameters.OfType<NonBodyParameter>()
on a.Name equals b?.Name
where a.ModelMetadata.ModelType == typeof(IFormFile)
select b;
result.ToList().ForEach(x =>
{
x.In = "formData";
x.Description = "Upload file.";
x.Type = "file";
});
}
}
a chamada fica assim
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API V1", Version = "v1" });
c.OperationFilter<FileUploadOperation>();
});
@RenanCarlosPereira your solution doesn't work when the IFormFile is tagged with [FromForm], but I've yet to figure out why. My knowledge of Swashbuckle is quite small.
This is a bit hacky, @theBoringCoder , but it seems to be having the desired result when using [FromForm]. YMMV
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null) return;
// if you don't use [Consumes("multipart/form-data")] on your operations, you don't need this
if (!operation.Consumes.Contains("multipart/form-data")) return;
var param = context.ApiDescription.ParameterDescriptions.FirstOrDefault(x =>
x.ModelMetadata.ModelType == typeof(IFormFile));
var existing = operation.Parameters.FirstOrDefault(x =>
x.Name.Equals(param?.Name, StringComparison.InvariantCultureIgnoreCase));
var toAdd = new NonBodyParameter
{
In = "formData",
Type = "file",
Description = existing?.Description,
Name = existing?.Name ?? "file",
Required = existing?.Required ?? false
};
if (existing != null) operation.Parameters.Remove(existing);
operation.Parameters.Add(toAdd);
}
Most helpful comment
Using an attriubute so it can be added to more controllers
public class ImportFileParamType : IOperationFilter
{
[AttributeUsage(AttributeTargets.Method)]
public sealed class SwaggerFormAttribute : Attribute
{
public SwaggerFormAttribute(string name, string description)
{
Name =name;
Description = description;
}
public string Name { get; private set; }
API Controller
` [Route("ImageUpload")] PostFormDataImage()
[HttpPost]
[ImportFileParamType.SwaggerFormAttribute("ImportImage", "Upload image file")]
public async Task
{