ASP.NET Core 中的静态文件
注意
此版本不是本文的最新版本。 有关当前版本,请参阅本文的 .NET 9 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文的 .NET 9 版本。
默认情况下,静态文件(如 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
文件夹中的多个文件夹:
wwwroot
css
js
lib
考虑一个包含 wwwroot/images/MyImage.jpg
文件的应用。 用于访问 images
文件夹中的文件的 URI 格式为 https://<hostname>/images/<image_file_name>
。 例如: https://localhost:5001/images/MyImage.jpg
MapStaticAssets
创建高性能 Web 应用需要优化到浏览器的资产传送。 可能的优化包括:
- 在文件发生更改或浏览器清除其缓存之前,提供一次给定资产。 设置 ETag 标头。
- 更新应用后,阻止浏览器使用旧资产或过时资产。 设置上次修改的标头。
- 设置正确的缓存标头。
- 使用缓存中间件。
- 尽可能提供资产的压缩版本。
- 使用 CDN 为离用户更近的资产提供服务。
- 最大程度地减少提供给浏览器的资产大小。 此优化不包括缩小。
MapStaticAssets 是路由终结点约定,用于优化应用中静态资产的交付。 它旨在处理所有 UI 框架,包括 Blazor、Razor、Pages 和 MVC。
UseStaticFiles
也提供静态文件,但它不提供与 MapStaticAssets
相同的优化级别。 有关 UseStaticFiles
和 MapStaticAssets
比较,请参阅优化静态 Web 资产传送。
在 Web 根目录中提供文件
默认 Web 应用模板在 Program.cs
中调用 MapStaticAssets 方法,这将允许提供静态文件:
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();
无参数 UseStaticFiles
方法重载将 Web 根目录中的文件标记为可用。 以下标记引用 wwwroot/images/MyImage.jpg
:
<img src="~/images/MyImage.jpg" class="img" alt="My image" />
在上面的代码标记中,波形符 ~
指向 Web 根目录。
提供 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 模板在调用 UseAuthorization 之前调用 MapStaticAssets。 大多数应用都遵循此模式。 如果在授权中间件之前调用静态文件中间件:
- 不会对静态文件执行任何授权检查。
- 由静态文件中间件提供的静态文件(例如
wwwroot
下的文件)可公开访问。
根据授权提供静态文件:
- 将它们存储在
wwwroot
之外。 - 调用
UseAuthorization
之后调用UseStaticFiles
,以指定路径。 - 设置回退授权策略。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using StaticFileAuth.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
app.MapRazorPages();
app.Run();
在前面的代码中,回退授权策略要求所有用户进行身份验证。 用于指定其自己的授权要求的终结点(如控制器、Razor Pages 等)不使用回退授权策略。 例如,具有 [AllowAnonymous]
或 [Authorize(PolicyName="MyPolicy")]
的 Razor Pages、控制器或操作方法使用应用的授权属性,而不是回退授权策略。
RequireAuthenticatedUser 将 DenyAnonymousAuthorizationRequirement 添加到当前实例,这将强制对当前用户进行身份验证。
wwwroot
下的静态资产是可公开访问的,因为在 UseAuthentication
之前会调用默认静态文件中间件 (app.UseStaticFiles();
)。 MyStaticFiles 文件夹中的静态资产需要身份验证。 示例代码对此进行了演示。
还有一种根据授权提供文件的方法是:
将文件存储在
wwwroot
和静态文件中间件可访问的任何目录之外。通过应用授权的操作方法为其提供服务,并返回 FileResult 对象:
[Authorize] public class BannerImageModel : PageModel { private readonly IWebHostEnvironment _env; public BannerImageModel(IWebHostEnvironment env) => _env = env; public PhysicalFileResult OnGet() { var filePath = Path.Combine( _env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg"); return PhysicalFile(filePath, "image/jpeg"); } }
上述方法要求对每个文件使用一个页面或终结点。 以下代码为经过身份验证的用户返回文件或上传文件:
app.MapGet("/files/{fileName}", IResult (string fileName) =>
{
var filePath = GetOrCreateFilePath(fileName);
if (File.Exists(filePath))
{
return TypedResults.PhysicalFile(filePath, fileDownloadName: $"{fileName}");
}
return TypedResults.NotFound("No file found with the supplied file name");
})
.WithName("GetFileByName")
.RequireAuthorization("AuthenticatedUsers");
app.MapPost("/files",
async (IFormFile file, LinkGenerator linker, HttpContext context) =>
{
// Don't rely on the file.FileName as it is only metadata that can be
// manipulated by the end-user. See 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");
app.Run();
前面的示例中的 IFormFile 使用内存缓冲区进行上传。 用于处理大型文件使用流式处理。 请参阅通过流式传输上传大型文件。
有关完整示例,请参阅 StaticFileAuth GitHub 文件夹。
目录浏览
目录浏览允许在指定目录中列出目录。
出于安全考虑,目录浏览默认处于禁用状态。 有关详细信息,请参阅静态文件的安全注意事项。
通过 AddDirectoryBrowser 和 UseDirectoryBrowser 启用目录浏览:
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.MapStaticAssets();
var fileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.WebRootPath, "images"));
var requestPath = "/MyImages";
// Enable displaying browser links.
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
上述代码允许使用 URL https://<hostname>/MyImages
浏览 wwwroot/images 文件夹的目录,并链接到每个文件和文件夹:
AddDirectoryBrowser
添加目录浏览中间件所需的服务,包括 HtmlEncoder。 这些服务可以通过其他调用(例如 AddRazorPages)添加,但我们建议调用 AddDirectoryBrowser
以确保将服务添加到所有应用中。
提供默认文档
设置默认页面为访问者提供网站的起点。 若要从 wwwroot
提供默认文件,而不要求请求 URL 包含文件名,请调用 UseDefaultFiles 方法:
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.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
要提供默认文件,必须在 UseStaticFiles
前调用 UseDefaultFiles
。 UseDefaultFiles
是不提供文件的 URL 重写工具。
使用 UseDefaultFiles
请求对 wwwroot
中的文件夹搜索:
default.htm
default.html
index.htm
index.html
如同请求包含了文件名一样,提供从列表中找到的第一个文件。 浏览器 URL 继续反映请求的 URI。 例如,在示例应用中,要从 wwwroot/def
中提供服务 default.html
的请求 https://localhost:<port>/def/
。
以下代码将默认文件名更改为 mydefault.html
:
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 options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
默认文档的 UseFileServer
UseFileServer 结合了 UseStaticFiles
、UseDefaultFiles
和 UseDirectoryBrowser
(可选)的功能。
调用 app.UseFileServer
以提供静态文件和默认文件。 未启用目录浏览:
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.UseFileServer();
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
以下代码提供静态文件、默认文件和目录浏览:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseRouting();
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
考虑以下目录层次结构:
wwwroot
css
images
js
MyStaticFiles
defaultFiles
default.html
image3.png
images
MyImage.jpg
以下代码提供静态文件、默认文件和 MyStaticFiles
的目录浏览:
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
必须在 EnableDirectoryBrowsing
属性值为 true
时调用 AddDirectoryBrowser。
使用前面的文件层次结构和代码,URL 解析如下:
URI | 响应 |
---|---|
https://<hostname>/StaticFiles/images/MyImage.jpg |
MyStaticFiles/images/MyImage.jpg |
https://<hostname>/StaticFiles |
目录列表 |
https://<hostname>/StaticFiles/defaultFiles |
MyStaticFiles/defaultFiles/default.html |
https://<hostname>/StaticFiles/defaultFiles/image3.png |
MyStaticFiles/defaultFiles//image3.png |
如果 MyStaticFiles 目录中不存在默认命名文件,则 https://<hostname>/StaticFiles
返回包含可单击链接的目录列表:
UseDefaultFiles 和 UseDirectoryBrowser 执行从不带尾部 /
的目标 URI 到带尾部 /
的目标 URI 的客户端重定向。 例如,从 https://<hostname>/StaticFiles
到 https://<hostname>/StaticFiles/
。 如果没有尾随斜杠 (/
),StaticFiles 目录中的相对 URL 无效,除非使用 DefaultFilesOptions 的 RedirectToAppendTrailingSlash 选项。
FileExtensionContentTypeProvider
FileExtensionContentTypeProvider 类包含映射属性,用作文件扩展名到 MIME 内容类型的映射。 在以下示例中,多个文件扩展名映射到了已知的 MIME 类型。 替换了 .rtf 扩展名,删除了 .mp4 :
using Microsoft.AspNetCore.StaticFiles;
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();
// 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
});
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
请参阅 MIME 内容类型。
非标准内容类型
静态文件中间件可理解近 400 种已知文件内容类型。 如果用户请求文件类型未知的文件,则静态文件中间件将请求传递给管道中的下一个中间件。 如果没有中间件处理请求,则返回“404 未找到”响应。 如果启用了目录浏览,则在目录列表中会显示该文件的链接。
以下代码提供未知类型,并以图像形式呈现未知文件:
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(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/png"
});
app.UseAuthorization();
app.MapDefaultControllerRoute().WithStaticAssets();
app.MapRazorPages().WithStaticAssets();
app.Run();
使用前面的代码,请求的文件含未知内容类型时,以图像形式返回请求。
警告
启用 ServeUnknownFileTypes 会形成安全隐患。 它默认处于禁用状态,不建议使用。 FileExtensionContentTypeProvider 提供了更安全的替代方法来提供含非标准扩展名的文件。
从多个位置提供文件
请考虑以下显示 /MyStaticFiles/image3.png
文件的 Razor 页面:
@page
<p> Test /MyStaticFiles/image3.png</p>
<img src="~/image3.png" class="img" asp-append-version="true" alt="Test">
UseStaticFiles
和 UseFileServer
默认为指向 wwwroot
的文件提供程序。 可使用其他文件提供程序提供 UseStaticFiles
和 UseFileServer
的其他实例,从多个位置提供文件。 以下示例调用 UseStaticFiles
两次以提供来自 wwwroot
和 MyStaticFiles
的文件:
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"))
});
使用上述代码:
- 将显示
/MyStaticFiles/image3.png
文件。 - 由于标记帮助程序依赖于 WebRootFileProvider,因此未应用图像标记帮助程序AppendVersion。
WebRootFileProvider
尚未更新为包含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 静态文件。
静态文件的安全注意事项
警告
UseDirectoryBrowser
和 UseStaticFiles
可能会泄漏机密。 强烈建议在生产中禁用目录浏览。 请仔细查看 UseStaticFiles
或 UseDirectoryBrowser
启用了哪些目录。 整个目录及其子目录均可公开访问。 将适合公开的文件存储在专用目录中,如 <content_root>/wwwroot
。 将这些文件与 MVC 视图、Razor Pages 和配置文件等分开。
使用
UseDirectoryBrowser
、UseStaticFiles
和MapStaticAssets
公开的内容的 URL 受区分大小写和基础文件系统字符限制的影响。 例如,Windows 不区分大小写,但 macOS 和 Linux 区分大小写。托管于 IIS 中的 ASP.NET Core 应用使用 ASP.NET Core 模块将所有请求转发到应用,包括静态文件请求。 不使用 IIS 静态文件处理程序,并且没有机会处理请求。
在 IIS Manager 中完成以下步骤,删除服务器或网站级别的 IIS 静态文件处理程序:
- 转到“模块”功能。
- 在列表中选择 StaticFileModule。
- 单击“操作”侧栏中的“删除” 。
警告
如果启用了 IIS 静态文件处理程序且 ASP.NET Core 模块配置不正确,则会提供静态文件。 例如,如果未部署 web.config 文件,则会发生这种情况。
- 将代码文件(包括
.cs
和.cshtml
)放在应用项目的 Web 根目录之外。 这样就在应用的客户端内容和基于服务器的代码间创建了逻辑分隔。 可以防止服务器端代码泄漏。
通过更新 IWebHostEnvironment.WebRootPath 提供 wwwroot 外的文件
当 IWebHostEnvironment.WebRootPath 设置为 wwwroot
以外的文件夹时:
- 在开发环境中,在
wwwroot
和更新的IWebHostEnvironment.WebRootPath
中发现的静态资产由wwwroot
提供。 - 在开发环境以外的任何环境中,重复的静态资产会从更新的
IWebHostEnvironment.WebRootPath
文件夹提供。
请考虑使用空 Web 模板创建的 Web 应用:
在
wwwroot
和wwwroot-custom
中包含一个Index.html
文件。使用以下设置了
WebRootPath = "wwwroot-custom"
的已更新Program.cs
文件: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
中删除重复的命名资产。将
Properties/launchSettings.json
中的"ASPNETCORE_ENVIRONMENT"
设置为"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();
其他资源
默认情况下,静态文件(如 HTML、CSS、图像和 JavaScript)是 ASP.NET Core 应用直接提供给客户端的资产。
提供静态文件
静态文件存储在项目的 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.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
可通过 Web 根目录的相关路径访问静态文件。 例如,Web 应用程序项目模板包含 wwwroot
文件夹中的多个文件夹:
wwwroot
css
js
lib
请考虑创建 wwwroot/images 文件夹,并添加 wwwroot/images/MyImage.jpg
文件。 用于访问 images
文件夹中的文件的 URI 格式为 https://<hostname>/images/<image_file_name>
。 例如,https://localhost:5001/images/MyImage.jpg
在 Web 根目录中提供文件
默认 Web 应用模板在 Program.cs
中调用 UseStaticFiles 方法,这将允许提供静态文件:
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();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
无参数 UseStaticFiles
方法重载将 Web 根目录中的文件标记为可用。 以下标记引用 wwwroot/images/MyImage.jpg
:
<img src="~/images/MyImage.jpg" class="img" alt="My image" />
在上面的代码标记中,波形符 ~
指向 Web 根目录。
提供 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(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
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();
app.MapRazorPages();
app.Run();
上述代码使静态文件在本地缓存中公开可用一周(604800 秒)。
静态文件授权
ASP.NET Core 模板在调用 UseAuthorization 之前调用 UseStaticFiles。 大多数应用都遵循此模式。 如果在授权中间件之前调用静态文件中间件:
- 不会对静态文件执行任何授权检查。
- 由静态文件中间件提供的静态文件(例如
wwwroot
下的文件)可公开访问。
根据授权提供静态文件:
- 将它们存储在
wwwroot
之外。 - 调用
UseAuthorization
之后调用UseStaticFiles
,以指定路径。 - 设置回退授权策略。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using StaticFileAuth.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
app.MapRazorPages();
app.Run();
在前面的代码中,回退授权策略要求所有用户进行身份验证。 用于指定其自己的授权要求的终结点(如控制器、Razor Pages 等)不使用回退授权策略。 例如,具有 [AllowAnonymous]
或 [Authorize(PolicyName="MyPolicy")]
的 Razor Pages、控制器或操作方法使用应用的授权属性,而不是回退授权策略。
RequireAuthenticatedUser 将 DenyAnonymousAuthorizationRequirement 添加到当前实例,这将强制对当前用户进行身份验证。
wwwroot
下的静态资产是可公开访问的,因为在 UseAuthentication
之前会调用默认静态文件中间件 (app.UseStaticFiles();
)。 MyStaticFiles 文件夹中的静态资产需要身份验证。 示例代码对此进行了演示。
还有一种根据授权提供文件的方法是:
将文件存储在
wwwroot
和静态文件中间件可访问的任何目录之外。通过应用授权的操作方法为其提供服务,并返回 FileResult 对象:
[Authorize] public class BannerImageModel : PageModel { private readonly IWebHostEnvironment _env; public BannerImageModel(IWebHostEnvironment env) => _env = env; public PhysicalFileResult OnGet() { var filePath = Path.Combine( _env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg"); return PhysicalFile(filePath, "image/jpeg"); } }
上述方法要求对每个文件使用一个页面或终结点。 以下代码为经过身份验证的用户返回文件或上传文件:
app.MapGet("/files/{fileName}", IResult (string fileName) =>
{
var filePath = GetOrCreateFilePath(fileName);
if (File.Exists(filePath))
{
return TypedResults.PhysicalFile(filePath, fileDownloadName: $"{fileName}");
}
return TypedResults.NotFound("No file found with the supplied file name");
})
.WithName("GetFileByName")
.RequireAuthorization("AuthenticatedUsers");
// IFormFile uses memory buffer for uploading. For handling large file use streaming instead.
// https://learn.microsoft.com/aspnet/core/mvc/models/file-uploads#upload-large-files-with-streaming
app.MapPost("/files", async (IFormFile file, LinkGenerator linker, HttpContext context) =>
{
// Don't rely on the file.FileName as it is only metadata that can be manipulated by the end-user
// Take a look at 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");
app.Run();
有关完整示例,请参阅 StaticFileAuth GitHub 文件夹。
目录浏览
目录浏览允许在指定目录中列出目录。
出于安全考虑,目录浏览默认处于禁用状态。 有关详细信息,请参阅静态文件的安全注意事项。
通过 AddDirectoryBrowser 和 UseDirectoryBrowser 启用目录浏览:
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
var fileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.WebRootPath, "images"));
var requestPath = "/MyImages";
// Enable displaying browser links.
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
上述代码允许使用 URL https://<hostname>/MyImages
浏览 wwwroot/images 文件夹的目录,并链接到每个文件和文件夹:
AddDirectoryBrowser
添加目录浏览中间件所需的服务,包括 HtmlEncoder。 这些服务可以通过其他调用(例如 AddRazorPages)添加,但我们建议调用 AddDirectoryBrowser
以确保将服务添加到所有应用中。
提供默认文档
设置默认页面为访问者提供网站的起点。 若要从 wwwroot
提供默认文件,而不要求请求 URL 包含文件名,请调用 UseDefaultFiles 方法:
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.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
要提供默认文件,必须在 UseStaticFiles
前调用 UseDefaultFiles
。 UseDefaultFiles
是不提供文件的 URL 重写工具。
使用 UseDefaultFiles
请求对 wwwroot
中的文件夹搜索:
default.htm
default.html
index.htm
index.html
如同请求包含了文件名一样,提供从列表中找到的第一个文件。 浏览器 URL 继续反映请求的 URI。
以下代码将默认文件名更改为 mydefault.html
:
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 options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
默认文档的 UseFileServer
UseFileServer 结合了 UseStaticFiles
、UseDefaultFiles
和 UseDirectoryBrowser
(可选)的功能。
调用 app.UseFileServer
以提供静态文件和默认文件。 未启用目录浏览:
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.UseFileServer();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
以下代码提供静态文件、默认文件和目录浏览:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseRouting();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
考虑以下目录层次结构:
wwwroot
css
images
js
MyStaticFiles
images
MyImage.jpg
default.html
以下代码提供静态文件、默认文件和 MyStaticFiles
的目录浏览:
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
必须在 EnableDirectoryBrowsing
属性值为 true
时调用 AddDirectoryBrowser。
使用前面的文件层次结构和代码,URL 解析如下:
URI | 响应 |
---|---|
https://<hostname>/StaticFiles/images/MyImage.jpg |
MyStaticFiles/images/MyImage.jpg |
https://<hostname>/StaticFiles |
MyStaticFiles/default.html |
如果 MyStaticFiles 目录中不存在默认命名文件,则 https://<hostname>/StaticFiles
返回包含可单击链接的目录列表:
UseDefaultFiles 和 UseDirectoryBrowser 执行从不带尾部 /
的目标 URI 到带尾部 /
的目标 URI 的客户端重定向。 例如,从 https://<hostname>/StaticFiles
到 https://<hostname>/StaticFiles/
。 如果没有尾随斜杠 (/
),StaticFiles 目录中的相对 URL 无效,除非使用 DefaultFilesOptions 的 RedirectToAppendTrailingSlash 选项。
FileExtensionContentTypeProvider
FileExtensionContentTypeProvider 类包含 Mappings
属性,用作文件扩展名到 MIME 内容类型的映射。 在以下示例中,多个文件扩展名映射到了已知的 MIME 类型。 替换了 .rtf 扩展名,删除了 .mp4 :
using Microsoft.AspNetCore.StaticFiles;
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();
// 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
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
请参阅 MIME 内容类型。
非标准内容类型
静态文件中间件可理解近 400 种已知文件内容类型。 如果用户请求文件类型未知的文件,则静态文件中间件将请求传递给管道中的下一个中间件。 如果没有中间件处理请求,则返回“404 未找到”响应。 如果启用了目录浏览,则在目录列表中会显示该文件的链接。
以下代码提供未知类型,并以图像形式呈现未知文件:
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(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/png"
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
使用前面的代码,请求的文件含未知内容类型时,以图像形式返回请求。
警告
启用 ServeUnknownFileTypes 会形成安全隐患。 它默认处于禁用状态,不建议使用。 FileExtensionContentTypeProvider 提供了更安全的替代方法来提供含非标准扩展名的文件。
从多个位置提供文件
请考虑以下显示 /MyStaticFiles/image3.png
文件的 Razor 页面:
@page
<p> Test /MyStaticFiles/image3.png</p>
<img src="~/image3.png" class="img" asp-append-version="true" alt="Test">
UseStaticFiles
和 UseFileServer
默认为指向 wwwroot
的文件提供程序。 可使用其他文件提供程序提供 UseStaticFiles
和 UseFileServer
的其他实例,从多个位置提供文件。 以下示例调用 UseStaticFiles
两次以提供来自 wwwroot
和 MyStaticFiles
的文件:
app.UseStaticFiles(); // Serve files from wwwroot
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"))
});
使用上述代码:
- 将显示
/MyStaticFiles/image3.png
文件。 - 由于标记帮助程序依赖于 WebRootFileProvider,因此未应用图像标记帮助程序AppendVersion。
WebRootFileProvider
尚未更新为包含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.UseStaticFiles();
注意
上述方法适用于 Razor Pages 和 MVC 应用。 有关适用于 Blazor Web App 的指南,请参阅 ASP.NET Core Blazor 静态文件。
静态文件的安全注意事项
警告
UseDirectoryBrowser
和 UseStaticFiles
可能会泄漏机密。 强烈建议在生产中禁用目录浏览。 请仔细查看 UseStaticFiles
或 UseDirectoryBrowser
启用了哪些目录。 整个目录及其子目录均可公开访问。 将适合公开的文件存储在专用目录中,如 <content_root>/wwwroot
。 将这些文件与 MVC 视图、Razor Pages 和配置文件等分开。
使用
UseDirectoryBrowser
和UseStaticFiles
公开的内容的 URL 受大小写和基础文件系统字符限制的影响。 例如,Windows 不区分大小写,但 macOS 和 Linux 区分大小写。托管于 IIS 中的 ASP.NET Core 应用使用 ASP.NET Core 模块将所有请求转发到应用,包括静态文件请求。 不使用 IIS 静态文件处理程序,并且没有机会处理请求。
在 IIS Manager 中完成以下步骤,删除服务器或网站级别的 IIS 静态文件处理程序:
- 转到“模块”功能。
- 在列表中选择 StaticFileModule。
- 单击“操作”侧栏中的“删除” 。
警告
如果启用了 IIS 静态文件处理程序且 ASP.NET Core 模块配置不正确,则会提供静态文件。 例如,如果未部署 web.config 文件,则会发生这种情况。
- 将代码文件(包括
.cs
和.cshtml
)放在应用项目的 Web 根目录之外。 这样就在应用的客户端内容和基于服务器的代码间创建了逻辑分隔。 可以防止服务器端代码泄漏。
通过更新 IWebHostEnvironment.WebRootPath 提供 wwwroot 外的文件
当 IWebHostEnvironment.WebRootPath 设置为 wwwroot
以外的文件夹时:
- 在开发环境中,在
wwwroot
和更新的IWebHostEnvironment.WebRootPath
中发现的静态资产由wwwroot
提供。 - 在开发环境以外的任何环境中,重复的静态资产会从更新的
IWebHostEnvironment.WebRootPath
文件夹提供。
请考虑使用空 Web 模板创建的 Web 应用:
在
wwwroot
和wwwroot-custom
中包含一个Index.html
文件。使用以下设置了
WebRootPath = "wwwroot-custom"
的已更新Program.cs
文件: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.UseStaticFiles(); app.Run();
在前面的代码中,请求 /
:
- 在开发环境中,返回
wwwroot/Index.html
- 在开发环境以外的任何环境中,返回
wwwroot-custom/Index.html
若要确保返回 wwwroot-custom
中的资产,请使用以下方法之一:
在
wwwroot
中删除重复的命名资产。将
Properties/launchSettings.json
中的"ASPNETCORE_ENVIRONMENT"
设置为"Development"
以外的任何值。通过在项目文件中设置
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
,完全禁用静态 Web 资产。 警告,禁用静态 Web 资产会禁用 Razor 类库。将以下 JSON 添加到项目文件:
<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.UseStaticFiles();
app.Run();
其他资源
作者:Rick Anderson 和 Kirk Larkin
默认情况下,静态文件(如 HTML、CSS、图像和 JavaScript)是 ASP.NET Core 应用直接提供给客户端的资产。
提供静态文件
静态文件存储在项目的 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.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
可通过 Web 根目录的相关路径访问静态文件。 例如,Web 应用程序项目模板包含 wwwroot
文件夹中的多个文件夹:
wwwroot
css
js
lib
请考虑创建 wwwroot/images 文件夹,并添加 wwwroot/images/MyImage.jpg
文件。 用于访问 images
文件夹中的文件的 URI 格式为 https://<hostname>/images/<image_file_name>
。 例如,https://localhost:5001/images/MyImage.jpg
在 Web 根目录中提供文件
默认 Web 应用模板在 Program.cs
中调用 UseStaticFiles 方法,这将允许提供静态文件:
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();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
无参数 UseStaticFiles
方法重载将 Web 根目录中的文件标记为可用。 以下标记引用 wwwroot/images/MyImage.jpg
:
<img src="~/images/MyImage.jpg" class="img" alt="My image" />
在上面的代码标记中,波形符 ~
指向 Web 根目录。
提供 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(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
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();
app.MapRazorPages();
app.Run();
上述代码使静态文件在本地缓存中公开可用一周(604800 秒)。
静态文件授权
ASP.NET Core 模板在调用 UseAuthorization 之前调用 UseStaticFiles。 大多数应用都遵循此模式。 如果在授权中间件之前调用静态文件中间件:
- 不会对静态文件执行任何授权检查。
- 由静态文件中间件提供的静态文件(例如
wwwroot
下的文件)可公开访问。
根据授权提供静态文件:
- 将它们存储在
wwwroot
之外。 - 调用
UseAuthorization
之后调用UseStaticFiles
,以指定路径。 - 设置回退授权策略。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using StaticFileAuth.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
app.MapRazorPages();
app.Run();
在前面的代码中,回退授权策略要求所有用户进行身份验证。 用于指定其自己的授权要求的终结点(如控制器、Razor Pages 等)不使用回退授权策略。 例如,具有 [AllowAnonymous]
或 [Authorize(PolicyName="MyPolicy")]
的 Razor Pages、控制器或操作方法使用应用的授权属性,而不是回退授权策略。
RequireAuthenticatedUser 将 DenyAnonymousAuthorizationRequirement 添加到当前实例,这将强制对当前用户进行身份验证。
wwwroot
下的静态资产是可公开访问的,因为在 UseAuthentication
之前会调用默认静态文件中间件 (app.UseStaticFiles();
)。 MyStaticFiles 文件夹中的静态资产需要身份验证。 示例代码对此进行了演示。
还有一种根据授权提供文件的方法是:
- 将文件存储在
wwwroot
和静态文件中间件可访问的任何目录之外。 - 通过应用授权的操作方法为其提供服务,并返回 FileResult 对象:
[Authorize]
public class BannerImageModel : PageModel
{
private readonly IWebHostEnvironment _env;
public BannerImageModel(IWebHostEnvironment env) =>
_env = env;
public PhysicalFileResult OnGet()
{
var filePath = Path.Combine(
_env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg");
return PhysicalFile(filePath, "image/jpeg");
}
}
目录浏览
目录浏览允许在指定目录中列出目录。
出于安全考虑,目录浏览默认处于禁用状态。 有关详细信息,请参阅静态文件的安全注意事项。
通过 AddDirectoryBrowser 和 UseDirectoryBrowser 启用目录浏览:
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
var fileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.WebRootPath, "images"));
var requestPath = "/MyImages";
// Enable displaying browser links.
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
上述代码允许使用 URL https://<hostname>/MyImages
浏览 wwwroot/images 文件夹的目录,并链接到每个文件和文件夹:
AddDirectoryBrowser
添加目录浏览中间件所需的服务,包括 HtmlEncoder。 这些服务可以通过其他调用(例如 AddRazorPages)添加,但我们建议调用 AddDirectoryBrowser
以确保将服务添加到所有应用中。
提供默认文档
设置默认页面为访问者提供网站的起点。 若要从 wwwroot
提供默认文件,而不要求请求 URL 包含文件名,请调用 UseDefaultFiles 方法:
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.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
要提供默认文件,必须在 UseStaticFiles
前调用 UseDefaultFiles
。 UseDefaultFiles
是不提供文件的 URL 重写工具。
使用 UseDefaultFiles
请求对 wwwroot
中的文件夹搜索:
default.htm
default.html
index.htm
index.html
如同请求包含了文件名一样,提供从列表中找到的第一个文件。 浏览器 URL 继续反映请求的 URI。
以下代码将默认文件名更改为 mydefault.html
:
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 options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
默认文档的 UseFileServer
UseFileServer 结合了 UseStaticFiles
、UseDefaultFiles
和 UseDirectoryBrowser
(可选)的功能。
调用 app.UseFileServer
以提供静态文件和默认文件。 未启用目录浏览:
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.UseFileServer();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
以下代码提供静态文件、默认文件和目录浏览:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseRouting();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
考虑以下目录层次结构:
wwwroot
css
images
js
MyStaticFiles
images
MyImage.jpg
default.html
以下代码提供静态文件、默认文件和 MyStaticFiles
的目录浏览:
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
必须在 EnableDirectoryBrowsing
属性值为 true
时调用 AddDirectoryBrowser。
使用前面的文件层次结构和代码,URL 解析如下:
URI | 响应 |
---|---|
https://<hostname>/StaticFiles/images/MyImage.jpg |
MyStaticFiles/images/MyImage.jpg |
https://<hostname>/StaticFiles |
MyStaticFiles/default.html |
如果 MyStaticFiles 目录中不存在默认命名文件,则 https://<hostname>/StaticFiles
返回包含可单击链接的目录列表:
UseDefaultFiles 和 UseDirectoryBrowser 执行从不带尾部 /
的目标 URI 到带尾部 /
的目标 URI 的客户端重定向。 例如,从 https://<hostname>/StaticFiles
到 https://<hostname>/StaticFiles/
。 如果没有尾随斜杠 (/
),StaticFiles 目录中的相对 URL 无效,除非使用 DefaultFilesOptions 的 RedirectToAppendTrailingSlash 选项。
FileExtensionContentTypeProvider
FileExtensionContentTypeProvider 类包含 Mappings
属性,用作文件扩展名到 MIME 内容类型的映射。 在以下示例中,多个文件扩展名映射到了已知的 MIME 类型。 替换了 .rtf 扩展名,删除了 .mp4 :
using Microsoft.AspNetCore.StaticFiles;
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();
// 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
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
请参阅 MIME 内容类型。
非标准内容类型
静态文件中间件可理解近 400 种已知文件内容类型。 如果用户请求文件类型未知的文件,则静态文件中间件将请求传递给管道中的下一个中间件。 如果没有中间件处理请求,则返回“404 未找到”响应。 如果启用了目录浏览,则在目录列表中会显示该文件的链接。
以下代码提供未知类型,并以图像形式呈现未知文件:
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(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/png"
});
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
使用前面的代码,请求的文件含未知内容类型时,以图像形式返回请求。
警告
启用 ServeUnknownFileTypes 会形成安全隐患。 它默认处于禁用状态,不建议使用。 FileExtensionContentTypeProvider 提供了更安全的替代方法来提供含非标准扩展名的文件。
从多个位置提供文件
请考虑以下显示 /MyStaticFiles/image3.png
文件的 Razor 页面:
@page
<p> Test /MyStaticFiles/image3.png</p>
<img src="~/image3.png" class="img" asp-append-version="true" alt="Test">
UseStaticFiles
和 UseFileServer
默认为指向 wwwroot
的文件提供程序。 可使用其他文件提供程序提供 UseStaticFiles
和 UseFileServer
的其他实例,从多个位置提供文件。 以下示例调用 UseStaticFiles
两次以提供来自 wwwroot
和 MyStaticFiles
的文件:
app.UseStaticFiles(); // Serve files from wwwroot
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"))
});
使用上述代码:
- 将显示
/MyStaticFiles/image3.png
文件。 - 由于标记帮助程序依赖于 WebRootFileProvider,因此未应用图像标记帮助程序AppendVersion。
WebRootFileProvider
尚未更新为包含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.UseStaticFiles();
静态文件的安全注意事项
警告
UseDirectoryBrowser
和 UseStaticFiles
可能会泄漏机密。 强烈建议在生产中禁用目录浏览。 请仔细查看 UseStaticFiles
或 UseDirectoryBrowser
启用了哪些目录。 整个目录及其子目录均可公开访问。 将适合公开的文件存储在专用目录中,如 <content_root>/wwwroot
。 将这些文件与 MVC 视图、Razor Pages 和配置文件等分开。
使用
UseDirectoryBrowser
和UseStaticFiles
公开的内容的 URL 受大小写和基础文件系统字符限制的影响。 例如,Windows 不区分大小写,但 macOS 和 Linux 区分大小写。托管于 IIS 中的 ASP.NET Core 应用使用 ASP.NET Core 模块将所有请求转发到应用,包括静态文件请求。 不使用 IIS 静态文件处理程序,并且没有机会处理请求。
在 IIS Manager 中完成以下步骤,删除服务器或网站级别的 IIS 静态文件处理程序:
- 转到“模块”功能。
- 在列表中选择 StaticFileModule。
- 单击“操作”侧栏中的“删除” 。
警告
如果启用了 IIS 静态文件处理程序且 ASP.NET Core 模块配置不正确,则会提供静态文件。 例如,如果未部署 web.config 文件,则会发生这种情况。
- 将代码文件(包括
.cs
和.cshtml
)放在应用项目的 Web 根目录之外。 这样就在应用的客户端内容和基于服务器的代码间创建了逻辑分隔。 可以防止服务器端代码泄漏。
通过更新 IWebHostEnvironment.WebRootPath 提供 wwwroot 外的文件
当 IWebHostEnvironment.WebRootPath 设置为 wwwroot
以外的文件夹时:
- 在开发环境中,在
wwwroot
和更新的IWebHostEnvironment.WebRootPath
中发现的静态资产由wwwroot
提供。 - 在开发环境以外的任何环境中,重复的静态资产会从更新的
IWebHostEnvironment.WebRootPath
文件夹提供。
请考虑使用空 Web 模板创建的 Web 应用:
在
wwwroot
和wwwroot-custom
中包含一个Index.html
文件。使用以下设置了
WebRootPath = "wwwroot-custom"
的已更新Program.cs
文件: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.UseStaticFiles(); app.Run();
在前面的代码中,请求 /
:
- 在开发环境中,返回
wwwroot/Index.html
- 在开发环境以外的任何环境中,返回
wwwroot-custom/Index.html
若要确保返回 wwwroot-custom
中的资产,请使用以下方法之一:
在
wwwroot
中删除重复的命名资产。将
Properties/launchSettings.json
中的"ASPNETCORE_ENVIRONMENT"
设置为"Development"
以外的任何值。通过在项目文件中设置
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
,完全禁用静态 Web 资产。 警告,禁用静态 Web 资产会禁用 Razor 类库。将以下 JSON 添加到项目文件:
<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.UseStaticFiles();
app.Run();
其他资源
作者:Rick Anderson 和 Kirk Larkin
默认情况下,静态文件(如 HTML、CSS、图像和 JavaScript)是 ASP.NET Core 应用直接提供给客户端的资产。
提供静态文件
静态文件存储在项目的 Web 根目录中。 默认目录为 {content root}/wwwroot
,但可通过 UseWebRoot 方法更改目录。 有关详细信息,请参阅内容根目录和 Web 根目录。
采用 CreateDefaultBuilder 方法可将内容根目录设置为当前目录:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
上述代码是使用 Web 应用程序模板创建的。
可通过 Web 根目录的相关路径访问静态文件。 例如,Web 应用程序项目模板包含 wwwroot
文件夹中的多个文件夹:
wwwroot
css
js
lib
请考虑创建 wwwroot/images 文件夹,并添加 wwwroot/images/MyImage.jpg
文件。 用于访问 images
文件夹中的文件的 URI 格式为 https://<hostname>/images/<image_file_name>
。 例如,https://localhost:5001/images/MyImage.jpg
在 Web 根目录中提供文件
默认 Web 应用模板在 Startup.Configure
中调用 UseStaticFiles 方法,这将允许提供静态文件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
无参数 UseStaticFiles
方法重载将 Web 根目录中的文件标记为可用。 以下标记引用 wwwroot/images/MyImage.jpg
:
<img src="~/images/MyImage.jpg" class="img" alt="My image" />
在上面的代码中,波形符 ~/
指向 Web 根目录。
提供 Web 根目录外的文件
考虑一个目录层次结构,其中要提供的静态文件位于 Web 根目录之外:
wwwroot
css
images
js
MyStaticFiles
images
red-rose.jpg
按如下方式配置静态文件中间件后,请求可访问 red-rose.jpg
文件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// using Microsoft.Extensions.FileProviders;
// using System.IO;
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
在前面的代码中,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 根目录提供静态文件外,以下代码还设置 Cache-Control
标头:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
const string cacheMaxAge = "604800";
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// using Microsoft.AspNetCore.Http;
ctx.Context.Response.Headers.Append(
"Cache-Control", $"public, max-age={cacheMaxAge}");
}
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
前面的代码将最长时间设置为 604800 秒(7 天)。
静态文件授权
ASP.NET Core 模板在调用 UseAuthorization 之前调用 UseStaticFiles。 大多数应用都遵循此模式。 如果在授权中间件之前调用静态文件中间件:
- 不会对静态文件执行任何授权检查。
- 由静态文件中间件提供的静态文件(例如
wwwroot
下的文件)可公开访问。
根据授权提供静态文件:
- 将它们存储在
wwwroot
之外。 - 调用
UseAuthorization
之后调用UseStaticFiles
,以指定路径。 - 设置回退授权策略。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// wwwroot css, JavaScript, and images don't require authentication.
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
}
// Remaining code ommitted for brevity.
在前面的代码中,回退授权策略要求所有用户进行身份验证。 用于指定其自己的授权要求的终结点(如控制器、Razor Pages 等)不使用回退授权策略。 例如,具有 [AllowAnonymous]
或 [Authorize(PolicyName="MyPolicy")]
的 Razor Pages、控制器或操作方法使用应用的授权属性,而不是回退授权策略。
RequireAuthenticatedUser 将 DenyAnonymousAuthorizationRequirement 添加到当前实例,这将强制对当前用户进行身份验证。
wwwroot
下的静态资产是可公开访问的,因为在 UseAuthentication
之前会调用默认静态文件中间件 (app.UseStaticFiles();
)。 MyStaticFiles 文件夹中的静态资产需要身份验证。 示例代码对此进行了演示。
还有一种根据授权提供文件的方法是:
- 将文件存储在
wwwroot
和静态文件中间件可访问的任何目录之外。 - 通过应用授权的操作方法为其提供服务,并返回 FileResult 对象:
[Authorize]
public IActionResult BannerImage()
{
var filePath = Path.Combine(
_env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg");
return PhysicalFile(filePath, "image/jpeg");
}
目录浏览
目录浏览允许在指定目录中列出目录。
出于安全考虑,目录浏览默认处于禁用状态。 有关详细信息,请参阅静态文件的安全注意事项。
通过以下方式启用目录浏览:
Startup.ConfigureServices
中的 AddDirectoryBrowser。Startup.Configure
中的 UseDirectoryBrowser。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDirectoryBrowser();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// using Microsoft.Extensions.FileProviders;
// using System.IO;
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.WebRootPath, "images")),
RequestPath = "/MyImages"
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.WebRootPath, "images")),
RequestPath = "/MyImages"
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
上述代码允许使用 URL https://<hostname>/MyImages
浏览 wwwroot/images 文件夹的目录,并链接到每个文件和文件夹:
提供默认文档
设置默认页面为访问者提供网站的起点。 若要从 wwwroot
提供默认文件,而不要求请求 URL 包含文件名,请调用 UseDefaultFiles 方法:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
要提供默认文件,必须在 UseStaticFiles
前调用 UseDefaultFiles
。 UseDefaultFiles
是不提供文件的 URL 重写工具。
使用 UseDefaultFiles
请求对 wwwroot
中的文件夹搜索:
default.htm
default.html
index.htm
index.html
如同请求包含了文件名一样,提供从列表中找到的第一个文件。 浏览器 URL 继续反映请求的 URI。
以下代码将默认文件名更改为 mydefault.html
:
var options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
以下代码通过上述代码演示了 Startup.Configure
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
var options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
默认文档的 UseFileServer
UseFileServer 结合了 UseStaticFiles
、UseDefaultFiles
和 UseDirectoryBrowser
(可选)的功能。
调用 app.UseFileServer
以提供静态文件和默认文件。 未启用目录浏览。 以下代码通过 UseFileServer
演示了 Startup.Configure
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseFileServer();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
以下代码提供静态文件、默认文件和目录浏览:
app.UseFileServer(enableDirectoryBrowsing: true);
以下代码通过上述代码演示了 Startup.Configure
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseFileServer(enableDirectoryBrowsing: true);
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
考虑以下目录层次结构:
wwwroot
css
images
js
MyStaticFiles
images
MyImage.jpg
default.html
以下代码提供静态文件、默认文件和 MyStaticFiles
的目录浏览:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDirectoryBrowser();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles(); // For the wwwroot folder.
// using Microsoft.Extensions.FileProviders;
// using System.IO;
app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
必须在 EnableDirectoryBrowsing
属性值为 true
时调用 AddDirectoryBrowser。
使用文件层次结构和前面的代码,URL 解析如下:
URI | 响应 |
---|---|
https://<hostname>/StaticFiles/images/MyImage.jpg |
MyStaticFiles/images/MyImage.jpg |
https://<hostname>/StaticFiles |
MyStaticFiles/default.html |
如果 MyStaticFiles 目录中不存在默认命名文件,则 https://<hostname>/StaticFiles
返回包含可单击链接的目录列表:
UseDefaultFiles 和 UseDirectoryBrowser 执行从不带尾部 /
的目标 URI 到带尾部 /
的目标 URI 的客户端重定向。 例如,从 https://<hostname>/StaticFiles
到 https://<hostname>/StaticFiles/
。 如果没有尾部斜杠 (/
),StaticFiles 目录中的相对 URL 无效。
FileExtensionContentTypeProvider
FileExtensionContentTypeProvider 类包含 Mappings
属性,用作文件扩展名到 MIME 内容类型的映射。 在以下示例中,多个文件扩展名映射到了已知的 MIME 类型。 替换了 .rtf 扩展名,删除了 .mp4 :
// 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 = "/MyImages",
ContentTypeProvider = provider
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.WebRootPath, "images")),
RequestPath = "/MyImages"
});
以下代码通过上述代码演示了 Startup.Configure
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// 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 = "/MyImages",
ContentTypeProvider = provider
});
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(env.WebRootPath, "images")),
RequestPath = "/MyImages"
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
请参阅 MIME 内容类型。
非标准内容类型
静态文件中间件可理解近 400 种已知文件内容类型。 如果用户请求文件类型未知的文件,则静态文件中间件将请求传递给管道中的下一个中间件。 如果没有中间件处理请求,则返回“404 未找到”响应。 如果启用了目录浏览,则在目录列表中会显示该文件的链接。
以下代码提供未知类型,并以图像形式呈现未知文件:
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/png"
});
以下代码通过上述代码演示了 Startup.Configure
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/png"
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
使用前面的代码,请求的文件含未知内容类型时,以图像形式返回请求。
警告
启用 ServeUnknownFileTypes 会形成安全隐患。 它默认处于禁用状态,不建议使用。 FileExtensionContentTypeProvider 提供了更安全的替代方法来提供含非标准扩展名的文件。
从多个位置提供文件
UseStaticFiles
和 UseFileServer
默认为指向 wwwroot
的文件提供程序。 可使用其他文件提供程序提供 UseStaticFiles
和 UseFileServer
的其他实例,从多个位置提供文件。 有关详细信息,请参阅此 GitHub 问题。
静态文件的安全注意事项
警告
UseDirectoryBrowser
和 UseStaticFiles
可能会泄漏机密。 强烈建议在生产中禁用目录浏览。 请仔细查看 UseStaticFiles
或 UseDirectoryBrowser
启用了哪些目录。 整个目录及其子目录均可公开访问。 将适合公开的文件存储在专用目录中,如 <content_root>/wwwroot
。 将这些文件与 MVC 视图、Razor Pages 和配置文件等分开。
使用
UseDirectoryBrowser
和UseStaticFiles
公开的内容的 URL 受大小写和基础文件系统字符限制的影响。 例如,Windows 不区分大小写,但 macOS 和 Linux 区分大小写。托管于 IIS 中的 ASP.NET Core 应用使用 ASP.NET Core 模块将所有请求转发到应用,包括静态文件请求。 不使用 IIS 静态文件处理程序,并且没有机会处理请求。
在 IIS Manager 中完成以下步骤,删除服务器或网站级别的 IIS 静态文件处理程序:
- 转到“模块”功能。
- 在列表中选择 StaticFileModule。
- 单击“操作”侧栏中的“删除” 。
警告
如果启用了 IIS 静态文件处理程序且 ASP.NET Core 模块配置不正确,则会提供静态文件。 例如,如果未部署 web.config 文件,则会发生这种情况。
- 将代码文件(包括
.cs
和.cshtml
)放在应用项目的 Web 根目录之外。 这样就在应用的客户端内容和基于服务器的代码间创建了逻辑分隔。 可以防止服务器端代码泄漏。