Edit

Share via


Map static files in ASP.NET Core

By Rick Anderson

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.

Serve static files

Static files are stored within the project's web root directory. The default directory is {content root}/wwwroot, but it can be changed with the UseWebRoot method. For more information, see Content root and Web root.

The CreateBuilder method sets the content root to the current directory:

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.MapStaticAssets();

app.UseAuthorization();

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

app.Run();

Static files are accessible via a path relative to the web root. For example, the Web Application project templates contain several folders within the wwwroot folder:

  • wwwroot
    • css
    • js
    • lib

Consider an app with the wwwroot/images/MyImage.jpg file. The URI format to access a file in the images folder is https://<hostname>/images/<image_file_name>. For example, https://localhost:5001/images/MyImage.jpg

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 up proper caching headers.
  • 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.0 and later. UseStaticFiles must be used in versions prior to .NET 9.0.

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

The default web app templates call the MapStaticAssets method in Program.cs, which enables static files to be served:

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.MapStaticAssets();

app.UseAuthorization();

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

app.Run();

The parameterless MapStaticAssets method overload marks the files in web root as servable. The following markup references wwwroot/images/MyImage.jpg:

<img src="~/images/MyImage.jpg" class="img" alt="My image" />

In the preceding markup, the tilde character ~ points to the web root.

Serve files outside of web root

Consider a directory hierarchy in which the static files to be served reside outside of the web root:

  • 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 URI segment. A request to https://<hostname>/StaticFiles/images/red-rose.jpg 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 static file serving 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, 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();

Additional resources