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 IFormFile in MapPost handlers to receive multipart/form-data uploads without MVC controller overhead.
  • Configure FormOptions via builder.Services.Configure to increase the default 30 MB upload limit.
  • Sanitize filenames using Path.GetFileName and generate unique names to prevent overwriting and path traversal attacks.
  • Stream files asynchronously with CopyToAsync to avoid memory pressure on the server.
  • Reference CoursesController.cs in the dotnet/skills repository 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →