Skip to content

Static Assets & MapStaticAssets

Key Points

  • MapStaticAssets (.NET 9+) replaces UseStaticFiles for modern apps. Pre-computes ETags, fingerprints filenames, automatically gzip+brotli, and integrates with build-time asset pipelines.
  • Outputs immutable, fingerprinted URLs (site.6f8b2.css) with Cache-Control: public, max-age=31536000, immutable.
  • UseStaticFiles still works and is still appropriate for non-fingerprinted dev/legacy scenarios.
  • Production hosting almost always puts a CDN in front of static assets — MapStaticAssets makes the CDN edge configuration trivial because every URL is content-addressable.

Concepts (deep dive)

MapStaticAssets (.NET 9+)

var app = builder.Build();
app.MapStaticAssets();   // serves wwwroot with optimized headers
app.MapRazorPages();
app.MapControllers();

What it does at build time:

  1. Scans wwwroot/.
  2. Computes content hash for each file → fingerprinted filename in the URL space.
  3. Pre-compresses with gzip and brotli.
  4. Pre-computes ETag (strong; based on content hash).
  5. Generates a manifest (appsettings.json-adjacent) mapping logical name → fingerprinted path.

What it does at runtime:

  • Serves the appropriate compression based on Accept-Encoding.
  • Sets Cache-Control: public, max-age=31536000, immutable for fingerprinted URLs.
  • Returns 304 on If-None-Match cache validation.
<link rel="stylesheet" href="@Assets["css/site.css"]" />

The Assets tag helper / accessor resolves logical to fingerprinted path. In Blazor:

<link rel="stylesheet" href="@Assets["css/site.css"]" />

UseStaticFiles — the older approach

app.UseStaticFiles();   // serves wwwroot
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "uploads")),
    RequestPath = "/uploads",
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers.CacheControl = "public, max-age=600";
    }
});

UseStaticFiles doesn't fingerprint — combine with asp-append-version="true" tag helper for cache-busting.

Asset pipelines (Vite, esbuild, Webpack)

For complex frontends, the build pipeline produces fingerprinted assets that you serve via MapStaticAssets:

src/styles.css ──► Vite build ──► wwwroot/dist/styles.6f8b2.css
                                  wwwroot/dist/manifest.json

@Assets["dist/styles.css"] reads the manifest to find the actual fingerprinted name. .NET 9+'s MapStaticAssets integrates with this naturally.

Long cache + content addressing

The combination:

  • MapStaticAssets produces site.<hash>.css.
  • Browser caches it forever (max-age=31536000, immutable).
  • New content → new hash → new URL → fresh fetch automatically.

Result: zero-cost client cache. The HTML page (which references the fingerprinted URLs) gets short-lived cache (e.g., 1 minute).

CDN integration

Put Azure Front Door / Cloudflare / CloudFront in front of wwwroot:

client ──► CDN edge ──► origin (Kestrel + MapStaticAssets)

CDN serves cached responses; only fetches origin when cold. Combined with content-addressing, you can set hour-long edge cache without staleness concerns.

When to use UseStaticFiles instead

  • Pre-.NET 9 apps without migration plan.
  • Non-fingerprinted assets (user uploads, generated reports).
  • Scenarios where fingerprinting isn't feasible (legacy paths, third-party tools expecting fixed URLs).

For new .NET 9+ apps: prefer MapStaticAssets.

Range requests

app.UseStaticFiles();   // supports Range automatically

For audio/video streaming, both UseStaticFiles and MapStaticAssets handle Range requests for partial content (HTTP 206).

MIME types

var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".geojson"] = "application/geo+json";
provider.Mappings[".webmanifest"] = "application/manifest+json";

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
});

For unusual file types, register the MIME mapping explicitly.

Default files

app.UseDefaultFiles();   // serves index.html, default.html, etc.
app.UseStaticFiles();

Order matters: UseDefaultFiles rewrites / to /index.html; then UseStaticFiles serves it.


Code: correct vs wrong

❌ Wrong: serving raw wwwroot without cache headers

app.UseStaticFiles();   // default 24h cache; resources updated → stale

