AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Minimal Api File Upload

skill-dotnet-skills-minimal-api-file-upload · by dotnet

File upload endpoints in ASP.NET minimal APIs (.NET 8+)

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-dotnet-skills-minimal-api-file-upload

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-dotnet-skills-minimal-api-file-upload)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Minimal Api File Upload? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Implementing File Uploads in ASP.NET Core Minimal APIs

When to Use

  • File upload endpoints in ASP.NET Core minimal APIs (.NET 8+)
  • Handling IFormFile or IFormFileCollection parameters
  • When you need size limits, content type validation, or streaming large files

When Not to Use

  • MVC controllers → [FromForm] IFormFile works directly with attributes
  • Simple JSON body → no file upload needed
  • Very large files (> 1GB) → use streaming with MultipartReader instead

Inputs

| Input | Required | Description | |-------|----------|-------------| | File parameter(s) | Yes | IFormFile or IFormFileCollection | | Size limits | Yes | Max file/request size | | Allowed types | No | Content type or extension restrictions |

Workflow

Step 1: CRITICAL — Understand IFormFile Binding in Minimal APIs

// In .NET 8+ minimal APIs, IFormFile binds automatically from multipart/form-data
// when it is the only complex parameter.
app.MapPost("/upload", (IFormFile file) => ...);

// CRITICAL: When you mix files with other form fields, use [FromForm] on all
// form-bound parameters (or group them into a single [FromForm] DTO).
app.MapPost("/upload-with-metadata",
    ([FromForm] IFormFile file, [FromForm] string description) =>
{
    return Results.Ok(new { file.FileName, Description = description });
});

// Multiple files: IFormFileCollection also binds automatically from multipart/form-data.
// You only need [FromForm] if you mix it with other form fields, as shown above.
app.MapPost("/upload-multiple", (IFormFileCollection files) =>
{
    return Results.Ok(files.Select(f => new { f.FileName, f.Length }));
});

Step 2: CRITICAL — File Size Limits Are Separate from Request Size Limits

// CRITICAL: There are TWO different size limits and you need to configure BOTH

// 1. Request body size limit (Kestrel level) — default is 30MB
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB
});

// 2. Form options — multipart body length limit — default is 128MB
builder.Services.Configure(options =>
{
    options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 10 MB
    options.ValueLengthLimit = 1024 * 1024; // 1 MB for form values
    options.MultipartHeadersLengthLimit = 16384; // 16 KB for section headers
});

// COMMON MISTAKE: Only increasing Kestrel MaxRequestBodySize
// upload still fails because FormOptions.MultipartBodyLengthLimit is exceeded

// COMMON MISTAKE: Only increasing FormOptions
// upload fails with "Request body too large" from Kestrel before reaching form parsing

// CRITICAL: Per-endpoint override with RequestSizeLimit attribute
app.MapPost("/upload-large", [RequestSizeLimit(200_000_000)] (IFormFile file) =>
{
    return Results.Ok(new { file.FileName, file.Length });
});

// CRITICAL: To disable the limit entirely (for streaming):
app.MapPost("/upload-unlimited", [DisableRequestSizeLimit] async (HttpContext context) =>
{
    // Handle manually
});

Step 3: CRITICAL — Anti-Forgery Auto-Validates Form Uploads in .NET 8+

// CRITICAL: In .NET 8+ with UseAntiforgery(), ALL form-bound endpoints
// automatically validate anti-forgery tokens, INCLUDING file uploads

builder.Services.AddAntiforgery();
var app = builder.Build();
app.UseAntiforgery();

// This endpoint now REQUIRES an anti-forgery token:
app.MapPost("/upload", (IFormFile file) => Results.Ok(file.FileName));
// Without the token → 400 Bad Request

// CRITICAL: For API-only file uploads (no anti-forgery needed), opt out:
app.MapPost("/api/upload", (IFormFile file) => Results.Ok(file.FileName))
    .DisableAntiforgery();  // CRITICAL: Must explicitly opt out

// COMMON MISTAKE: Getting 400 errors on file uploads and not realizing
// it's because UseAntiforgery() is in the pipeline

