ASP.NET Web API (.NET Framework) · Cross-Cutting

File Upload APIs

Profile photos, invoices, CSV imports — sooner or later your API must accept files. Web API 2 reads multipart uploads with a provider class, and you must validate everything about the file.

Advanced 6 min Lesson 28 of 36Covers ASP.NET Web API 2.2 on .NET Framework 4.8.
Before you start:Controllers and Endpoints
0/36 in ASP.NET Web API
On this page · 6 min · Advanced

Learning objectives#

  • Accept multipart/form-data uploads
  • Save files with MultipartFormDataStreamProvider
  • Validate size and extension and generate safe names
  • Stream a file download with the right headers

Explanation#

Browsers and apps send files as multipart/form-data: several parts, each with headers, some containing files and some ordinary form fields.

Check Request.Content.IsMimeMultipartContent(), then await Request.Content.ReadAsMultipartAsync(new MultipartFormDataStreamProvider(folder)). Files land in temporary names; provider.FormData holds the other fields.

Never trust the client’s file name or Content-Type. Check the extension against an allow-list, check the size, and store the file under a new GUID name outside the web root (App_Data).

IIS limits request size: maxRequestLength (in KB) and maxAllowedContentLength (in bytes) in web.config. Raise them deliberately and enforce your own lower limit in code.

For downloads, return an HttpResponseMessage with StreamContent, a Content-Type and a Content-Disposition: attachment header. Use Path.GetFileName on the requested name to block ../ path traversal.

For large or numerous files, store them in Azure Blob Storage or similar rather than on the web server’s disk.

Syntax#

Razor / Markup Illustrative
<!-- Web.config: allow uploads up to 10 MB -->
<system.web>
  <httpRuntime targetFramework="4.8" maxRequestLength="10240" />      <!-- KB -->
</system.web>
<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="10485760" />          <!-- bytes -->
    </requestFiltering>
  </security>
</system.webServer>

Code example: Upload and download endpoints#

C#FilesController.cs Illustrative
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Web.Http;


[RoutePrefix("api/files")]
public class FilesController : ApiController
{
    private static readonly string[] AllowedExtensions = { ".jpg", ".jpeg", ".png", ".pdf" };
    private const long MaxBytes = 5 * 1024 * 1024;   // 5 MB


    private static string UploadRoot => HostingEnvironment.MapPath("~/App_Data/uploads");


    [HttpPost, Route("")]
    public async Task<IHttpActionResult> Upload()
    {
        if (!Request.Content.IsMimeMultipartContent())
            return StatusCode(HttpStatusCode.UnsupportedMediaType);


        Directory.CreateDirectory(UploadRoot);
        var provider = new MultipartFormDataStreamProvider(UploadRoot);
        await Request.Content.ReadAsMultipartAsync(provider);


        var saved = new List<object>();
        foreach (var file in provider.FileData)
        {
            string original = Path.GetFileName(file.Headers.ContentDisposition.FileName.Trim('"'));
            string extension = Path.GetExtension(original).ToLowerInvariant();
            long size = new FileInfo(file.LocalFileName).Length;


            if (!AllowedExtensions.Contains(extension) || size > MaxBytes)
            {
                File.Delete(file.LocalFileName);
                return BadRequest(original + " is not allowed (jpg, png or pdf up to 5 MB).");
            }


            string storedName = Guid.NewGuid().ToString("N") + extension;
            File.Move(file.LocalFileName, Path.Combine(UploadRoot, storedName));
            saved.Add(new { original, storedName, size });
        }


        string description = provider.FormData["description"];
        return Ok(new { description, files = saved });
    }


    [HttpGet, Route("{name}")]
    public HttpResponseMessage Download(string name)
    {
        string safeName = Path.GetFileName(name);                 // blocks ../../web.config
        string path = Path.Combine(UploadRoot, safeName);
        if (!File.Exists(path)) return Request.CreateResponse(HttpStatusCode.NotFound);


        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StreamContent(File.OpenRead(path)),
        };
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeMapping.GetMimeMapping(safeName));
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = safeName };
        return response;
    }
}
Expected output
POST /api/files   Content-Type: multipart/form-data
  description=Invoice October
  file=@invoice.pdf (240 KB)
→ 200 {"description":"Invoice October","files":[{"original":"invoice.pdf","storedName":"9b1e...c4.pdf","size":245760}]}

POST /api/files   file=@setup.exe   → 400 "setup.exe is not allowed (jpg, png or pdf up to 5 MB)."
GET  /api/files/9b1e...c4.pdf        → 200  Content-Type: application/pdf
                                        Content-Disposition: attachment; filename=9b1e...c4.pdf

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. The action rejects anything that is not multipart with 415 Unsupported Media Type.
  2. MultipartFormDataStreamProvider streams each file to disk instead of holding it in memory.
  3. Each file’s extension and size are checked; rejected files are deleted immediately.
  4. Accepted files are renamed to a GUID, so user-supplied names never touch the file system.
  5. provider.FormData gives access to the ordinary form fields such as description.
  6. Download uses Path.GetFileName to strip directory parts and streams the file with download headers.

Real-world use case#

An HR portal lets employees upload ID documents. Files are validated, renamed, virus-scanned by a background job, then moved to private blob storage; downloads require the employee’s own token or an HR role.

Common mistakes#

Trusting the uploaded file name

A name like ../../web.config or shell.aspx can overwrite files or run code. Generate your own names and store outside the web root.

Reading uploads into memory

ReadAsByteArrayAsync on a 500 MB upload can exhaust memory. Stream to disk or blob storage.

Dots in download URLs

IIS may treat /api/files/report.pdf as a static file and return 404. Configure the handler (or pass the name as a query parameter).

Best practices#

  • Allow-list extensions and enforce a size limit in code
  • Rename files to GUIDs; keep originals only as metadata
  • Store outside the web root or in blob storage
  • Protect downloads with authorization

Remember this#

multipart/form-data → ReadAsMultipartAsync(provider).

Never trust the name, type or size the client claims.

Download = StreamContent + Content-Disposition: attachment.

Practice exercise#

Your task: Write a helper IsAllowedImage(string fileName, long size) that returns true only for .jpg/.jpeg/.png up to 2 MB, and use it in an upload action for profile photos.

Need a hint?
Path.GetExtension(fileName).ToLowerInvariant() and compare size with 2 * 1024 * 1024.
Show solution
C# Illustrative
using System.IO;
using System.Linq;


public static class UploadRules
{
    private static readonly string[] ImageExtensions = { ".jpg", ".jpeg", ".png" };


    public static bool IsAllowedImage(string fileName, long size)
    {
        string extension = Path.GetExtension(fileName ?? "").ToLowerInvariant();
        return ImageExtensions.Contains(extension) && size > 0 && size <= 2 * 1024 * 1024;
    }
}
// In the action: if (!UploadRules.IsAllowedImage(original, size)) return BadRequest("Images only, max 2 MB.");

Mini quiz#

File Upload APIs quizMultiple choice Question 1 of 3

Which provider saves multipart files to disk in Web API 2?

Score so far: 0/0

Want more? Practise all ASP.NET Web API MCQs · ASP.NET Web API interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.