Search code examples
c#.netazureasp.net-core-webapihttp-status-code-413

File upload fails with 413 Request Entity Too Large in Azure app service but not while running locally


I'm trying to send a multi-part form with a binary inside (70MiB) to my API but I'm getting two error messages when the upload finishes:

Access to XMLHttpRequest at 'https://api.contoso.com/v1/controller/upload' from origin 'https://www.contoso.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

POST https://api.contoso.com/v1/controller/upload net::ERR_FAILED 413 (Request Entity Too Large)

builder.Services.Configure<KestrelServerOptions>(options =>
{
    options.Limits.MaxRequestBodySize = long.MaxValue;
    options.Limits.MaxRequestBufferSize = long.MaxValue;
});
builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = 200 * 1024 * 1024; //200 MB
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

//DB connection
//Blob storage connection
//Auth/JWT
//Services/DI

var app = builder.Build();
app.Logger.LogInformation("Starting...");

When in production:

app.Logger.LogInformation("Production mode");

app.UseCors(policyBuilder =>
{
    policyBuilder.SetIsOriginAllowed((origin) => {
        app.Logger.LogInformation("Origin of call: " + origin);

        return origin.Equals("https://contoso.com") || origin.Equals("https://www.contoso.com");
    })
    .AllowAnyHeader().AllowAnyMethod().WithExposedHeaders("App-Desktop"); 
});

//Allow access by the desktop app to these endpoints.
app.Use(async (context, next) =>
{
    if (context.Request.Headers.ContainsKey("App-Desktop"))
    {
        var path = context.Request.Path.Value?.ToLower() ?? "";

        app.Logger.LogInformation("Path of call from desktop app: " + path);

        if (path.StartsWith("/v1/blogs"))
            context.Response.Headers.Append("Access-Control-Allow-Origin", "*");
    }

    await next();
});

The controller action in question:

[HttpPost("upload")]
[Consumes("multipart/form-data")]
//[RequestSizeLimit(209_715_200)] //200Mib
[DisableRequestSizeLimit]
public async Task<IActionResult> UploadBinary(IFormFile file, [FromForm] BinaryRequest binary)
{
  _logger.LogInformation("Uploading asset: " + file.FileName);

  //...
}

Checking the API insights on Azure shows me these 4 messages (top to bottom):

Request starting HTTP/1.1 OPTIONS https://api.contoso.com/v1/controller/upload - - -

Origin of call: https://www.contoso.com

CORS policy execution successful.

Request finished HTTP/1.1 OPTIONS https://api.contoso.com/v1/controller/upload - 204 - - 2.1230ms

The controller action is never executed, so the "Uploading asset" message is never logged.
No other endpoint suffers from the same issue (no CORS issue).

What could be the reason for this?


EDIT

My config looks like this and only small files, like 5MiB are uploaded successfully:

builder.Services.Configure<KestrelServerOptions>(options =>
{
    options.Limits.MaxRequestBodySize = long.MaxValue;
    options.Limits.MaxRequestBufferSize = long.MaxValue;
    options.Limits.MaxRequestHeadersTotalSize = int.MaxValue;
    options.Limits.MaxResponseBufferSize = long.MaxValue;
});
builder.Services.Configure<FormOptions>(options =>
{
    options.ValueLengthLimit = int.MaxValue;
    options.MultipartBodyLengthLimit = long.MaxValue;
    options.MultipartHeadersLengthLimit = int.MaxValue;
    options.BufferBodyLengthLimit = long.MaxValue;
    options.MemoryBufferThreshold = int.MaxValue;
});

Solution

  • The issue was due to Azure App Service's request size limits in IIS, which override Kestrel and FormOptions settings. Adding a web.config file explicitly increase the allowed request size, can resolve the issue.

    By default, Azure App Service runs on IIS, which imposes the following request size limits:

    • maxAllowedContentLength (default: 30MB): Restricts the maximum allowed request body size.
    • maxRequestLength (default: ~4MB): Limits request size in older ASP.NET apps

    IIS in Azure App Service applies its own request size limits before the request reaches Kestrel, which caused the upload to fail

    To allow larger file uploads in Azure App Service, add the following web.config file in the root of your application:

    <configuration>
        <system.webServer>
            <security>
                <requestFiltering>
                
                    <requestLimits maxAllowedContentLength="209715200" />
                </requestFiltering>
            </security>
        </system.webServer>
        <system.web>
            <httpRuntime maxRequestLength="204800" />
        </system.web>
    </configuration>
    

    After adding web.config, you can simplify your Program.cs as follows:

    builder.Services.Configure<KestrelServerOptions>(options =>
    {
        options.Limits.MaxRequestBodySize = null;
    });
    
    builder.Services.Configure<FormOptions>(options =>
    {
        options.MultipartBodyLengthLimit = 200 * 1024 * 1024; 
    });
    
    • Please refer this MSdoc for better understanding about configuring IIS request limits.