// WARNING: DisableAntiforgery() is safe for unauthenticated endpoints and
// endpoints using JWT bearer authentication. However, for endpoints
// authenticated with cookies, disabling antiforgery removes CSRF protection
// and exposes the endpoint to cross-site request forgery attacks.
// For cookie-authenticated endpoints, include a valid antiforgery token instead.

Step 4: CRITICAL — Validate File Content, Not Just Extension

app.MapPost("/upload", async (IFormFile file) =>
{
    // CRITICAL: Check content type AND file signature (magic bytes)
    // NEVER trust file extension alone — it can be spoofed

    // Allow only JPEG/PNG by default. To support more (e.g., GIF),
    // add the MIME type here AND validate its magic bytes below.
    var allowedTypes = new[] { "image/jpeg", "image/png" };
    if (!allowedTypes.Contains(file.ContentType, StringComparer.OrdinalIgnoreCase))
        return Results.BadRequest("File type not allowed");

    // CRITICAL: Check magic bytes for file type verification
    using var stream = file.OpenReadStream();
    var header = new byte[8];
    var bytesRead = await stream.ReadAsync(header, 0, header.Length);
    if (bytesRead 
{
    // Extract the multipart boundary from the Content-Type header
    var contentType = context.Request.ContentType;
    if (contentType == null)
        return Results.BadRequest("Missing Content-Type");

    // Safely parse the Content-Type header to avoid FormatException from MediaTypeHeaderValue.Parse
    if (!MediaTypeHeaderValue.TryParse(contentType, out var mediaType))
        return Results.BadRequest("Invalid Content-Type");

    var boundary = HeaderUtilities.RemoveQuotes(mediaType.Boundary).Value;
    if (string.IsNullOrWhiteSpace(boundary))
        return Results.BadRequest("Not a multipart request");

    var reader = new MultipartReader(boundary, context.Request.Body);

    // CRITICAL: ReadNextSectionAsync returns null when there are no more sections
    while (await reader.ReadNextSectionAsync() is { } section)
    {
        // Parse Content-Disposition to identify file sections
        if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition))
            continue;

        if (contentDisposition.DispositionType.Equals("form-data")
            && !string.IsNullOrEmpty(contentDisposition.FileName.Value))
        {
            // Sanitize the user-provided filename to prevent path traversal
            var originalFileName = contentDisposition.FileName.Value ?? string.Empty;
            var sanitizedFileName = Path.GetFileName(originalFileName.Trim('"'));
            var safeFile = $"{Guid.NewGuid()}";

            // CRITICAL: Stream directly to disk — avoids buffering in memory
            Directory.CreateDirectory("uploads");
            using var fileStream = File.Create(Path.Combine("uploads", safeFile));
            await section.Body.CopyToAsync(fileStream);
        }
    }

    return Results.Ok("Uploaded");
}).DisableAntiforgery();

// COMMON MISTAKE: Using IFormFile for very large files
// Multipart form parsing can buffer large uploads and consume memory/disk.
// Use MultipartReader for streaming directly to storage.

Common Mistakes

  1. Only configuring one size limit: Must configure BOTH Kestrel MaxRequestBodySize AND FormOptions.MultipartBodyLengthLimit.
  2. 400 errors from anti-forgery: In .NET 8+, UseAntiforgery() auto-validates form uploads. Use .DisableAntiforgery() for API endpoints (safe for JWT/unauthenticated; do NOT disable for cookie-authenticated endpoints).
  3. Trusting file.FileName: User-provided filename can contain path traversal. Generate a safe filename with Guid.NewGuid() and derive the extension from validated content.
  4. Trusting Content-Type only: Content type is client-spoofable. Always check magic bytes for actual file type verification.
  5. Using IFormFile for very large files: Multipart form parsing buffers with a memory threshold and spills to temp files. Use MultipartReader to stream data in chunks directly to storage without buffering the entire file.
  6. Deriving file extension from user input: Prefer deriving the extension from the validated content type or magic bytes rather than Path.GetExtension(file.FileName). If the original extension must be preserved, validate it against the detected content type.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.