在 ASP.NET Core 中映射静态文件

作者:Rick Anderson

默认情况下,静态文件(如 HTML、CSS、图像和 JavaScript)是 ASP.NET Core 应用直接提供给客户端的资产。

有关添加或取代本文中指导的 Blazor 静态文件指导,请参阅 ASP.NET Core Blazor 静态文件

提供静态文件

静态文件存储在项目的 Web 根目录中。 默认目录为 {content root}/wwwroot,但可通过 UseWebRoot 方法更改目录。 有关详细信息,请参阅内容根目录Web 根目录

采用 CreateBuilder 方法可将内容根目录设置为当前目录:

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

可通过 Web 根目录的相关路径访问静态文件。 例如,Web 应用程序项目模板包含 文件夹中的多个文件夹:

  • wwwroot
    • css
    • js
    • lib

请考虑到带有 wwwroot/images/MyImage.jpg 文件的应用。 用于访问 images 文件夹中的文件的 URI 格式为 https://<hostname>/images/<image_file_name>。 例如: https://localhost:5001/images/MyImage.jpg

映射静态资源路由终结点约定 (MapStaticAssets)

创建性能良好的 Web 应用需要优化将资源传递到浏览器的过程。 MapStaticAssets 可能的优化包括:

  • 提供一次给定资产,直到文件发生更改或浏览器清除其缓存为止。 设置 ETag上次修改 的标头。
  • 更新应用后,阻止浏览器使用旧或失效的资源。 设置 Last-Modified 标头。
  • 设置正确的缓存标头
  • 使用 缓存中间件
  • 尽可能提供资产的压缩版本。 此优化不包括缩小。
  • 使用 CDN 为离用户更近的资产提供服务。
  • 指纹资产,以防止重复使用旧版本的文件。

MapStaticAssets:

  • 将生成过程中收集有关静态 Web 资产的信息与运行时库集成,该库处理此信息以优化向浏览器提供的文件。
  • 用于优化应用中的静态资产交付的路由终结点约定。 它旨在处理所有 UI 框架,包括 Blazor、Razor、Pages 和 MVC。

MapStaticAssetsUseStaticFiles

MapStaticAssets 在 .NET 9 或更高版本的 ASP.NET Core 中提供。 UseStaticFiles 必须在 .NET 9 之前的版本中使用。

UseStaticFiles 提供静态文件,但它没有达到 MapStaticAssets 相同的优化级别。 MapStaticAssets 进行了优化,以提供应用在运行时了解的资产。 如果应用服务来自其他位置(如磁盘或嵌入资源)的资产,则应使用 UseStaticFiles

映射静态资产提供在调用 UseStaticFiles 时不可用的以下优点:

  • 在应用中,对所有资产进行构建时压缩,包括 JavaScript(JS)和样式表,但不包括已压缩的图像和字体资产。 GzipContent-Encoding: gz) 压缩在开发期间使用。 发布期间使用附带 Brotli (Content-Encoding: br) 压缩的 Gzip。
  • 使用每个文件的内容的 SHA-256 哈希的 Base64 编码字符串在生成时为所有资产创建指纹。 这可以防止重用旧版本的文件,即使缓存了旧文件。 指纹资产是使用 immutable 指令缓存的,这会导致浏览器在更改之前永远不会再次请求资产。 对于不支持该指令的 immutable 浏览器,将添加一个 max-age 指令
    • 即使资产没有指纹,也会使用文件的指纹哈希作为 ETags 值为每个静态资产生成基于内容的 ETag。 这可确保浏览器仅在文件内容发生更改时下载文件(或首次下载该文件)。
    • 在内部,框架将物理资产映射到其指纹,使应用能够:
      • 查找自动生成的资产,例如为 Razor 的 Blazor生成的 组件范围的 CSS,以及由JS描述的 资产。
      • <head> 页面内容中生成链接标记以预加载资产。
  • Visual Studio 热重载开发测试期间:
    • 从资产中删除完整性信息,以避免在应用运行时更改文件时出现问题。
    • 静态资产不会缓存,以确保浏览器始终检索当前内容。

