翻译自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-5.0
静态文件,例如 HTML, CSS,images 和 JavaScript,都是作为资源文件由 ASP.NET Core 应用程序默认的直接提供给客户端。
静态文件存储在项目的 web root 目录。默认目录是 {contentroot}/wwwroot,但是可以使用 UseWebRoot 方法更改。更多信息,查看 Content root 和 Web root。
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 root 的路径获取。例如,Web 应用程序项目模板包含几个文件夹在 wwwroot 中:
wwwroot:
考虑床架你 wwwroot/images 文件夹,添加 wwwroot/images/MyImage.jpg 文件到该目录。访问一个 Images 文件夹中的文件的 URI 格式是 https://<hostname>/images/<image_file_name>。例如,https://localhost:5001/images/MyImage.jpg。
默认的 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 root 中的文件可以被服务。下面的标记引用了 wwwroot/images/MyImage.jpg:
<img src="~/images/MyImage.jpg" class="img" alt="My image" />
在上面的代码中,波浪 ~/ 指向 web root。
考虑一个目录层级,在这个目录中静态文件被服务于 web root 之外:
wwwroot:
MyStaticsFiles:
通过以下配置静态文件中间件,一个请求可以访问 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" />
一个 StaticFileOptions 对象可用于设置 HTTP 相应头部。除了设置从 web root 服务静态文件外,下面的代码也设置了 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(); }); }
静态文件公开缓存的时间是 600 秒:
ASP.NET Core 模板在调用 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.
在上面的代码中,fallback authorization policy 要求所有的用户都要认证。像 controller, Razore Pages 等等指定了它们自己授权要求的 Endpoints 不会使用 fallback authorization 策略。例如, Razor Pages, controllers 或者使用 [AllowAnonymous] 或 [Authorize(PolicyNAme="MyPolicy")] 会使用属性授权而不是 fallback authorization 策略。
RequireAuthenticatedUser 添加 DenyAnonymousAuthorizationRequirement 到当前实例,这会强制当前用户被认证。
在 wwwroot 下面的静态资源文件是公开可以访问的,因为默认的静态文件中间件 (app.UseStaticFiles();) 在 UseAuthentication 之前调用。MyStaticFiles 中的静态资源要求认证。 sample code 展示了这些。
一种替代的基于授权的方法服务文件是:
[Authorize] public IActionResult BannerImage() { var filePath = Path.Combine( _env.ContentRootPath, "MyStaticFiles", "images", "red-rose.jpg"); return PhysicalFile(filePath, "image/jpeg"); }
目录浏览允许目录列出指定目录的文件。
默认的由于安全的原因,目录浏览是关闭的。更多信息查看 Considerations。
使用以下方法打开目录浏览:
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(); }); }
上面的代码允许浏览目录 wwwroot/images 使用 URL https://<hostname>/MyImages,使用链接访问每一个文件和目录:
为访客在站点开始的地方提供一个默认的页面。从 wwwroot 提供一个默认的页面而无需一个完整的 URI,调用 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(); }); }
UseDefaultFiles 必须在 UseStaticFiles 之前调用。UseDefaultFiles 是一个 URL 的重写,它并不服务文件。
使用 UseDefaultFiles,请求 wwwroot 的一个目录的话,会搜索以下文件:
从列表中找到的第一个文件被作为服务的文件,就好像请求的是完整的指定的 URI。浏览器的 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 结合了 UseStaticFiles,UseDefaultFiles 和可选的 UseDirectoryBrowder 的功能。
调用 app.UseFileServer 使能了服务静态文件和默认文件。目录浏览并没有打开。下面的代码展示了在 Startup.Configure 中使用 UseFileServer:
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 必须被调用。
使用文件层级和上面的代码, URLs 解析如下:
URI | Response |
https://<hostname>/StaticFiles/images/MyImage.jpg | MyStaticFiles/images/MyImage.jpg |
https://<hostname>/StaticFiles | MuStaticFiles/default.html |
如果没有 default-named 的文件存在于 MyStaticFiles 目录,https://<hostname>/StaticFiles 返回可点击的目录列表:
UseDefaultFiles 和 UseDirectoryBrowser 执行了从没有尾部 / 的目标 URI 到带有尾部 / 的目标 URI。例如,从 https://<hostname>/StaticFiles 到 https://<hostname>/StaticFiles/。没有尾部斜杠(/) 的 StaticFiles 目录的相对的 URLs 是无效的。
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.Confiure 中使用上面的代码:
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 content types。
静态文件中间件能够识别将近 400 个已知的文件内容类型。如果用户请求的一个未知的文件类型,静态文件中间件会传递请求到管道中的下一个中间件。如果没有中间件处理请求,会返回一个 404 Not Found 响应。如果目录浏览打开了,文件的链接会显示到一个目录列表中。
下面的代码使能了服务未知文件类型,并且渲染未知文件为一个图片:
app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = true, DefaultContentType = "image/png" });
下面的代码展示了在 Startup.Cofigure 中使用前面的代码:
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 提供一个安全的可替代的方法使用 non-standard 扩展服务文件。
UseStaticFiles 和 UseFileServer 默认的文件提供器都指向 wwwroot。另外,UseStaticFiles 和 UseFileServer 的实例可以使用其它文件提供器从其它位置服务文件。更多信息,查看 this GitHub issue。
警告:
UseDirectoryBrowser 和 UseStaticFiles 可能会导致泄漏秘密。强烈推荐在生产环境中禁用目录浏览。细心的检查哪些目录通过 UseStaticFiles 或者 UseDirectoryBrowser 开启了。整个目录和它的子目录都变的公开可访问了。公开服务的文件应该存储到一个专用的目录中,例如 <content_root>/wwwroot。把这些文件和 MVC views,Razor Pages,配置文件等等隔离开。
警告:
如果 IIS 静态文件处理程序开启了,并且 ASP.NET Core 模块没有正确配置,静态文件也会被服务。例如,如果 web.config 没有部署的话就会发生这种情况。