默认情况下,静态文件(如 HTML、CSS、图像和 JavaScript)是 ASP.NET Core 应用直接提供给客户端的资产。
提供静态文件
静态文件存储在项目的 Web 根目录中。 默认目录为 {content root}/wwwroot,但可通过 UseWebRoot
方法更改目录。
采用 CreateBuilder
方法可将内容根目录设置为当前目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 文件夹中的多个文件夹:
在 Web 根目录中提供文件
默认 Web 应用模板在 Program.cs 中调用 UseStaticFiles
方法,这将允许提供静态文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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:
1 <img src ="~/images/MyImage.jpg" class ="img" alt ="My image" />
波形符 ~ 指向 Web 根目录。
提供 Web 根目录外的文件
考虑一个目录层次结构,其中要提供的静态文件位于 Web 根目录之外:
按如下方式配置静态文件中间件后,请求可访问 red-rose.jpg 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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:///StaticFiles/images/red-rose.jpg 的请求将提供 red-rose.jpg 文件。
以下标记引用 MyStaticFiles/images/red-rose.jpg:
1 <img src ="~/StaticFiles/images/red-rose.jpg" class ="img" alt ="A red rose" />
设置HTTP响应标头
StaticFileOptions
对象可用于设置 HTTP 响应标头。 除配置从 Web 根目录提供静态文件外,以下代码还设置 标头:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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
,以指定路径。
设置回退授权策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 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 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [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" ); } }
上述方法要求对每个文件使用一个页面或终结点。 以下代码为经过身份验证的用户返回文件或上传文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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) => { 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();
目录浏览
目录浏览允许在指定目录中列出目录。出于安全考虑,目录浏览默认处于禁用状态。
通过 AddDirectoryBrowser
和 UseDirectoryBrowser
启用目录浏览:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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" ;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:///MyImages 浏览 wwwroot/images 文件夹的目录,并链接到每个文件和文件夹:
AddDirectoryBrowser
添加目录浏览中间件所需的服务,包括 HtmlEncoder。 这些服务可以通过其他调用(例如 AddRazorPages)添加,但我们建议调用 AddDirectoryBrowser
以确保将服务添加到所有应用中。
提供默认文档
设置默认页面为访问者提供网站的起点。 若要从 wwwroot 提供默认文件,而不要求请求 URL 包含文件名,请调用 UseDefaultFiles
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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
以提供静态文件和默认文件。 未启用目录浏览 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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();
以下代码提供静态文件、默认文件和目录浏览:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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
MyStaticFiles
images
MyImage.jpg
default.html
以下代码提供静态文件、默认文件和 MyStaticFiles 的目录浏览:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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();
FileExtensionContentTypeProvider
FileExtensionContentTypeProvider
类包含 Mappings 属性,用作文件扩展名到 MIME 内容类型的映射。 在以下示例中,多个文件扩展名映射到了已知的 MIME 类型。 替换了 .rtf 扩展名,删除了 .mp4 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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(); var provider = new FileExtensionContentTypeProvider();provider.Mappings[".myapp" ] = "application/x-msdownload" ; provider.Mappings[".htm3" ] = "text/html" ; provider.Mappings[".image" ] = "image/png" ; provider.Mappings[".rtf" ] = "application/x-msdownload" ; provider.Mappings.Remove(".mp4" ); app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider }); app.UseAuthorization(); app.MapDefaultControllerRoute(); app.MapRazorPages(); app.Run();
非标准内容类型
静态文件中间件可理解近 400 种已知文件内容类型。 如果用户请求文件类型未知的文件,则静态文件中间件将请求传递给管道中的下一个中间件。 如果没有中间件处理请求,则返回“404 未找到”响应。 如果启用了目录浏览,则在目录列表中会显示该文件的链接。
以下代码提供未知类型,并以图像形式呈现未知文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 提供了更安全的替代方法来提供含非标准扩展名的文件。
静态文件的安全注意事项
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。
单击“操作”侧栏中的“删除” 。
将代码文件(包括 .cs 和 .cshtml)放在应用项目的 Web 根目录之外。 这样就在应用的客户端内容和基于服务器的代码间创建了逻辑分隔。 可以防止服务器端代码泄漏。
如果启用了 IIS 静态文件处理程序且 ASP.NET Core 模块配置不正确,则会提供静态文件。 例如,如果未部署 web.config 文件,则会发生这种情况。
IWebHostEnvironment
当 IWebHostEnvironment.WebRootPath
设置为 wwwroot 以外的文件夹时:
在开发环境中,在 wwwroot 和更新的 IWebHostEnvironment.WebRootPath
中发现的静态资产由 wwwroot 提供。
在开发环境以外的任何环境中,重复的静态资产会从更新的 IWebHostEnvironment.WebRootPath
文件夹提供。
请考虑使用空 Web 模板创建的 Web 应用:
在 wwwroot 和 wwwroot-custom 中包含一个 Index.html 文件。
使用以下设置了 WebRootPath = "wwwroot-custom"
的已更新 Program.cs 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 var builder = WebApplication.CreateBuilder(new WebApplicationOptions{ Args = args, 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” 以外的任何值。
通过在项目文件中设置 false ,完全禁用静态 Web 资产。 警告,禁用静态 Web 资产会禁用 Razor 类库。
将以下 JSON 添加到项目文件:
1 2 3 <ItemGroup > <Content Remove ="wwwroot\**" /> </ItemGroup >
以下代码将 IWebHostEnvironment.WebRootPath
更新为非开发值,从而保证从 wwwroot-custom(而不是 wwwroot)返回重复内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var builder = WebApplication.CreateBuilder(new WebApplicationOptions{ Args = args, 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();