ASP.NET Core 中的静态文件

Note

此版本不是本文的最新版本。 对于当前版本,请参阅本文的 .NET 9 版本

Warning

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅本文的 .NET 9 版本

Important

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅本文的 .NET 9 版本

静态文件(也称为静态资产)是未动态生成的 ASP.NET 核心应用的文件。 相反,它们会根据请求直接提供给客户端,例如 HTML、CSS、图像和 JavaScript 文件。

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

若要在 ASP.NET Core 中启用静态文件处理,请调用 MapStaticAssets

默认情况下,静态文件存储在项目的 Web 根 目录中。 默认目录是 {CONTENT ROOT}/wwwroot,其中 {CONTENT ROOT} 占位符是应用的 内容根目录。 只有 wwwroot 文件夹中的文件可寻址,因此无需担心其余代码。

只有具有特定文件扩展名并映射到受支持媒体类型的文件,才能作为静态 Web 资产。

在构建时发现静态 Web 资产,并通过内容指纹进行优化,以防止重复使用旧文件。 资产也会 压缩 ,以减少资产交付时间。

在运行时,所发现的静态 Web 资源将作为终结点进行公开,并应用了 HTTP 标头,例如 缓存头 和内容类型头。 在文件更改或浏览器清除缓存之前,资产只提供一次。 设置ETagLast-ModifiedContent-Type标头。 更新应用后,将阻止浏览器使用过时资产。

静态资产的交付基于 终结点路由,因此它适用于其他终结点感知功能,例如授权。 它旨在处理所有 UI 框架,包括 Blazor、Razor、Pages 和 MVC。

映射静态资产具有以下优势:

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

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

若要在 ASP.NET Core 中启用静态文件处理,请调用 UseStaticFiles

默认情况下,静态文件存储在项目的 Web 根 目录中。 默认目录是 {CONTENT ROOT}/wwwroot,其中 {CONTENT ROOT} 占位符是应用的 内容根目录。 只有 wwwroot 文件夹中的文件可寻址,因此无需担心其余代码。

在运行时,静态 Web 资产会由静态文件中间件返回,前提是请求中包含已应用的资产修改和内容类型标头。 设置ETagLast-ModifiedContent-Type标头。

静态文件中间件启用静态文件服务,当在应用的请求处理管道中调用UseStaticFiles时被使用。 文件从指定IWebHostEnvironment.WebRootPathWebRootFileProvider路径提供,通常默认为 Web 根文件夹wwwroot

还可以从 引用的项目和包中提供静态网站资源。

更改 Web 根目录

UseWebRoot如果要更改 Web 根,请使用该方法。 有关详细信息,请参阅 ASP.NET 核心基础知识概述

阻止在项目文件中发布wwwroot包含<Content>项目项的文件。 以下示例阻止在 wwwroot/local 及其子目录中发布内容:

<ItemGroup>
  <Content Update="wwwroot\local\**\*.*" CopyToPublishDirectory="Never" />
</ItemGroup>

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

var builder = WebApplication.CreateBuilder(args);

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

Host.CreateDefaultBuilder(args)

在调用 UseHttpsRedirection后的请求处理管道中,调用 MapStaticAssets 应用的请求处理管道,以启用从应用的 Web 根目录提供静态文件:

app.MapStaticAssets();

在调用 UseHttpsRedirection后的请求处理管道中,调用 UseStaticFiles 应用的请求处理管道,以启用从应用的 Web 根目录提供静态文件:

app.UseStaticFiles();

可通过 Web 根目录的相关路径访问静态文件。

若要访问指定位置的图像,请执行以下步骤 wwwroot/images/favicon.png

  • URL 格式: https://{HOST}/images/{FILE NAME}
    • 占位符号 {HOST} 是主机。
    • 占位符 {FILE NAME} 是文件名。
  • 示例
    • 绝对 URL: https://localhost:5001/images/favicon.png
    • 根目录相对 URL:images/favicon.png

在Blazor应用中,images/favicon.png从应用的favicon.png文件夹加载图标图像(wwwroot/images)。

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

在 Razor Pages 和 MVC 应用中,平铺字符 ~ 指向 Web 根。 在以下示例中,~/images/favicon.png从应用的favicon.png文件夹中加载图标图像(wwwroot/images):

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

对中间件管道进行短路

