Edit

Share via


Map static files in ASP.NET Core

Static files, such as HTML, CSS, images, and JavaScript, are assets an ASP.NET Core app serves directly to clients by default.

For Blazor static files guidance, which adds to or supersedes the guidance in this article, see ASP.NET Core Blazor static files.

Map Static Assets routing endpoint conventions (MapStaticAssets)

Creating performant web apps requires optimizing asset delivery to the browser. Possible optimizations with MapStaticAssets include:

  • Serve a given asset once until the file changes or the browser clears its cache. Set the ETag and Last-Modified headers.
  • Prevent the browser from using old or stale assets after an app is updated. Set the Last-Modified header.
  • Set appropriate caching headers on the response.
  • Use Caching Middleware.
  • Serve compressed versions of the assets when possible. This optimization doesn't include minification.
  • Use a CDN to serve the assets closer to the user.
  • Fingerprinting assets to prevent reusing old versions of files.

MapStaticAssets:

  • Integrates the information gathered about static web assets during the build and publish process with a runtime library that processes this information to optimize file serving to the browser.
  • Are routing endpoint conventions that optimize the delivery of static assets in an app. It's designed to work with all UI frameworks, including Blazor, Razor Pages, and MVC.

MapStaticAssets versus UseStaticFiles

MapStaticAssets is available in ASP.NET Core in .NET 9 or later. UseStaticFiles must be used in versions prior to .NET 9.

UseStaticFiles serves static files, but it doesn't provide the same level of optimization as MapStaticAssets. MapStaticAssets is optimized for serving assets that the app has knowledge of at runtime. If the app serves assets from other locations, such as disk or embedded resources, UseStaticFiles should be used.

Map Static Assets provides the following benefits that aren't available when calling UseStaticFiles:

  • Build-time compression for all the assets in the app, including JavaScript (JS) and stylesheets but excluding image and font assets that are already compressed. Gzip (Content-Encoding: gz) compression is used during development. Gzip with Brotli (Content-Encoding: br) compression is used during publish.
  • Fingerprinting for all assets at build time with a Base64-encoded string of the SHA-256 hash of each file's content. This prevents reusing an old version of a file, even if the old file is cached. Fingerprinted assets are cached using the immutable directive, which results in the browser never requesting the asset again until it changes. For browsers that don't support the immutable directive, a max-age directive is added.
    • Even if an asset isn't fingerprinted, content based ETags are generated for each static asset using the fingerprint hash of the file as the ETag value. This ensures that the browser only downloads a file if its content changes (or the file is being downloaded for the first time).
    • Internally, the framework maps physical assets to their fingerprints, which allows the app to:
      • Find automatically-generated assets, such as Razor component scoped CSS for Blazor's CSS isolation feature and JS assets described by JS import maps.
      • Generate link tags in the <head> content of the page to preload assets.
  • During Visual Studio Hot Reload development testing:
    • Integrity information is removed from the assets to avoid issues when a file is changed while the app is running.
    • Static assets aren't cached to ensure that the browser always retrieves current content.

Map Static Assets doesn't provide features for minification or other file transformations. Minification is usually handled by custom code or third-party tooling.

The following features are supported with UseStaticFiles but not with MapStaticAssets:

Serve files in web root

In the app's Program file, WebApplication.CreateBuilder sets the content root to the current directory. Call MapStaticAssets method to enable serving static files. The parameterless overload results in serving the files from the app's web root. The default web root directory is {CONTENT ROOT}/wwwroot, where the {CONTENT ROOT} placeholder is the content root.

var builder = WebApplication.CreateBuilder(args);

...

app.MapStaticAssets();

You can change the web root with the UseWebRoot method. For more information, see the Content root and Web root sections of the ASP.NET Core fundamentals overview article.

Static files are accessible via a path relative to the web root. For example, the Blazor Web App project template contains the lib folder within the wwwroot folder, which contains Bootstrap static assets.

If an app placed its images in an images folder in wwwroot, the following markup references wwwroot/images/favicon.png:

<link rel="icon" type="image/png" href="images/favicon.png" />

In Razor Pages and MVC apps (but not Blazor apps), the tilde character ~ points to the web root. In the following example, ~/images/icon.jpg loads the icon image (icon.jpg) from the app's wwwroot/images folder:

<img src="~/images/icon.jpg" alt="Icon image" />

The URL format for the preceding example is https://{HOST}/images/{FILE NAME}. The {HOST} placeholder is the host, and the {FILE NAME} placeholder is the file name. For the preceding example running at the app's localhost address on port 5001, the absolute URL is https://localhost:5001/images/icon.jpg.

Serve files outside of web root

Consider the following directory hierarchy with static files residing outside of the app's web root in a folder named MyStaticFiles:

  • wwwroot
    • css
    • images
    • js
  • MyStaticFiles
    • images
      • red-rose.jpg