✅ Correct: fingerprint + long cache

app.MapStaticAssets();

Or with manual cache:

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        if (ctx.File.Name.Contains('.') && ctx.File.Name.Split('.').Length > 2)   // crude fingerprint check
            ctx.Context.Response.Headers.CacheControl = "public, max-age=31536000, immutable";
    }
});

❌ Wrong: serving sensitive files

// wwwroot/secrets.json — accessible publicly!

wwwroot/ is the public root. Sensitive files belong elsewhere (Storage/, Data/).

❌ Wrong: arbitrary file traversal

app.MapGet("/file/{path}", (string path) => Results.File($"./uploads/{path}"));
// "../etc/passwd" — directory traversal

✅ Correct: validate & sanitize

app.MapGet("/file/{path}", (string path, IWebHostEnvironment env) =>
{
    var safe = Path.GetFileName(path);   // strip ../
    var full = Path.Combine(env.ContentRootPath, "uploads", safe);
    if (!full.StartsWith(Path.Combine(env.ContentRootPath, "uploads")))
        return Results.NotFound();
    return File.Exists(full) ? Results.File(full) : Results.NotFound();
});

Design patterns for this topic

Pattern 1 — "MapStaticAssets for new apps"

  • Intent: modern asset pipeline with fingerprinting + compression.

Pattern 2 — "CDN in front of wwwroot"

  • Intent: edge caching for global users.

Pattern 3 — "Long cache + content addressing"

  • Intent: zero-stale-risk forever caching.

Pattern 4 — "Custom file provider for user uploads"

  • Intent: serve from non-default directories.

Pattern 5 — "Default files for SPA shell"

  • Intent: serve index.html for /.

Pros & cons / trade-offs

Approach Pros Cons
MapStaticAssets Modern; fingerprinting; compression .NET 9+ only
UseStaticFiles Universal No fingerprinting
asp-append-version Manual fingerprint Per-file overhead
CDN Edge cache Setup

When to use / when to avoid

  • Use MapStaticAssets for new .NET 9+ apps.
  • Use CDN in front of wwwroot for production.
  • Avoid serving sensitive data from wwwroot.
  • Avoid path traversal when serving user-named files.

Interview Q&A

Q1. What's MapStaticAssets (.NET 9+)? Modern static file serving: fingerprints filenames, pre-computes compressed (gzip/brotli) variants, sets immutable cache headers automatically.

Q2. Why fingerprint static URLs? Cache forever; bust automatically when content changes.

Q3. MapStaticAssets vs UseStaticFiles? MapStaticAssets handles fingerprinting + compression at build time. UseStaticFiles is the older middleware without those features.

Q4. What cache headers does MapStaticAssets set? Cache-Control: public, max-age=31536000, immutable for fingerprinted URLs.

Q5. How do you serve files from outside wwwroot? Use UseStaticFiles with StaticFileOptions.FileProvider = new PhysicalFileProvider(...).

Q6. Why not put secrets in wwwroot? wwwroot is publicly exposed by static-file serving. Sensitive files must live elsewhere.

Q7. How does CDN integrate? CDN points origin at your Kestrel; users hit CDN; CDN caches based on Cache-Control. Fingerprinted URLs work perfectly.

Q8. What's a FileExtensionContentTypeProvider? Maps file extensions to MIME types. Add custom mappings for unusual types.

Q9. How do you handle large media (range requests)? Both static-file serving options support Range automatically. Browsers/players send Range: bytes=0-1023; server responds 206 Partial Content.

Q10. What's the default file convention? UseDefaultFiles rewrites / to /index.html (or default.htm, etc.). Run before UseStaticFiles/MapStaticAssets.


Gotchas / common mistakes

  • ⚠️ Sensitive files in wwwroot — exposed.
  • ⚠️ Path traversal when serving user-named files.
  • ⚠️ Forgetting UseDefaultFiles/ returns 404 instead of index.html.
  • ⚠️ No CDN for global apps — slow remote regions.
  • ⚠️ Mixing fingerprinted and unfingerprinted — confusing cache strategy.

Further reading