为了避免在匹配静态资产后运行整个中间件管道的默认行为,请在UseStaticFiles上调用ShortCircuitMapStaticAssets。 调用 ShortCircuit 会立即执行终结点并返回响应,从而阻止其他中间件执行静态资产请求:

app.MapStaticAssets().ShortCircuit();

在开发过程中控制静态文件缓存

在开发环境中运行时(例如在 Visual Studio 热重载 开发测试期间)时,框架会重写缓存标头,以防止浏览器缓存静态文件。 这有助于确保在文件更改时使用最新版本的文件,从而避免过时内容的问题。 在生产环境中,设置了正确的缓存标头,允许浏览器按预期缓存静态资产。

若要禁用此行为,请在开发环境的应用设置文件中设置为 EnableStaticAssetsDevelopmentCachingtrueappsettings.Development.json)。

Development 环境中的静态文件

在本地运行应用时,静态 Web 资产仅在开发环境中启用。 若要在本地开发和测试期间为除开发以外的环境启用静态文件(例如,在过渡环境中),请调用 UseStaticWebAssetsWebApplicationBuilder

Warning

调用UseStaticWebAssets以获取特定的环境,避免在生产环境中激活此功能,因为它会从磁盘上的其他位置提供文件,而不是从项目目录中的位置。 本节中的示例检查暂存环境是否使用 IsStaging

if (builder.Environment.IsStaging())
{
    builder.WebHost.UseStaticWebAssets();
}

通过 IWebHostEnvironment.WebRootPath 在 Web 根目录之外提供文件

如果 IWebHostEnvironment.WebRootPath 设置为不同于 wwwroot 的文件夹,将会展示以下默认行为:

  • 在开发环境中,如果具有相同名称的资产既在wwwroot中又在分配到wwwroot的其他文件夹中,则静态资产从WebRootPath中提供。
  • 在开发以外的任何环境中,将从文件夹中提供重复的 WebRootPath 静态资产。

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

  • Index.htmlwwwroot 中包含一个 wwwroot-custom 文件。
  • 文件 Program 更新以设置 WebRootPath = "wwwroot-custom"
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    WebRootPath = "wwwroot-custom"
});

默认情况下,对于请求 /

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

使用以下方法 wwwroot-custom ,来确保始终返回来自的资产:

  • 删除wwwroot中重名资产。

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

  • 在应用的项目文件中,通过将<StaticWebAssetsEnabled>设置为false来禁用静态 Web 资源。 警告: 禁用静态 Web 资产会 Razor 禁用类库

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

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

以下代码更新 WebRootPath 为非开发值(Staging),保证从 wwwroot-custom 中返回重复内容,而不是 wwwroot

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    EnvironmentName = Environments.Staging,
    WebRootPath = "wwwroot-custom"
});

静态文件中间件

静态文件中间件在特定静态文件场景下提供静态文件服务,通常结合映射静态资源终结点路由约定(MapStaticAssets)一起使用。

静态文件中间件在请求处理中被包含,当在应用的请求处理管道中调用UseStaticFiles时,通常是在添加映射静态资产终结点约定(MapStaticAssets)之后。

映射静态资产终结点约定用于面向 .NET 9 或更高版本的应用。 静态文件中间件必须在面向 .NET 9 之前的 .NET 版本的应用中使用。

静态文件中间件提供静态文件,但它不提供与“映射静态资源”端点惯例相同的优化级别。 仅依赖静态文件中间件时,地图静态资产终结点约定的构建时压缩和指纹功能将不可用。

端点约定已针对应用在运行时可识别的资源进行优化,以便更高效地提供服务。 如果应用提供来自其他位置(例如磁盘或嵌入资源)的资产,则应使用静态文件中间件。

静态文件中间件支持本文中介绍的以下功能,但不支持映射静态资源终结点约定:

通过 UseStaticFiles 在 Web 根目录之外提供文件

请考虑以下目录结构,其中静态文件位于一个名为的文件夹中,而不是应用程序的ExtraStaticFiles下:

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

通过配置静态文件中间件的新实例,请求可以访问 red-rose.jpg

以下 API 的命名空间:

using Microsoft.Extensions.FileProviders;

在对现有调用 MapStaticAssets (.NET 9 或更高版本) 或 UseStaticFiles (.NET 8 或更早版本) 之后的请求处理管道中:

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "ExtraStaticFiles")),
    RequestPath = "/static-files"
});

