4

I have an ASP.NET Core 2.2 WebApi and want to upload large files with some additional metadata. The request is a multipart/form-data. Because the files to upload can get quite large, I do not want to read it into memory for processing but rather stream it directly to it's desired destination. I followed the documentation to disable form value model binding and I also adjusted the maximum request size for the endpoint.

I have tested the endpoint with postman and it works as expected: enter image description here

However, Swagger obviously does not recognize that there should be parameters for the request. How can I add these parameters to the swagger documentation without defining the parameters in the method's signature?

My endpoint looks like the following example:

[HttpPost]
[DisableFormValueModelBinding]
[DisableRequestSizeLimit]
public async Task<IActionResult> Upload() // "department" and "file" needed in the multipart/form-data
{
  // var path = await uploader.UploadAsync(Request);
  // return Ok(path);
}

Usually, I would bind the parameters like the following example:

public async Task<IActionResult> Upload([FromForm] string department, [FromForm] IFormFile file)

This works as expected in Swagger but as mentioned above, I do not want to bind the parameters.

2 Answers 2

4

For Swashbuckle.AspNetCore version 5 and above some things have changed.

To provide the parameters like Alexander did in his answer, the code would look something like the following:

operation.Parameters.Add(new OpenApiParameter()
{
    Name = "department",
    Schema = new OpenApiSchema { Type = "string", Format = "string" },
    Required = true,
});

operation.Parameters.Add(new OpenApiParameter()
{
    Name = "file",
    Schema = new OpenApiSchema { Type = "string", Format = "binary" },
    Required = true,
});

For some reason however (which I did not investigate further), I was not able to perform an call in the Swagger UI with this approach.

In the end, the following example provided me the result I was looking for:

public class AddUnboundParametersOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var descriptor = context.ApiDescription.ActionDescriptor as ControllerActionDescriptor;
    
        if (descriptor != null && descriptor.ControllerTypeInfo == typeof(RemoteUpdateController) && descriptor.ActionName == nameof(RemoteUpdateController.Upload))
        {
            var openApiMediaType = new OpenApiMediaType
            {
                Schema = new OpenApiSchema
                {
                    Type = "object",
                    Required = new HashSet<string> { "department", "file" }, // make the parameter(s) required if needed
                    Properties = new Dictionary<string, OpenApiSchema>
                    {
                        { "department" , new OpenApiSchema() { Type = "string", Format = "string" } },
                        { "file" , new OpenApiSchema() { Type = "string", Format = "binary" } },
                    }
                }
            };

            operation.RequestBody = new OpenApiRequestBody
            {
                Content = new Dictionary<string, OpenApiMediaType>
                {
                    { "multipart/form-data", openApiMediaType }
                }
            };
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

You can use IOperationFilter for this. Add the following class, adjust controller and action names

public class AddUnboundParametersOperationFilter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        if (operation.Parameters == null)
            operation.Parameters = new List<IParameter>();

        var descriptor = context.ApiDescription.ActionDescriptor as ControllerActionDescriptor;

        if (descriptor != null && descriptor.ControllerTypeInfo == typeof(TestController) && descriptor.ActionName == nameof(TestController.Upload))
        {
            operation.Parameters.Add(new NonBodyParameter()
            {
                Name = "department",
                Type = "string",
                Required = true,
                In = "formData",
            });

            operation.Parameters.Add(new NonBodyParameter()
            {
                Type = "file",
                In = "formData",
                Name = "file",
                Required = true
            });
        }
    }
}

In Startup.cs

services.AddSwaggerGen(c =>
{
    c.OperationFilter<AddUnboundParametersOperationFilter>();
    //...
});

1 Comment

This definitely put me on the right track, however, your solution does not work for Swashbuckle version 5 and above. Anyways, I think this will work in earlier versions. If anyone looking for an example for version 5 and above, take a look at my answer to this question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.