映射静态资产不提供用于缩小或其他文件转换的功能。 缩小通常由自定义代码或 第三方工具处理。

使用 UseStaticFiles 支持以下功能,但使用 MapStaticAssets 不支持这些功能:

在 Web 根目录中提供文件

默认 Web 应用模板在 MapStaticAssets 中调用 Program.cs 方法,这将允许提供静态文件:

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

无参数 MapStaticAssets 方法重载将 Web 根目录中的文件标记为可用。 以下标记引用 wwwroot/images/MyImage.jpg

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

在上面的代码标记中,波形符 ~ 指向 Web 根目录

在网站根目录之外提供文件

考虑一个目录层次结构,其中要提供的静态文件位于 Web 根目录之外:

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

按如下方式配置静态文件中间件后,请求可访问 red-rose.jpg 文件:

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

在前面的代码中,MyStaticFiles 目录层次结构通过 StaticFiles URI 段公开 。 对 https://<hostname>/StaticFiles/images/red-rose.jpg 的请求将提供 red-rose.jpg 文件。

以下标记引用 MyStaticFiles/images/red-rose.jpg

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

若要从多个位置提供文件,请参阅从多个位置提供文件

设置 HTTP 响应标头

StaticFileOptions 对象可用于设置 HTTP 响应标头。 除配置从 Web 根目录提供静态文件外,以下代码还设置 标头:

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

上面的代码使静态文件在本地缓存中公开提供一周。

静态文件授权

ASP.NET Core 模板在调用 MapStaticAssets 之前调用 UseAuthorization。 大多数应用都遵循此模式。 在授权中间件之前调用 MapStaticAssets 时:

  • 不会对静态文件执行任何授权检查。
  • 由静态文件中间件提供的静态文件(例如 wwwroot 下的文件)可公开访问。

若要根据授权提供静态文件,请参阅 静态文件授权

从多个位置提供文件

请考虑以下显示 Razor 文件的 /MyStaticFiles/image3.png 页面:

@page

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

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

UseStaticFilesUseFileServer 默认为指向 wwwroot 的文件提供程序。 可使用其他文件提供程序提供 UseStaticFilesUseFileServer 的其他实例,从多个位置提供文件。 以下示例调用 UseStaticFiles 两次以提供来自 wwwrootMyStaticFiles 的文件:

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

使用上述代码:

以下代码会更新 WebRootFileProvider,使图像标记帮助程序能够提供版本:

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

注释

上述方法适用于 Razor Pages 和 MVC 应用。 有关适用于 Blazor Web App 的指南,请参阅 ASP.NET Core Blazor 静态文件

通过更新 IWebHostEnvironment.WebRootPath 提供 wwwroot 外的文件

IWebHostEnvironment.WebRootPath 设置为 wwwroot 以外的文件夹时:

  • 在开发环境中,在 wwwroot 和更新的 IWebHostEnvironment.WebRootPath 中发现的静态资产由 wwwroot 提供。
  • 在开发环境以外的任何环境中,重复的静态资产会从更新的 IWebHostEnvironment.WebRootPath 文件夹提供。

请考虑使用空 Web 模板创建的 Web 应用:

  • Index.htmlwwwroot 中包含一个 wwwroot-custom 文件。

  • 使用以下设置了 Program.cs 的已更新 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();
    

在前面的代码中,请求 /

  • 在开发环境中,返回 wwwroot/Index.html
  • 在开发环境以外的任何环境中,返回 wwwroot-custom/Index.html

若要确保返回 wwwroot-custom 中的资产,请使用以下方法之一:

  • wwwroot 中删除重复命名的资产。

  • "ASPNETCORE_ENVIRONMENT" 中的 Properties/launchSettings.json 设置为 "Development" 以外的任何值。

  • 通过在项目文件中设置 <StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>,完全禁用静态 Web 资产。 警告,禁用静态 Web 资产会禁用 Razor 类库

  • 将以下 XML 添加到项目文件:

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

以下代码将 IWebHostEnvironment.WebRootPath 更新为非开发值,从而保证从 wwwroot-custom(而不是 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();

其他资源