在前面的代码中, ExtraStaticFiles 目录层次结构通过 static-files URL 段公开。 对 https://{HOST}/StaticFiles/images/red-rose.jpg 的请求(其中 {HOST} 占位符是主机)为 red-rose.jpg 文件提供服务。

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

<img src="static-files/images/red-rose.jpg" alt="A red rose" />

对于前面的示例,Razor Pages 和 MVC 视图(src="~/StaticFiles/images/red-rose.jpg")中支持波浪号斜杠表示法,但 Razor 应用中的 Blazor 组件不支持。

从多个位置提供文件

本部分中的指南适用于 Razor Pages 和 MVC 应用。 有关适用于 Blazor Web App 的指南,请参阅 ASP.NET Core Blazor 静态文件

请考虑显示公司徽标的以下标记:

<img src="~/logo.png" asp-append-version="true" alt="Company logo">

开发人员打算使用 映像标记帮助程序 来追加版本,并从自定义位置(名为 ExtraStaticFiles文件夹)提供文件。

以下示例调用 MapStaticAssetswwwroot 提供文件服务,以及使用 UseStaticFilesExtraStaticFiles 提供文件服务:

在对现有调用 MapStaticAssets (.NET 9 或更高版本) 或 UseStaticFiles (.NET 8 或更早版本) 之后的请求处理管道中:

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

以下示例调用UseStaticFiles两次,以分别从wwwrootExtraStaticFiles 提供文件。

在现有调用 UseStaticFiles后的请求处理管道中:

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

使用前面的代码来显示文件ExtraStaticFiles/logo.png。 但是,图像标记助手AppendVersion)未应用,因为标记帮助程序依赖于WebRootFileProvider,而WebRootFileProvider尚未更新以包含文件夹。

以下代码使用 WebRootFileProviderExtraStaticFiles 更新以包含 CompositeFileProvider 文件夹。 这使图像标签帮助程序能够将版本应用于ExtraStaticFiles文件夹中的图像。

以下 API 的命名空间:

using Microsoft.Extensions.FileProviders;

在对现有调用 MapStaticAssets (.NET 9 或更高版本)或 UseStaticFiles (.NET 8 或更早版本)之前的请求处理管道中:

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

var compositeProvider = new CompositeFileProvider(webRootProvider, newPathProvider);

app.Environment.WebRootFileProvider = compositeProvider;

UseStaticFilesUseFileServer 默认为指向 wwwroot 的文件提供程序。 可使用其他文件提供程序提供 UseStaticFilesUseFileServer 的其他实例,从多个位置提供文件。 有关详细信息,请参阅 使用 UseFileServer for wwwroot 时仍然需要 UseStaticFiles(dotnet/AspNetCore.Docs #15578)

设置 HTTP 响应标头

使用 StaticFileOptions 设置 HTTP 响应标头。 除了将静态文件中间件配置为提供静态文件外,以下代码将标头设置为 Cache-Control 604,800 秒(一周)。

以下 API 的命名空间:

using Microsoft.AspNetCore.Http;

在对现有调用 MapStaticAssets (.NET 9 或更高版本) 或 UseStaticFiles (.NET 8 或更早版本) 之后的请求处理管道中:

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers.Append(
            "Cache-Control", "public, max-age=604800");
    }
});

大量资产集合

处理大量资产(被视为大约 1,000 个或更多资产)时,我们建议使用捆绑程序来减少应用程序所提供的资产总数,并合并MapStaticAssetsUseStaticFiles

MapStaticAssets 及时地加载在资源生成过程中捕获的预先计算的元数据,以支持压缩、缓存和指纹。 这些功能的成本是应用占用更大的内存。 对于频繁访问的资产,通常值得花费。 对于不经常访问的资产,权衡后可能不值得去承担这些成本。

如果不使用捆绑,我们建议结合 MapStaticAssetsUseStaticFiles。 以下示例演示了该方法。

在项目文件中(.csproj),使用 StaticWebAssetEndpointExclusionPattern MSBuild 属性从用于 MapStaticAssets 的最终清单中过滤终结点。 被排除的文件由 UseStaticFiles 提供,不受益于压缩、缓存和指纹识别。

设置值 StaticWebAssetEndpointExclusionPattern时,请保留 $(StaticWebAssetEndpointExclusionPattern) 以保留框架的默认排除模式。 在分号分隔的列表中添加其他模式。

