# Pitfalls of IFormFile for Large File Uploads: How to Use MultipartReader in ASP.NET Core

> Avoid IFormFile memory issues for large uploads. Learn how ASP.NET Core MultipartReader streams files efficiently, preventing bottlenecks and exhaustion.

- Repository: [.NET Platform/skills](https://github.com/dotnet/skills)
- Tags: deep-dive
- Published: 2026-07-11

---

**Using `IFormFile` buffers the entire file in memory or disk before your action method executes, causing memory exhaustion and disk I/O bottlenecks with large uploads, while `MultipartReader` streams data chunk-by-chunk with minimal footprint.**

The **dotnet/skills** repository contains reference implementations demonstrating both approaches. In [`tests/dotnet-test/code-testing-agent/ContosoUniversity/Controllers/CoursesController.cs`](https://github.com/dotnet/skills/blob/main/tests/dotnet-test/code-testing-agent/ContosoUniversity/Controllers/CoursesController.cs), the framework binds uploaded files to `IFormFile` objects, whereas [`tests/dotnet-aspnetcore/minimal-api-file-upload/Program.cs`](https://github.com/dotnet/skills/blob/main/tests/dotnet-aspnetcore/minimal-api-file-upload/Program.cs) leverages `MultipartReader` for true streaming. Understanding the architectural differences between these patterns is critical for building scalable ASP.NET Core applications that handle large file uploads without exhausting server resources.

## Why IFormFile Fails at Scale

The `IFormFile` abstraction simplifies small file handling but introduces significant performance constraints for high-volume scenarios.

### Full In-Memory Buffering

When ASP.NET Core binds an `IFormFile` parameter, the framework **buffers the entire request body** before invoking your controller action. For files exceeding the default size threshold (approximately 28 MB), the runtime writes temporary files to disk, but for smaller files, it holds the complete payload in memory. This behavior occurs in [`CoursesController.cs`](https://github.com/dotnet/skills/blob/main/CoursesController.cs) where the action method receives the file only after the buffering completes.

### Default Size Limit Restrictions

ASP.NET Core enforces a default request size limit that triggers a `BadHttpRequestException` before your code executes. You must explicitly configure `[RequestSizeLimit]` attributes or middleware settings to accept larger payloads, but doing so increases the risk of memory exhaustion since the entire file buffers regardless of your processing logic.

### Lack of Streaming Control

The `OpenReadStream()` method on `IFormFile` does not provide true streaming capabilities. You cannot begin processing data while the upload is in progress, preventing real-time operations like virus scanning, hashing, or cloud storage persistence until the complete file resides in memory or temporary disk storage.

## Streaming with MultipartReader

The `MultipartReader` class in `Microsoft.AspNetCore.WebUtilities` parses multipart requests as a sequence of sections, delivering each part as a stream while the request is still being read.

### How MultipartReader Works

Unlike `IFormFile`, `MultipartReader` processes the request body incrementally. As shown in [`tests/dotnet-aspnetcore/minimal-api-file-upload/Program.cs`](https://github.com/dotnet/skills/blob/main/tests/dotnet-aspnetcore/minimal-api-file-upload/Program.cs), you instantiate the reader with the request boundary and iterate through sections using `ReadNextSectionAsync()`. Each section exposes its content as a stream that you can process immediately without waiting for the entire upload to complete.

```csharp
var boundary = HeaderUtilities.RemoveQuotes(
    MediaTypeHeaderValue.Parse(Request.ContentType!).Boundary!.Value).ToString();

var reader = new MultipartReader(boundary, Request.Body);
MultipartSection? section;
while ((section = await reader.ReadNextSectionAsync()) != null)
{
    var hasContentDisposition = 
        ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var disposition);

    if (hasContentDisposition && disposition.DispositionType.Equals("form-data") &&
        !string.IsNullOrEmpty(disposition.FileName.Value))
    {
        await using var target = File.Create(Path.Combine(_uploadsPath, disposition.FileName.Value));
        await section.Body.CopyToAsync(target);
    }
}

```

### Memory Efficiency Benefits

Because `MultipartReader` does not buffer the entire payload, memory usage remains constant regardless of file size. You can process multi-gigabyte uploads on modest hardware by streaming content directly to persistent storage, processing chunks for validation, or forwarding data to external services without intermediate temporary files.

## Implementation Examples from dotnet/skills

The repository provides concrete examples contrasting these approaches.

### The Problematic IFormFile Approach

In [`tests/dotnet-test/code-testing-agent/ContosoUniversity/Controllers/CoursesController.cs`](https://github.com/dotnet/skills/blob/main/tests/dotnet-test/code-testing-agent/ContosoUniversity/Controllers/CoursesController.cs), the controller accepts `IFormFile` parameters that force the framework to buffer uploads:

```csharp
public ActionResult Create([Bind("…")] Course course, IFormFile teachingMaterialImage)
{
    using var stream = teachingMaterialImage.OpenReadStream();
    // Processing occurs only after full file buffering
}

```

This pattern works for small files but creates memory pressure and disk I/O bottlenecks under load.

### The Streaming Solution

The minimal API implementation in [`tests/dotnet-aspnetcore/minimal-api-file-upload/Program.cs`](https://github.com/dotnet/skills/blob/main/tests/dotnet-aspnetcore/minimal-api-file-upload/Program.cs) demonstrates the `MultipartReader` pattern:

```csharp
app.MapPost("/upload", async (HttpRequest request) =>
{
    var boundary = HeaderUtilities.RemoveQuotes(
        MediaTypeHeaderValue.Parse(request.ContentType!).Boundary!.Value).ToString();

    var reader = new MultipartReader(boundary, request.Body);
    MultipartSection? section;
    while ((section = await reader.ReadNextSectionAsync()) != null)
    {
        var filePath = Path.GetTempFileName();
        await using var target = File.Create(filePath);
        await section.Body.CopyToAsync(target);
    }

    return Results.Ok();
});

```

This approach streams files directly to storage without intermediate buffering, supporting arbitrary file sizes limited only by available storage space.

## Best Practices for Production

When implementing large file uploads in ASP.NET Core:

1. **Prefer `MultipartReader`** for any endpoint accepting files larger than a few megabytes to avoid memory exhaustion.
2. **Configure section-specific limits** using `MultipartReaderOptions` to protect against malicious payloads without rejecting legitimate large files.
3. **Dispose streams immediately** using `await using` declarations to prevent handle leaks, especially when the framework creates temporary files.
4. **Validate content before processing** by checking `ContentDispositionHeaderValue` and content types within the read loop to reject unexpected data early.
5. **Leverage asynchronous I/O** with `CopyToAsync` to keep the request pipeline responsive during large transfers.

## Summary

- **`IFormFile` buffers entire files** in memory or disk before your code runs, creating scalability bottlenecks for large uploads.
- **Default size limits** in ASP.NET Core trigger exceptions before application code executes when using `IFormFile` with large payloads.
- **`MultipartReader` enables true streaming** by processing multipart sections incrementally without full request buffering.
- **Memory footprint remains constant** with `MultipartReader` regardless of file size, supporting gigabyte-scale uploads on standard hardware.
- **Always dispose streams** and validate content types when implementing streaming upload endpoints.

## Frequently Asked Questions

### What is the maximum file size for IFormFile in ASP.NET Core?

ASP.NET Core imposes a default request size limit of approximately 28 MB. While you can increase this limit using the `[RequestSizeLimit]` attribute or middleware configuration, `IFormFile` will still buffer the entire file in memory or to a temporary disk location before your action method executes, making it unsuitable for very large files regardless of the configured limit.

### How does MultipartReader handle memory differently than IFormFile?

`MultipartReader` processes the HTTP request body as a stream, yielding each multipart section sequentially through `ReadNextSectionAsync()`. This allows you to read and process file content in chunks while the upload is still in progress, maintaining constant memory usage. In contrast, `IFormFile` requires the complete request to buffer before exposing the file stream, causing memory consumption proportional to file size.

### When should I use IFormFile vs MultipartReader?

Use **IFormFile** for small, infrequent uploads under a few megabytes where convenience outweighs performance concerns, such as avatar uploads or document attachments in administrative interfaces. Use **MultipartReader** for large file uploads, high-throughput scenarios, or when you need to process content while streaming, such as video uploads, large data imports, or real-time virus scanning.

### How do I prevent resource leaks when handling file uploads?

Always dispose streams obtained from `IFormFile.OpenReadStream()` or `MultipartSection.Body` using `await using` statements or explicit `Dispose()` calls. For `IFormFile`, the framework automatically deletes temporary files when the stream is disposed, but only if you properly dispose the stream. In `MultipartReader` implementations, ensure you dispose each section's body stream before calling `ReadNextSectionAsync()` to release underlying network resources promptly.