ASP.NET Core
Handle MultipleUpload file uploads with ASP.NET Core minimal APIs using
IFormFile.
This guide covers both standard multipart uploads and chunked uploads.
Multipart Form Upload
Use ASP.NET Core minimal APIs to receive multipart/form-data uploads via IFormFile. Files are saved to disk with a unique GUID filename.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.WithHeaders("Content-Type", "X-Upload-Id", "X-Chunk-Index",
"X-Chunk-Count", "X-File-Name", "X-File-Size");
});
});
var app = builder.Build();
app.UseCors();
var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "uploads");
var chunkDir = Path.Combine(uploadDir, "chunks");
Directory.CreateDirectory(uploadDir);
app.MapPost("/api/upload", async (IFormFile file) =>
{
var fileGuid = Guid.NewGuid().ToString();
var ext = Path.GetExtension(file.FileName);
var destPath = Path.Combine(uploadDir, fileGuid + ext);
using (var stream = new FileStream(destPath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return Results.Ok(new
{
success = true,
fileGuid,
fileName = file.FileName,
fileSize = file.Length
});
})
.DisableAntiforgery(); Chunked Upload
Each chunk arrives as a raw body with custom headers. Store chunks in a temporary directory and assemble the final file once all chunks have arrived.
// Add after the /api/upload endpoint
app.MapPost("/api/upload-chunk", async (HttpContext context) =>
{
var headers = context.Request.Headers;
var uploadId = headers["X-Upload-Id"].ToString();
var chunkIndex = int.Parse(headers["X-Chunk-Index"].ToString());
var chunkCount = int.Parse(headers["X-Chunk-Count"].ToString());
var fileName = headers["X-File-Name"].ToString();
var fileSize = long.Parse(headers["X-File-Size"].ToString());
if (string.IsNullOrEmpty(uploadId) || chunkCount == 0)
{
return Results.BadRequest(new { success = false, error = "Missing headers" });
}
var sessionDir = Path.Combine(chunkDir, uploadId);
Directory.CreateDirectory(sessionDir);
// Save this chunk from raw body
var chunkPath = Path.Combine(sessionDir, $"chunk_{chunkIndex}");
using (var fs = new FileStream(chunkPath, FileMode.Create))
{
await context.Request.Body.CopyToAsync(fs);
}
// Count received chunks
var received = Directory.GetFiles(sessionDir, "chunk_*").Length;
if (received == chunkCount)
{
// Assemble final file
var fileGuid = Guid.NewGuid().ToString();
var ext = Path.GetExtension(fileName);
var finalPath = Path.Combine(uploadDir, fileGuid + ext);
using (var output = new FileStream(finalPath, FileMode.Create))
{
for (var i = 0; i < chunkCount; i++)
{
var cp = Path.Combine(sessionDir, $"chunk_{i}");
using var chunkStream = File.OpenRead(cp);
await chunkStream.CopyToAsync(output);
}
}
// Clean up chunks
Directory.Delete(sessionDir, recursive: true);
return Results.Ok(new
{
success = true,
fileGuid,
fileName,
fileSize
});
}
return Results.Ok(new { success = true, chunksReceived = received });
});
app.Run(); Kestrel Configuration
Increase the maximum request body size in Program.cs or appsettings.json if you need to accept files larger than 30 MB.
// In Program.cs, before builder.Build()
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = 200 * 1024 * 1024; // 200 MB
}); Expected JSON Response
Both endpoints must return this JSON structure on successful upload completion.
{
"success": true,
"fileGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fileName": "photo.jpg",
"fileSize": 204800
} Client-Side JavaScript
Initialize MultipleUpload on the client, pointing to your ASP.NET Core API endpoints.
<link rel="stylesheet" href="multipleupload.css" />
<div id="uploader"></div>
<script src="multipleupload.js"></script>
<script>
var uploader = new MultipleUpload("#uploader", {
uploadUrl: "/api/upload",
chunkUploadUrl: "/api/upload-chunk",
chunkSize: 2 * 1024 * 1024, // 2 MB chunks
onFileUploaded: function (file, response) {
console.log("Uploaded:", response.fileName, response.fileGuid);
}
});
</script>