在以下示例中,排除模式会将 lib/icons 文件夹中的静态文件排除,这表示一批假设的图标:

<StaticWebAssetEndpointExclusionPattern>
  $(StaticWebAssetEndpointExclusionPattern);lib/icons/**
</StaticWebAssetEndpointExclusionPattern>

app.UseHttpsRedirection(); 文件中经过 HTTPS 重定向中间件(Program)处理后:

app.UseStaticFiles();

app.UseAuthorization();

app.MapStaticAssets();

静态文件授权

当应用采用 回退授权策略时,所有未显式指定授权策略的请求都需要授权,包括授权中间件处理请求后对静态文件的请求。 通过将 AllowAnonymousAttribute 应用于静态文件的端点构建器,允许匿名访问静态文件:

app.MapStaticAssets().Add(endpointBuilder => 
    endpointBuilder.Metadata.Add(new AllowAnonymousAttribute()));

当应用采用 回退授权策略时,所有未显式指定授权策略的请求都需要授权,包括授权中间件处理请求后对静态文件的请求。 ASP.NET Core 模板允许通过在调用UseStaticFiles之前调用UseAuthorization来匿名访问静态文件。 大多数应用都遵循此模式。 如果在授权中间件之前调用静态文件中间件:

  • 不会对静态文件执行任何授权检查。
  • 静态文件中间件提供的静态文件,如 Web 根目录下的文件(通常为 wwwroot),可以公开访问。

根据授权提供静态文件:

  • 确认应用设置 回退授权策略 以要求经过身份验证的用户。
  • 将静态文件存储在应用的 Web 根目录之外。
  • 调用 UseAuthorization后,调用 UseStaticFiles,指定 Web 根外部静态文件文件夹的路径。

以下 API 的命名空间:

using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.FileProviders;

服务注册:

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

在调用 UseAuthorization后的请求处理管道中:

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "SecureStaticFiles")),
    RequestPath = "/static-files"
});

以下 API 的命名空间:

using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.FileProviders;

Startup.ConfigureServices中:

services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

Startup.Configure调用UseAuthorization之后:

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.ContentRootPath, "SecureStaticFiles")),
    RequestPath = "/static-files"
});

在前面的代码中,回退授权策略需要经过身份验证的用户。 指定其自己的授权要求的终结点(如控制器和 Razor 页面)不使用回退授权策略。 例如,具有 Razor 或 [AllowAnonymous][Authorize(PolicyName="MyPolicy")] Pages、控制器或操作方法使用应用的授权属性,而不是回退授权策略。

RequireAuthenticatedUserDenyAnonymousAuthorizationRequirement 添加到当前实例,这将强制对当前用户进行身份验证。

存储在应用的 Web 根目录中的静态资产是可公开访问的,因为之前UseStaticFiles调用默认的静态文件中间件(UseAuthorization)。 文件夹中的 SecureStaticFiles 静态资产需要身份验证。

还有一种根据授权提供文件的方法是:

  • 将文件存储在 Web 根目录之外以及可以被静态文件中间件访问的任何目录之外。
  • 通过应用授权的操作方法提供文件服务,并返回 FileResult 对象。

从Razor页面(Pages/BannerImage.cshtml.cs):

public class BannerImageModel : PageModel
{
    private readonly IWebHostEnvironment _env;

    public BannerImageModel(IWebHostEnvironment env) => _env = env;

    public PhysicalFileResult OnGet()
    {
        var filePath = Path.Combine(
            _env.ContentRootPath, "SecureStaticFiles", "images", "red-rose.jpg");

        return PhysicalFile(filePath, "image/jpeg");
    }
}

来自控制器(Controllers/HomeController.cs):

[Authorize]
public IActionResult BannerImage()
{
    var filePath = Path.Combine(
        _env.ContentRootPath, "SecureStaticFiles", "images", "red-rose.jpg");

    return PhysicalFile(filePath, "image/jpeg");
}

上述方法要求对每个文件使用一个页面或终结点。

以下路由终结点示例返回经过身份验证的用户的文件。

Program 文件中:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AuthenticatedUsers", b => b.RequireAuthenticatedUser());
});

...

app.MapGet("/files/{fileName}", IResult (string fileName) => 
{
    var filePath = GetOrCreateFilePath(fileName);

    if (File.Exists(filePath))
    {
        return TypedResults.PhysicalFile(filePath, fileName);
    }

    return TypedResults.NotFound("No file found with the supplied file name");
})
.WithName("GetFileByName")
.RequireAuthorization("AuthenticatedUsers");

以下路由终结点示例上传管理员角色(“”admin)中经过身份验证的用户的文件。

Program 文件中:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminsOnly", b => b.RequireRole("admin"));
});

...

// IFormFile uses memory buffer for uploading. For handling large 
// files, use streaming instead. See the *File uploads* article
// in the ASP.NET Core documentation:
// https://free.blessedness.top/aspnet/core/mvc/models/file-uploads
app.MapPost("/files", async (IFormFile file, LinkGenerator linker, 
    HttpContext context) =>
{
    // Don't rely on the value in 'file.FileName', as it's only metadata that can 
    // be manipulated by the end-user. Consider the 'Utilities.IsFileValid' method 
    // that takes an 'IFormFile' and validates its signature within the 
    // 'AllowedFileSignatures'.

    var fileSaveName = Guid.NewGuid().ToString("N") + 
        Path.GetExtension(file.FileName);
    await SaveFileWithCustomFileName(file, fileSaveName);

    context.Response.Headers.Append("Location", linker.GetPathByName(context, 
        "GetFileByName", new { fileName = fileSaveName}));

    return TypedResults.Ok("File Uploaded Successfully!");
})
.RequireAuthorization("AdminsOnly");

Startup.ConfigureServices中:

services.AddAuthorization(options =>
{
    options.AddPolicy("AuthenticatedUsers", b => b.RequireAuthenticatedUser());
});

Startup.Configure中:

app.MapGet("/files/{fileName}", IResult (string fileName) => 
{
    var filePath = GetOrCreateFilePath(fileName);

    if (File.Exists(filePath))
    {
        return TypedResults.PhysicalFile(filePath, fileName);
    }

    return TypedResults.NotFound("No file found with the supplied file name");
})
.WithName("GetFileByName")
.RequireAuthorization("AuthenticatedUsers");

以下代码上传管理员角色(“”admin)中经过身份验证的用户的文件。

Startup.ConfigureServices中:

services.AddAuthorization(options =>
{
    options.AddPolicy("AdminsOnly", b => b.RequireRole("admin"));
});

Startup.Configure中:

// IFormFile uses memory buffer for uploading. For handling large 
// files, use streaming instead. See the *File uploads* article
// in the ASP.NET Core documentation:
// https://free.blessedness.top/aspnet/core/mvc/models/file-uploads
app.MapPost("/files", async (IFormFile file, LinkGenerator linker, 
    HttpContext context) =>
{
    // Don't rely on the value in 'file.FileName', as it's only metadata that can 
    // be manipulated by the end-user. Consider the 'Utilities.IsFileValid' method 
    // that takes an 'IFormFile' and validates its signature within the 
    // 'AllowedFileSignatures'.

    var fileSaveName = Guid.NewGuid().ToString("N") + 
        Path.GetExtension(file.FileName);
    await SaveFileWithCustomFileName(file, fileSaveName);

    context.Response.Headers.Append("Location", linker.GetPathByName(context, 
        "GetFileByName", new { fileName = fileSaveName}));

    return TypedResults.Ok("File Uploaded Successfully!");
})
.RequireAuthorization("AdminsOnly");

目录浏览

目录浏览允许在指定目录中列出目录。

出于安全考虑,目录浏览默认处于禁用状态。 有关详细信息,请参阅静态文件的安全注意事项

使用以下 API 启用目录浏览:

在下面的示例中:

  • images应用根目录中的文件夹保存用于目录浏览的图像。
  • 浏览图像的请求路径为 /DirectoryImages
  • 调用UseStaticFiles和设置FileProviderStaticFileOptions可显示指向各个文件的浏览器链接。

以下 API 的命名空间:

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

服务注册:

builder.Services.AddDirectoryBrowser();

在对现有调用 MapStaticAssets (.NET 9 或更高版本) 或 UseStaticFiles (.NET 8 或更早版本) 之后的请求处理管道中:

var fileProvider = new PhysicalFileProvider(
    Path.Combine(builder.Environment.WebRootPath, "images"));
var requestPath = "/DirectoryImages";

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = fileProvider,
    RequestPath = requestPath
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = fileProvider,
    RequestPath = requestPath
});

以下 API 的命名空间:

using Microsoft.Extensions.FileProviders;
using System.IO;

Startup.ConfigureServices中:

services.AddDirectoryBrowser();

在现有对 Startup.Configure 的调用之后执行 UseStaticFiles

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.WebRootPath, "images")),
    RequestPath = "/DirectoryImages"
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.WebRootPath, "images")),
    RequestPath = "/DirectoryImages"
});

前面的代码允许通过 URL wwwroot/images 浏览 https://{HOST}/DirectoryImages 文件夹的目录,并提供每个文件和文件夹的链接,其中 {HOST} 占位符表示主机。

AddDirectoryBrowser 添加目录浏览中间件所需的服务,包括 HtmlEncoder。 这些服务可能由其他调用添加,例如 AddRazorPages,我们建议调用 AddDirectoryBrowser 以确保添加服务。

提供默认文档

设置默认页面为访问者提供网站的起点。 若要从 wwwroot 中提供默认文件而不要求请求 URL 包含文件名,请调用该方法 UseDefaultFiles

UseDefaultFiles 是一个不提供文件服务的 URL 重写工具。 在对 .NET 9 或更高版本的 MapStaticAssets 调用或 .NET 8 或更低版本的 UseStaticFiles 调用之前的请求处理管道阶段:

app.UseDefaultFiles();

使用 UseDefaultFiles 请求对 wwwroot 中的文件夹搜索:

  • default.htm
  • default.html
  • index.htm
  • index.html

如同请求包含了文件名一样,提供从列表中找到的第一个文件。 浏览器 URL 继续反映请求的 URI。

以下代码将默认文件名更改为 default-document.html

var options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("default-document.html");
app.UseDefaultFiles(options);

合并静态文件、默认文档和目录浏览

UseFileServer 结合了 UseStaticFilesUseDefaultFilesUseDirectoryBrowser(可选)的功能。

在对现有调用 MapStaticAssets (.NET 9 或更高版本)或 UseStaticFiles (.NET 8 或更早版本)之后的请求处理管道中,调用 UseFileServer 以启用静态文件和默认文件的服务:

app.UseFileServer();

前面的示例未启用目录浏览。

以下代码支持提供静态文件、默认文件和目录浏览。

服务注册:

builder.Services.AddDirectoryBrowser();

在现有调用 UseStaticFiles后的请求处理管道中:

app.UseFileServer(enableDirectoryBrowsing: true);

Startup.ConfigureServices中:

services.AddDirectoryBrowser();

在现有对 Startup.Configure 的调用之后执行 UseStaticFiles

app.UseFileServer(enableDirectoryBrowsing: true);

对于主机地址(/),在默认UseFileServer页面(Razor)或默认 MVC 视图(Pages/Index.cshtml)之前,返回默认 HTML 文档。

考虑以下目录层次结构:

  • wwwroot
    • css
    • images
    • js
  • ExtraStaticFiles
    • images
      • logo.png
    • default.html

以下代码允许提供静态文件、默认文件和目录浏览 ExtraStaticFiles

以下 API 的命名空间:

using Microsoft.Extensions.FileProviders;

服务注册:

builder.Services.AddDirectoryBrowser();

在现有调用 UseStaticFiles后的请求处理管道中:

app.UseFileServer(new FileServerOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "ExtraStaticFiles")),
    RequestPath = "/static-files",
    EnableDirectoryBrowsing = true
});

以下 API 的命名空间:

using Microsoft.Extensions.FileProviders;
using System.IO;

Startup.ConfigureServices中:

services.AddDirectoryBrowser();

在现有对 Startup.Configure 的调用之后执行 UseStaticFiles

app.UseFileServer(new FileServerOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.ContentRootPath, "ExtraStaticFiles")),
    RequestPath = "/static-files",
    EnableDirectoryBrowsing = true
});

必须在 AddDirectoryBrowser 属性值为 EnableDirectoryBrowsing 时调用 true

使用前面的文件层次结构和代码,URL 解析,如下表所示( {HOST} 占位符是主机)。

URI 响应文件
https://{HOST}/static-files/images/logo.png ExtraStaticFiles/images/logo.png
https://{HOST}/static-files ExtraStaticFiles/default.html

如果目录中不存在默认命名的文件 ExtraStaticFileshttps://{HOST}/static-files 则返回包含可单击链接的目录列表,其中 {HOST} 占位符是主机。

UseDefaultFilesUseDirectoryBrowser 执行从不带尾部 / 的目标 URI 到带尾部 / 的目标 URI 的客户端重定向。 例如,从 https://{HOST}/static-files (无尾随 /)到 https://{HOST}/static-files/ (包括尾随 /)。 除非使用ExtraStaticFiles/选项,否则在RedirectToAppendTrailingSlash目录中的相对 URL,如果没有尾部斜杠(DefaultFilesOptions),将被视为无效。

将文件扩展名映射到 MIME 类型

Note

有关适用于 Blazor 应用的指南,请参阅 ASP.NET Core Blazor 静态文件

使用 FileExtensionContentTypeProvider.Mappings 来添加或修改文件扩展名至 MIME 内容类型的映射。 在以下示例中,多个文件扩展名映射到已知的 MIME 类型。 该 .rtf 扩展将被替换,并 .mp4 被删除:

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

...

// Set up custom content types - associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".myapp"] = "application/x-msdownload";
provider.Mappings[".htm3"] = "text/html";
provider.Mappings[".image"] = "image/png";
// Replace an existing mapping
provider.Mappings[".rtf"] = "application/x-msdownload";
// Remove MP4 videos
provider.Mappings.Remove(".mp4");

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

如果有多个要配置的静态文件选项,也可以使用以下命令设置提供程序 StaticFileOptions

var provider = new FileExtensionContentTypeProvider();

...

builder.Services.Configure<StaticFileOptions>(options =>
{
    options.ContentTypeProvider = provider;
});

app.UseStaticFiles();

Startup.Configure中:

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using System.IO;

...

// Set up custom content types - associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".myapp"] = "application/x-msdownload";
provider.Mappings[".htm3"] = "text/html";
provider.Mappings[".image"] = "image/png";
// Replace an existing mapping
provider.Mappings[".rtf"] = "application/x-msdownload";
// Remove MP4 videos
provider.Mappings.Remove(".mp4");

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.WebRootPath, "images")),
    RequestPath = "/images",
    ContentTypeProvider = provider
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(env.WebRootPath, "images")),
    RequestPath = "/images"
});

有关详细信息,请参阅 MIME 内容类型

非标准内容类型

静态文件中间件可理解近 400 种已知文件内容类型。 如果用户请求文件类型未知的文件,则静态文件中间件将请求传递给管道中的下一个中间件。 如果没有中间件处理请求,则返回“404 未找到”响应。 如果启用了目录浏览,则在目录列表中会显示该文件的链接。

以下代码支持提供未知内容类型,并将未知文件呈现为图像:

app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true,
    DefaultContentType = "image/png"
});

使用前面的代码,请求的文件含未知内容类型时,以图像形式返回请求。

Warning

启用 ServeUnknownFileTypes 会形成安全隐患。 它默认处于禁用状态,不建议使用。 将文件扩展名映射到 MIME 类型 可提供更安全的替代方法,用于为具有非标准扩展名的文件提供服务。

提供自定义静态文件清单

staticAssetsManifestPath如果是nullIHostEnvironment.ApplicationName则用于查找清单。 或者,指定清单文件的完整路径。 如果使用相对路径,框架将在AppContext.BaseDirectory中搜索该文件。

静态文件的安全注意事项

Warning

UseDirectoryBrowserUseStaticFiles 可能会泄漏机密。 强烈建议在生产中禁用目录浏览。 请仔细查看哪些目录是通过 UseStaticFilesUseDirectoryBrowser 启用的。 整个目录及其子目录均可公开访问。 将适合公开的文件存储在专用目录中,如 <content_root>/wwwroot。 将这些文件与 MVC 视图、Razor Pages 和配置文件等分开。

  • 使用 UseDirectoryBrowserUseStaticFiles 公开的内容的 URL 受大小写和基础文件系统字符限制的影响。 例如,Windows 不区分大小写,但 macOS 和 Linux 区分大小写。

  • 托管于 IIS 中的 ASP.NET Core 应用使用 ASP.NET Core 模块将所有请求转发到应用,包括静态文件请求。 不使用 IIS 静态文件处理程序,并且没有机会处理请求。

  • 在 IIS Manager 中完成以下步骤,删除服务器或网站级别的 IIS 静态文件处理程序:

    1. 转到“模块”功能
    2. 在列表中选择 StaticFileModule。
    3. 单击“操作”侧栏中的“删除”

Warning

如果启用了 IIS 静态文件处理程序且 ASP.NET Core 模块配置不正确,则会提供静态文件。 例如,如果文件未 web.config 部署,则会发生此情况。

  • 将代码文件(包括 .cs.cshtml)放在应用项目的 Web 根目录之外。 这样就在应用的客户端内容和基于服务器的代码间创建了逻辑分隔。 可以防止服务器端代码泄漏。

MSBuild 属性

下表显示了静态文件 MSBuild 属性和元数据说明。

资产 Description
EnableDefaultCompressedItems 启用默认压缩包括/排除模式。
CompressionIncludePatterns 要包括用于压缩的文件模式的分号分隔列表。
CompressionExcludePatterns 要从压缩中排除的文件模式的分号分隔列表。
EnableDefaultCompressionFormats 启用默认压缩格式(Gzip 和 Brotli)。
BuildCompressionFormats 在构建期间使用的压缩格式。
PublishCompressionFormats 发布期间要使用的压缩格式。
DisableBuildCompression 在生成过程中禁用压缩。
CompressDiscoveredAssetsDuringBuild 在构建过程中压缩发现的资产。
BrotliCompressionLevel Brotli 算法的压缩级别。
StaticWebAssetBuildCompressAllAssets 压缩构建过程中所有的资产,而不是仅限于构建过程中发现或计算的那些资产。
StaticWebAssetPublishCompressAllAssets 在发布期间压缩所有资产,而不仅仅是在构建过程中发现或计算的资产。
资产 Description
StaticWebAssetBasePath 库中所有资产的基 URL 路径。
StaticWebAssetsFingerprintContent 启用用于缓存破坏的内容指纹。
StaticWebAssetFingerprintingEnabled 为静态 Web 资产启用指纹功能。
StaticWebAssetsCacheDefineStaticWebAssetsEnabled 启用静态 Web 资产定义的缓存。
StaticWebAssetEndpointExclusionPattern 排除终结点的模式。
物料组 Description Metadata
StaticWebAssetContentTypeMapping 将文件模式映射到终结点的内容类型和缓存标头。 PatternCache
StaticWebAssetFingerprintPattern 定义用于将指纹应用于静态 Web 资产以用于缓存破坏的模式。 PatternExpression

元数据说明:

  • Pattern:用于匹配文件的 glob 模式。 对于 StaticWebAssetContentTypeMapping,它匹配文件以确定其内容类型(例如,*.js JavaScript 文件)。 StaticWebAssetFingerprintPattern 用于标识需要特殊指纹处理的多扩展文件(例如 *.lib.module.js)。

  • Cache:指定 Cache-Control 匹配内容类型的标头值。 这将控制浏览器的缓存行为(例如对媒体文件进行max-age=3600, must-revalidate)。

  • Expression:定义指纹标识插入文件名的方式。 默认值为 #[.{FINGERPRINT}],在扩展之前插入指纹({FINGERPRINT} 占位符)。

以下示例将位图文件模式 (.bmp) 映射到 image/bmp 内容类型,其中 {CACHE HEADER} 占位符表示 Cache-Control 标头,用于没有指纹的端点:

<ItemGroup>
  <StaticWebAssetContentTypeMapping Include="image/bmp" Cache="{CACHE HEADER}" Pattern="*.bmp" />
</ItemGroup>

运行时配置选项

下表介绍了运行时配置选项。

配置密钥 Description
ReloadStaticAssetsAtRuntime 启用开发时的静态资源热重载:提供经过修改的 Web 根目录文件(重新计算wwwroot,如需要重新压缩ETag),而不是使用构建时的清单版本。 默认情况下,仅在服务构建清单时启用,除非有明确设置。
DisableStaticAssetNotFoundRuntimeFallback 当启用true时,会抑制用于服务于生成清单中不存在的新增文件的回退端点。 当 false 存在或不存在时,文件存在检查的 {**path} 回退(GET/HEAD)会记录一个警告,并使用计算得到的 ETag 为文件提供服务。
EnableStaticAssetsDevelopmentCaching true时,保留资产描述符中的原始Cache-Control信息标头。 当false为空或缺失时,将Cache-Control标头重写为no-cache以避免在开发过程中出现过于激进的客户端缓存。
EnableStaticAssetsDevelopmentIntegrity true运行时,会在资产描述符上保持完整性属性。 如果 false 不存在,则删除任何完整性属性,以防止在开发过程中文件发生更改时不匹配。

其他资源