A request can access the red-rose.jpg file by configuring the Static File Middleware as follows:

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();    //Serve files from wwwroot
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
    RequestPath = "/StaticFiles"
});

app.UseAuthorization();

app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

In the preceding code, the MyStaticFiles directory hierarchy is exposed publicly via the StaticFiles URL segment. A request to https://{HOST}/StaticFiles/images/red-rose.jpg, where the {HOST} placeholder is the host, serves the red-rose.jpg file.

The following markup references MyStaticFiles/images/red-rose.jpg:

<img src="~/StaticFiles/images/red-rose.jpg" class="img" alt="A red rose" />

To serve files from multiple locations, see Serve files from multiple locations.

Set HTTP response headers

A StaticFileOptions object can be used to set HTTP response headers. In addition to configuring the middleware to serve static files from the web root, the following code sets the Cache-Control header:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

var cacheMaxAgeOneWeek = (60 * 60 * 24 * 7).ToString();
app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers.Append(
            "Cache-Control", $"public, max-age={cacheMaxAgeOneWeek}");
    }
});

app.UseAuthorization();

app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();

app.Run();

The preceding code makes static files publicly available in the local cache for one week.

Static file authorization

The ASP.NET Core templates call MapStaticAssets before calling UseAuthorization. Most apps follow this pattern. When MapStaticAssets is called before the authorization middleware:

  • No authorization checks are performed on the static files.
  • Static files served by the Static File Middleware, such as those under wwwroot, are publicly accessible.

To serve static files based on authorization, see Static file authorization.

Serve files from multiple locations

Consider the following Razor page which displays the /MyStaticFiles/image3.png file:

@page

<p>Test /MyStaticFiles/image3.png</p>

<img src="~/image3.png" class="img" asp-append-version="true" alt="Test">

UseStaticFiles and UseFileServer default to the file provider pointing at wwwroot. Additional instances of UseStaticFiles and UseFileServer can be provided with other file providers to serve files from other locations. The following example calls UseStaticFiles twice to serve files from both wwwroot and MyStaticFiles:

app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"))
});

Using the preceding code:

The following code updates the WebRootFileProvider, which enables the Image Tag Helper to provide a version:

var webRootProvider = new PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider = new PhysicalFileProvider(
    Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"));

var compositeProvider = new CompositeFileProvider(webRootProvider, newPathProvider);

// Update the default provider.
app.Environment.WebRootFileProvider = compositeProvider;

app.MapStaticAssets();

Note

The preceding approach applies to Razor Pages and MVC apps. For guidance that applies to Blazor Web Apps, see ASP.NET Core Blazor static files.

Serve files outside wwwroot by updating IWebHostEnvironment.WebRootPath

When IWebHostEnvironment.WebRootPath is set to a folder other than wwwroot:

  • In the development environment, static assets found in both wwwroot and the updated IWebHostEnvironment.WebRootPath are served from wwwroot.
  • In any environment other than development, duplicate static assets are served from the updated IWebHostEnvironment.WebRootPath folder.

Consider a web app created with the empty web template:

  • Containing an Index.html file in wwwroot and wwwroot-custom.

  • With the following updated Program.cs file that sets WebRootPath = "wwwroot-custom":

    var builder = WebApplication.CreateBuilder(new WebApplicationOptions
    {
        Args = args,
        // Look for static files in "wwwroot-custom"
        WebRootPath = "wwwroot-custom"
    });
    
    var app = builder.Build();
    
    app.UseDefaultFiles();
    app.MapStaticAssets();
    
    app.Run();
    

In the preceding code, requests to /:

  • In the development environment, return wwwroot/Index.html.
  • In any environment other than development, return wwwroot-custom/Index.html.

To ensure assets from wwwroot-custom are returned, use one of the following approaches:

  • Delete duplicate named assets in wwwroot.

  • Set "ASPNETCORE_ENVIRONMENT" in Properties/launchSettings.json to any value other than "Development".

  • Completely disable static web assets by setting <StaticWebAssetsEnabled>false</StaticWebAssetsEnabled> in the project file. WARNING, disabling static web assets disables Razor Class Libraries.

  • Add the following XML to the project file:

    <ItemGroup>
        <Content Remove="wwwroot\**" />
    </ItemGroup>
    

The following code updates IWebHostEnvironment.WebRootPath to a non-Development value (Staging), guaranteeing duplicate content is returned from wwwroot-custom rather than wwwroot:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Examine Hosting environment: logging value
    EnvironmentName = Environments.Staging,
    WebRootPath = "wwwroot-custom"
});

var app = builder.Build();

app.Logger.LogInformation("ASPNETCORE_ENVIRONMENT: {env}",
      Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));

app.Logger.LogInformation("app.Environment.IsDevelopment(): {env}",
      app.Environment.IsDevelopment().ToString());

app.UseDefaultFiles();
app.MapStaticAssets();

app.Run();

When developing a server-side Blazor app and testing locally, see ASP.NET Core Blazor static files.

Additional resources