How to Configure File Upload Size Limits in ASP.NET Core Minimal APIs
Configure both Kestrel's MaxRequestBodySize (default 30 MiB) and FormOptions.MultipartBodyLengthLimit (default 128 MiB) to handle file uploads in ASP.NET Core minimal APIs, and use [RequestSizeLimit] or [DisableRequestSizeLimit] attributes for per-endpoint overrides.
ASP.NET Core minimal APIs automatically bind IFormFile and IFormFileCollection parameters from multipart/form-data requests starting in .NET 8. To prevent "Request body too large" exceptions, you must configure size limits at two independent layers of the request pipeline, as documented in the dotnet/skills repository's minimal-api-file-upload skill.
Understanding the Two-Layer Limit Architecture
File uploads in minimal APIs are constrained by two separate subsystems that operate at different stages of request processing:
- Kestrel request body limit: Controls the maximum raw HTTP payload the server accepts (default 30 MiB)
- FormOptions multipart limit: Governs the parsed multipart body size during model binding (default 128 MiB)
If you increase only one limit, the other will reject large uploads. According to the skill documentation in plugins/dotnet-aspnetcore/skills/minimal-api-file-upload/SKILL.md, both must be configured explicitly for production scenarios.
Configuring Global Upload Limits
Set application-wide limits using the WebApplicationBuilder in Program.cs.
Kestrel Request Body Size
Configure the MaxRequestBodySize property via ConfigureKestrel:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
// Set global limit to 10 MiB
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024;
});
Multipart Form Data Limits
Configure the FormOptions via the dependency injection container:
builder.Services.Configure<FormOptions>(options =>
{
// Multipart body size limit (default is 128 MiB)
options.MultipartBodyLengthLimit = 10 * 1024 * 1024;
// Optional: limit individual form fields
options.ValueLengthLimit = 1 * 1024 * 1024;
// Optional: limit multipart headers per section
options.MultipartHeadersLengthLimit = 16 * 1024;
});
Per-Endpoint Size Limit Overrides
For endpoints that handle larger files or require streaming, apply attributes directly to the route handler.
Increasing Limits for Specific Endpoints
Use [RequestSizeLimit] to raise the allowance for a single route:
app.MapPost("/upload-large",
[RequestSizeLimit(200_000_000)] // 200 MiB
(IFormFile file) =>
{
return Results.Ok(new { file.FileName, file.Length });
});
Removing Limits for Streaming Uploads
Use [DisableRequestSizeLimit] when processing files with MultipartReader to avoid buffering:
app.MapPost("/upload-stream",
[DisableRequestSizeLimit]
async (HttpContext ctx) =>
{
var mediaType = MediaTypeHeaderValue.Parse(ctx.Request.ContentType);
var boundary = HeaderUtilities.RemoveQuotes(mediaType.Boundary).Value;
var reader = new MultipartReader(boundary, ctx.Request.Body);
while (await reader.ReadNextSectionAsync() is { } section)
{
// Stream directly to storage without buffering
}
return Results.Ok();
});
Handling Anti-Forgery Validation
By default, .NET 8 minimal API templates enable UseAntiforgery(), which automatically validates anti-forgery tokens on all form-bound endpoints. For API-only uploads using JWT authentication or other stateless mechanisms, disable this validation:
app.MapPost("/api/upload", (IFormFile file) =>
{
return Results.Ok(file.FileName);
})
.DisableAntiforgery();
Only disable anti-forgery for stateless API endpoints. Keep it enabled for cookie-based authentication scenarios.
Complete Implementation Example
The following Program.cs demonstrates global limits, per-endpoint overrides, and security configuration:
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
// 1. Configure Kestrel global limit (30 MiB default)
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024;
});
// 2. Configure FormOptions global limit (128 MiB default)
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 10 * 1024 * 1024;
options.ValueLengthLimit = 1 * 1024 * 1024;
options.MultipartHeadersLengthLimit = 16 * 1024;
});
var app = builder.Build();
// 3. Standard upload with global limits
app.MapPost("/upload", (IFormFile file) =>
{
// Validate content type and file signature here
return Results.Ok(new { file.FileName, file.Length });
});
// 4. Large file upload with per-endpoint limit
app.MapPost("/upload-large",
[RequestSizeLimit(200_000_000)]
(IFormFile file) => Results.Ok(new { file.FileName, file.Length })
);
// 5. Streaming upload without size limits
app.MapPost("/upload-stream",
[DisableRequestSizeLimit]
async (HttpContext ctx) =>
{
if (!MediaTypeHeaderValue.TryParse(ctx.Request.ContentType, out var mediaType))
return Results.BadRequest("Invalid Content-Type");
var boundary = HeaderUtilities.RemoveQuotes(mediaType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
return Results.BadRequest("Missing boundary");
var reader = new MultipartReader(boundary, ctx.Request.Body);
while (await reader.ReadNextSectionAsync() is { } section)
{
// Process stream without buffering to memory
}
return Results.Ok();
});
// 6. API endpoint without anti-forgery (for JWT scenarios)
app.MapPost("/api/upload", (IFormFile file) => Results.Ok(file.FileName))
.DisableAntiforgery();
app.Run();
Summary
- Configure both layers: Set
MaxRequestBodySizein Kestrel andMultipartBodyLengthLimitinFormOptionsto avoid rejection at either the server or form-parsing layer. - Use attributes for exceptions: Apply
[RequestSizeLimit]or[DisableRequestSizeLimit]to individual endpoints that exceed global limits or require streaming. - Manage anti-forgery: Call
.DisableAntiforgery()only on stateless API endpoints, not on cookie-authenticated routes. - Validate content: Always verify MIME types and file signatures server-side, regardless of size limits.
Frequently Asked Questions
Why do I still get "Request body too large" errors after configuring limits?
This occurs when you only configure one of the two required limit layers. Kestrel's MaxRequestBodySize (default 30 MiB) controls the raw HTTP payload, while ASP.NET Core's FormOptions.MultipartBodyLengthLimit (default 128 MiB) controls the parsed multipart body. You must configure both via builder.WebHost.ConfigureKestrel() and builder.Services.Configure<FormOptions>() respectively, as shown in the plugins/dotnet-aspnetcore/skills/minimal-api-file-upload/SKILL.md documentation.
How do I completely disable file upload size limits for a specific endpoint?
Apply the [DisableRequestSizeLimit] attribute to the route handler. This removes both the Kestrel request body limit and the FormOptions multipart limit for that endpoint, enabling scenarios like MultipartReader streaming where you process the upload in chunks without buffering the entire file to memory or disk.
Do file upload endpoints require anti-forgery token validation?
Yes, by default. .NET 8 minimal API templates enable UseAntiforgery() automatically, which validates anti-forgery tokens on all form-bound endpoints including those accepting IFormFile. For stateless API clients using JWT Bearer authentication, chain .DisableAntiforgery() on the endpoint route. Never disable antiforgery on endpoints that accept cookie-based authentication.
What's the difference between RequestSizeLimit and DisableRequestSizeLimit?
[RequestSizeLimit(bytes)] sets a specific byte limit for the endpoint, overriding the global configuration with a higher or lower value. [DisableRequestSizeLimit] removes size constraints entirely, allowing uploads of any size. Use RequestSizeLimit when you know the maximum expected file size; use DisableRequestSizeLimit only when implementing custom streaming logic with MultipartReader that handles backpressure and storage limits manually.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →