How to Build Minimal APIs with File Upload Support in ASP.NET Core
ASP.NET Core minimal APIs handle file uploads through the IFormFile model binder in MapPost handlers, streaming uploaded files to disk while leveraging the same middleware pipeline as traditional MVC applications.
The dotnet/skills repository demonstrates production-ready file handling patterns that translate directly into lightweight minimal API endpoints. By eliminating controllers and views, you create concise HTTP endpoints in a single Program.cs file while retaining full access to form-data binding and static file serving. This approach reduces boilerplate while maintaining enterprise-grade file upload capabilities.
Configuring the Minimal API Host
A minimal API starts with WebApplication.CreateBuilder and registers only the services you need. For file uploads, you must configure form options and static file handling.
using Microsoft.AspNetCore.Http.Features;
var builder = WebApplication.CreateBuilder(args);
// Optional: increase the default 30 MB multipart limit
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 100L * 1024 * 1024; // 100 MB
});
var app = builder.Build();
// Enable static file serving for downloading uploaded files later
app.UseStaticFiles();
app.Run();
This configuration mirrors the service registration pattern found in tests/dotnet-upgrade/migrate-nullable-references/fixtures/enable-nrt-in-asp-net-core-web-api-with-ef-core/BookStore/Program.cs, which demonstrates how ASP.NET Core Web APIs register infrastructure services.
Creating the File Upload Endpoint
The MapPost method defines an endpoint that accepts IFormFile when the request uses multipart/form-data encoding. The minimal API pipeline automatically binds the uploaded file to the parameter.
app.MapPost("/upload", async (IFormFile file) =>
{
if (file is null || file.Length == 0)
{
return Results.BadRequest("No file uploaded.");
}
// Ensure upload directory exists
var uploadPath = Path.Combine(app.Environment.WebRootPath, "uploads");
Directory.CreateDirectory(uploadPath);
// Sanitize filename and ensure uniqueness
var safeName = Path.GetFileNameWithoutExtension(file.FileName);
var ext = Path.GetExtension(file.FileName);
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var finalName = $"{safeName}_{timestamp}{ext}";
var filePath = Path.Combine(uploadPath, finalName);
// Stream file to disk efficiently
await using var stream = File.Create(filePath);
await file.CopyToAsync(stream);
var url = $"/uploads/{finalName}";
return Results.Created(url, new { FileName = finalName, Url = url });
})
.Accepts<IFormFile>("multipart/form-data")
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest);
The Accepts metadata tells Swagger/OpenAPI clients that this endpoint expects form-data, while Produces documents the possible response codes. This implementation follows the file streaming pattern shown in tests/dotnet-test/code-testing-agent/ContosoUniversity/Controllers/CoursesController.cs, adapted for the minimal API syntax.
Adapting MVC File Handling Patterns
The repository's CoursesController.cs contains a traditional MVC implementation that you can port directly to minimal APIs. The core logic—checking IFormFile.Length, combining paths with Path.Combine, and streaming via CopyToAsync—remains identical.
// From Controllers/CoursesController.cs (MVC pattern)
public ActionResult Create(Course course, IFormFile teachingMaterialImage)
{
if (teachingMaterialImage != null && teachingMaterialImage.Length > 0)
{
var fileName = Path.GetFileName(teachingMaterialImage.FileName);
var path = Path.Combine(_environment.WebRootPath, "uploads", fileName);
using var stream = new FileStream(path, FileMode.Create);
teachingMaterialImage.CopyTo(stream);
course.TeachingMaterialImagePath = $"/uploads/{fileName}";
}
return RedirectToAction(nameof(Index));
}
To convert this to a minimal API handler, remove the ActionResult return type and RedirectToAction calls, replacing them with Results.Created or Results.Ok JSON responses. The file validation and storage logic transfers directly.
Security and Performance Considerations
Path traversal attacks are mitigated by Path.GetFileName and Path.GetFileNameWithoutExtension, which strip directory information from the original filename. Always combine these sanitized names with Path.Combine rather than string concatenation.
For large files, the CopyToAsync method streams the upload directly to disk without buffering the entire file in memory. This prevents out-of-memory exceptions when handling multi-gigabyte uploads, as implemented in the repository's test fixtures under tests/dotnet-test/code-testing-agent/ContosoUniversity/.
Summary
- Minimal APIs use
IFormFileinMapPosthandlers to receive multipart/form-data uploads without MVC controller overhead. - Configure
FormOptionsviabuilder.Services.Configureto increase the default 30 MB upload limit. - Sanitize filenames using
Path.GetFileNameand generate unique names to prevent overwriting and path traversal attacks. - Stream files asynchronously with
CopyToAsyncto avoid memory pressure on the server. - Reference
CoursesController.csin thedotnet/skillsrepository for production-grade file validation logic that adapts to minimal API endpoints.
Frequently Asked Questions
How do I set a maximum file size limit for uploads in minimal APIs?
Configure the FormOptions service in your application builder. Use builder.Services.Configure<FormOptions>(options => options.MultipartBodyLengthLimit = sizeInBytes) to override the default 30 MB limit. You can also apply the [RequestSizeLimit] attribute to specific endpoints if you prefer per-route configuration.
Can I upload multiple files in a single minimal API request?
Yes, change the handler parameter to accept IFormFileCollection files or List<IFormFile> files. The model binder automatically populates the collection with all files from the multipart request. Iterate through the collection and process each file individually, checking Length and ContentType as needed.
Where should I store uploaded files in a minimal API application?
Store files in a subdirectory under wwwroot (accessible via app.Environment.WebRootPath) when you want to serve them directly through static files middleware. For sensitive files, store them outside the web root and create a separate MapGet endpoint that reads and returns the file with proper authorization checks, similar to the security patterns found in the repository's Program.cs examples.
Does minimal API support the same file validation as MVC controllers?
Yes, minimal APIs support the same IFormFile interface and validation properties (Length, FileName, ContentType). While minimal APIs don't use model binding attributes like [Bind], you can implement validation logic directly in the handler or use the FluentValidation library. The file handling implementation in CoursesController.cs demonstrates validation checks that work identically in minimal API handlers.
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 →