CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
理解ASP.NET Core - 文件服务器(File Server)
0
.NetCore
小笨蛋
发布于:2022年11月01日
更新于:2022年11月01日
145
#custom-toc-container
### 提供静态文件 静态文件默认存放在 Web根目录(Web Root) 中,路径为 项目根目录(Content Root) 下的wwwroot文件夹,也就是{Content Root}/wwwroot。 如果你调用了Host.CreateDefaultBuilder方法,那么在该方法中,会通过UseContentRoot方法,将程序当前工作目录(Directory.GetCurrentDirectory())设置为项目根目录。 ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup
(); }); ``` 当然,你也可以通过UseWebRoot扩展方法将默认的路径{Content Root}/wwwroot修改为自定义目录(不过,你改它干啥捏?) ```csharp public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { // 配置静态资源的根目录为 mywwwroot, 默认为 wwwroot webBuilder.UseWebRoot("mywwwroot"); webBuilder.UseStartup
(); }); ``` > 为了方便,后面均使用 wwwroot 来表示Web根目录 首先,我们先在 wwwroot 文件夹下创建一个名为 config.json 的文件,内容随便填写 > 注意,确保 wwwroot 下的文件的属性为“如果较新则复制”或“始终复制”。 接着,我们通过UseStaticFiles扩展方法,来注册静态文件中间件StaticFileMiddleware: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseStaticFiles(); } ``` 现在,尝试一下通过 http://localhost:5000/config.json 来获取 wwwroot/config.json 的文件内容吧 > 如果你的项目中启用SwaggerUI,那么你会发现,即使你没有手动通过调用UseStaticFiles()添加中间件,你也可以访问 wwwroot 文件下的文件,这是因为 SwaggerUIMiddleware 中使用了 StaticFileMiddleware ### 提供Web根目录之外的文件 上面我们已经能够提供 wwwroot 文件夹内的静态文件了,那如果我们的文件不在 wwwroot 文件夹内,那如何提供呢? 很简单,我们可以针对StaticFileMiddleware中间件进行一些额外的配置,了解一下配置项: ```csharp public abstract class SharedOptionsBase { // 用于自定义静态文件的相对请求路径 public PathString RequestPath { get; set; } // 文件提供程序 public IFileProvider FileProvider { get; set; } // 是否补全路径末尾斜杠“/”,并重定向 public bool RedirectToAppendTrailingSlash { get; set; } } public class StaticFileOptions : SharedOptionsBase { // ContentType提供程序 public IContentTypeProvider ContentTypeProvider { get; set; } // 如果 ContentTypeProvider 无法识别文件类型,是否仍作为默认文件类型提供 public bool ServeUnknownFileTypes { get; set; } // 当 ServeUnknownFileTypes = true 时,若出现无法识别的文件类型,则将该属性的值作为此文件的类型 // 当 ServeUnknownFileTypes = true 时,必须赋值该属性,才会生效 public string DefaultContentType { get; set; } // 当注册了HTTP响应压缩中间件时,是否对文件进行压缩 public HttpsCompressionMode HttpsCompression { get; set; } = HttpsCompressionMode.Compress; // 在HTTP响应的 Status Code 和 Headers 设置完毕之后,Body 写入之前进行调用 // 用于添加或更改 Headers public Action
OnPrepareResponse { get; set; } } ``` 假设我们现在有这样一个文件目录结构: - wwwroot - config.json - files - file.json 然后,除了用于提供 wwwroot 静态文件的中间件外,我们还要注册一个用于提供 files 静态文件的中间件: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 提供 wwwroot 静态文件 app.UseStaticFiles(); // 提供 files 静态文件 app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files")), // 指定文件的访问路径,允许与 FileProvider 中的文件夹不同名 // 如果不指定,则可通过 http://localhost:5000/file.json 获取, // 如果指定,则需要通过 http://localhost:5000/files/file.json 获取 RequestPath = "/files", OnPrepareResponse = ctx => { // 配置前端缓存 600s(为了后续示例的良好运行,建议先不要配置该Header) ctx.Context.Response.Headers.Add(HeaderNames.CacheControl, "public,max-age=600"); } }); } ``` > 建议将公开访问的文件放置到 wwwroot 目录下,而将需要授权访问的文件放置到其他目录下(在调用UseAuthorization之后调用UseStaticFiles并指定文件目录) ### 提供目录浏览 上面,我们可以通过Url访问某一个文件的内容,而通过UseDirectoryBrowser,注册DirectoryBrowserMiddleware中间件,可以让我们在浏览器中以目录的形式来访问文件列表。 另外,DirectoryBrowserMiddleware中间件的可配置项除了SharedOptionsBase中的之外,还有一个Formatter,用于自定义目录视图。 ```csharp public class DirectoryBrowserOptions : SharedOptionsBase { public IDirectoryFormatter Formatter { get; set; } } ``` 示例如下: ```csharp public void ConfigureServices(IServiceCollection services) { services.AddDirectoryBrowser(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 通过 http://localhost:5000,即可访问 wwwroot 目录 app.UseDirectoryBrowser(); // 通过 http://localhost:5000/files,即可访问 files 目录 app.UseDirectoryBrowser(new DirectoryBrowserOptions { // 如果指定了没有在 UseStaticFiles 中提供的文件目录,虽然可以浏览文件列表,但是无法访问文件内容 FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files")), // 这里一定要和 StaticFileOptions 中的 RequestPath 一致,否则会无法访问文件 RequestPath = "/files" }); } ``` ### 提供默认页 通过UseDefaultFiles,注册DefaultFilesMiddleware中间件,允许在访问静态文件、但未提供文件名的情况下(即传入的是一个目录的路径),提供默认页的展示。 > 注意:UseDefaultFiles必须在UseStaticFiles之前进行调用。因为DefaultFilesMiddleware仅仅负责重写Url,实际上默认页文件,仍然是通过StaticFilesMiddleware来提供的。 默认情况下,该中间件会按照顺序搜索文件目录下的HTML页面文件: - default.htm - default.html - index.htm - index.html 另外,DefaultFilesMiddleware中间件的可配置项除了SharedOptionsBase中的之外,还有一个DefaultFileNames,是个列表,用于自定义默认页的文件名,里面的默认值就是上面提到的4个文件名。 ```csharp public class DefaultFilesOptions : SharedOptionsBase { public IList
DefaultFileNames { get; set; } } ``` 示例如下: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 会去 wwwroot 寻找 default.htm 、default.html 、index.htm 或 index.html 文件作为默认页 app.UseDefaultFiles(); // 设置 files 目录的默认页 var defaultFilesOptions = new DefaultFilesOptions(); defaultFilesOptions.DefaultFileNames.Clear(); // 指定默认页名称 defaultFilesOptions.DefaultFileNames.Add("index1.html"); // 指定请求路径 defaultFilesOptions.RequestPath = "/files"; // 指定默认页所在的目录 defaultFilesOptions.FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files")); app.UseDefaultFiles(defaultFilesOptions); } ``` ### UseFileServer UseFileServer集成了UseStaticFiles、UseDefaultFiles和UseDirectoryBrowser的功能,用起来方便一些,也是我们项目中使用的首选扩展方法。 先看一下FileServerOptions: ```csharp public class FileServerOptions : SharedOptionsBase { public FileServerOptions() : base(new SharedOptions()) { StaticFileOptions = new StaticFileOptions(SharedOptions); DirectoryBrowserOptions = new DirectoryBrowserOptions(SharedOptions); DefaultFilesOptions = new DefaultFilesOptions(SharedOptions); EnableDefaultFiles = true; } public StaticFileOptions StaticFileOptions { get; private set; } public DirectoryBrowserOptions DirectoryBrowserOptions { get; private set; } public DefaultFilesOptions DefaultFilesOptions { get; private set; } // 默认禁用目录浏览 public bool EnableDirectoryBrowsing { get; set; } // 默认启用默认页(在构造函数中初始化的) public bool EnableDefaultFiles { get; set; } } ``` 可以看到,FileServerOptions包含了StaticFileOptions、DirectoryBrowserOptions和DefaultFilesOptions三个选项,可以针对StaticFileMiddleware、DirectoryBrowserMiddleware和DefaultFilesMiddleware进行自定义配置。另外,其默认启用了静态文件和默认页,禁用了目录浏览。 下面举个例子熟悉一下: 假设文件目录: - files - images - 1.jpg - file.json - myindex.html ```csharp public void ConfigureServices(IServiceCollection services) { // 如果将 EnableDirectoryBrowsing 设为 true,记得注册服务 services.AddDirectoryBrowser(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 启用 StaticFileMiddleware // 启用 DefaultFilesMiddleware // 禁用 DirectoryBrowserMiddleware // 默认指向 wwwroot app.UseFileServer(); // 针对 files 文件夹配置 var fileServerOptions = new FileServerOptions { FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "files")), RequestPath = "/files", EnableDirectoryBrowsing = true }; fileServerOptions.StaticFileOptions.OnPrepareResponse = ctx => { // 配置缓存600s ctx.Context.Response.Headers.Add(HeaderNames.CacheControl, "public,max-age=600"); }; fileServerOptions.DefaultFilesOptions.DefaultFileNames.Clear(); fileServerOptions.DefaultFilesOptions.DefaultFileNames.Add("myindex.html"); app.UseFileServer(fileServerOptions); } ``` 当访问 http://localhost:5000/files 时,由于在DefaultFilesOptions.DefaultFileNames中添加了文件名myindex.html,所以可以找到默认页,此时会显示默认页的内容。 假如我们没有在DefaultFilesOptions.DefaultFileNames中添加文件名myindex.html,那么便找不到默认页,但由于启用了DirectoryBrowsing,所以此时会展示文件列表。 ### 核心配置项 #### FileProvider 上面我们已经见过PhysicalFileProvider了,它仅仅是众多文件提供程序中的一种。所有的文件提供程序均实现了IFileProvider接口: ```csharp public interface IFileProvider { // 获取给定路径的目录信息,可枚举该目录中的所有文件 IDirectoryContents GetDirectoryContents(string subpath); // 获取给定路径的文件信息 IFileInfo GetFileInfo(string subpath); // 创建指定 filter 的 ChangeToken IChangeToken Watch(string filter); } public interface IDirectoryContents : IEnumerable
, IEnumerable { bool Exists { get; } } public interface IFileInfo { bool Exists { get; } bool IsDirectory { get; } DateTimeOffset LastModified { get; } // 字节(bytes)长度 // 如果是目录或文件不存在,则是 -1 long Length { get; } // 目录或文件名,纯文件名,不包括路径 string Name { get; } // 文件路径,包含文件名 // 如果文件无法直接访问,则返回 null string PhysicalPath { get; } // 创建该文件只读流 Stream CreateReadStream(); } ``` 常用的文件提供程序有以下三种: - PhysicalFileProvider - ManifestEmbeddedFileProvider - CompositeFileProvider ##### glob模式 在介绍这三种文件提供程序之前,先说一下glob模式,即通配符模式。两个通配符分别是*和**。 - *:匹配当前目录层级(不包含子目录)下的任何内容、任何文件名或任何文件扩展名,可以通过/、\和.进行分隔。 - **:匹配目录多层级(包含子目录)的任何内容,用于递归匹配多层级目录的多个文件。 ##### PhysicalFileProvider PhysicalFileProvider用于提供物理文件系统的访问。该提供程序需要将文件路径范围限定在一个目录及其子目录中,不能访问目录外部的内容。 当实例化该文件提供程序时,需要提供一个绝对的目录路径,作为文件目录的root。 > PhysicalFileProvider目录或文件路径不支持glob(通配符)模式。 ##### ManifestEmbeddedFileProvider ManifestEmbeddedFileProvider用于提供嵌入在程序集中的文件的访问。 可能你对这个嵌入文件比较陌生,没关系,请按照下面的步骤来: - 安装Nuget包:Install-Package Microsoft.Extensions.FileProviders.Embedded - 编辑.csproj文件: - 添加
,并设置为true - 使用
添加要嵌入的文件 以下是 .csproj 文件的示例: ```csharp
net5.0
true
``` 现在我们通过ManifestEmbeddedFileProvider来提供嵌入到程序集的 files 目录下文件的访问: ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { var fileServerOptions = new FileServerOptions(); fileServerOptions.StaticFileOptions.FileProvider = new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly(), "/files"); fileServerOptions.StaticFileOptions.RequestPath = "/files"; app.UseFileServer(fileServerOptions); } ``` 现在,你可以通过 http://localhost:5000/files/file.json 来访问文件了。 ##### CompositeFileProvider CompositeFileProvider用于将多种文件提供程序进行集成。 ```csharp public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { var fileServerOptions = new FileServerOptions(); var fileProvider = new CompositeFileProvider( env.WebRootFileProvider, new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly(), "/files") ); fileServerOptions.StaticFileOptions.FileProvider = fileProvider; fileServerOptions.StaticFileOptions.RequestPath = "/composite"; app.UseFileServer(fileServerOptions); } ``` 现在,你可以通过 http://localhost:5000/composite/file.json 来访问文件了。 #### ContentTypeProvider Http请求头中的Content-Type大家一定很熟悉,ContentTypeProvider就是用来提供文件扩展名和MIME类型映射关系的。 若我们没有显示指定ContentTypeProvider,则框架默认使用FileExtensionContentTypeProvider,其实现了接口IContentTypeProvider: ```csharp public interface IContentTypeProvider { // 尝试根据文件路径,获取对应的 MIME 类型 bool TryGetContentType(string subpath, out string contentType); } public class FileExtensionContentTypeProvider : IContentTypeProvider { public FileExtensionContentTypeProvider() : this(new Dictionary
(StringComparer.OrdinalIgnoreCase) { // ...此处省略一万字 } { } public FileExtensionContentTypeProvider(IDictionary
mapping) { Mappings = mapping; } public IDictionary
Mappings { get; private set; } public bool TryGetContentType(string subpath, out string contentType) { string extension = GetExtension(subpath); if (extension == null) { contentType = null; return false; } return Mappings.TryGetValue(extension, out contentType); } private static string GetExtension(string path) { // 没有使用 Path.GetExtension() 的原因是:当路径中存在无效字符时,其会抛出异常,而这里不应抛出异常。 if (string.IsNullOrWhiteSpace(path)) { return null; } int index = path.LastIndexOf('.'); if (index < 0) { return null; } return path.Substring(index); } } ``` 在FileExtensionContentTypeProvider的无参构造函数中,默认添加了380种已知的文件扩展名和MIME类型的映射,存放在Mappings属性中。你也可以添加自定义的映射,或移除不想要的映射。 ### 核心中间件 #### StaticFileMiddleware 通过UseStaticFiles扩展方法,可以方便的注册StaticFileMiddleware中间件: ```csharp public static class StaticFileExtensions { public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app) { return app.UseMiddleware
(); } public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, string requestPath) { return app.UseStaticFiles(new StaticFileOptions { RequestPath = new PathString(requestPath) }); } public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options) { return app.UseMiddleware
(Options.Create(options)); } } ``` 紧接着查看StaticFileMiddleware的Invoke方法: ```csharp public class StaticFileMiddleware { private readonly StaticFileOptions _options; private readonly PathString _matchUrl; private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IFileProvider _fileProvider; private readonly IContentTypeProvider _contentTypeProvider; public StaticFileMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions
options, ILoggerFactory loggerFactory) { _next = next; _options = options.Value; // 若未指定 ContentTypeProvider,则默认使用 FileExtensionContentTypeProvider _contentTypeProvider = _options.ContentTypeProvider ?? new FileExtensionContentTypeProvider(); // 若未指定 FileProvider,则默认使用 hostingEnv.WebRootFileProvider _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv); _matchUrl = _options.RequestPath; _logger = loggerFactory.CreateLogger
(); } public Task Invoke(HttpContext context) { // 若已匹配到 Endpoint,则跳过 if (!ValidateNoEndpoint(context)) { _logger.EndpointMatched(); } // 若HTTP请求方法不是 Get,也不是 Head,则跳过 else if (!ValidateMethod(context)) { _logger.RequestMethodNotSupported(context.Request.Method); } // 如果请求路径不匹配,则跳过 else if (!ValidatePath(context, _matchUrl, out var subPath)) { _logger.PathMismatch(subPath); } // 如果 ContentType 不受支持,则跳过 else if (!LookupContentType(_contentTypeProvider, _options, subPath, out var contentType)) { _logger.FileTypeNotSupported(subPath); } else { // 尝试提供静态文件 return TryServeStaticFile(context, contentType, subPath); } return _next(context); } private static bool ValidateNoEndpoint(HttpContext context) => context.GetEndpoint() == null; private static bool ValidateMethod(HttpContext context) => Helpers.IsGetOrHeadMethod(context.Request.Method); internal static bool ValidatePath(HttpContext context, PathString matchUrl, out PathString subPath) => Helpers.TryMatchPath(context, matchUrl, forDirectory: false, out subPath); internal static bool LookupContentType(IContentTypeProvider contentTypeProvider, StaticFileOptions options, PathString subPath, out string contentType) { // 查看 Provider 中是否支持该 ContentType if (contentTypeProvider.TryGetContentType(subPath.Value, out contentType)) { return true; } // 如果提供未知文件类型,则将其设置为默认 ContentType if (options.ServeUnknownFileTypes) { contentType = options.DefaultContentType; return true; } return false; } private Task TryServeStaticFile(HttpContext context, string contentType, PathString subPath) { var fileContext = new StaticFileContext(context, _options, _logger, _fileProvider, contentType, subPath); // 如果文件不存在,则跳过 if (!fileContext.LookupFileInfo()) { _logger.FileNotFound(fileContext.SubPath); } else { // 若文件存在,则提供该静态文件 return fileContext.ServeStaticFile(context, _next); } return _next(context); } } ``` #### DirectoryBrowserMiddleware 通过UseDirectoryBrowser扩展方法,可以方便的注册DirectoryBrowserMiddleware中间件: ```csharp public static class DirectoryBrowserExtensions { public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app) { return app.UseMiddleware
(); } public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, string requestPath) { return app.UseDirectoryBrowser(new DirectoryBrowserOptions { RequestPath = new PathString(requestPath) }); } public static IApplicationBuilder UseDirectoryBrowser(this IApplicationBuilder app, DirectoryBrowserOptions options) { return app.UseMiddleware
(Options.Create(options)); } } ``` 紧接着查看DirectoryBrowserMiddleware的Invoke方法: ```csharp public class DirectoryBrowserMiddleware { private readonly DirectoryBrowserOptions _options; private readonly PathString _matchUrl; private readonly RequestDelegate _next; private readonly IDirectoryFormatter _formatter; private readonly IFileProvider _fileProvider; public DirectoryBrowserMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions
options) : this(next, hostingEnv, HtmlEncoder.Default, options) { } public DirectoryBrowserMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, HtmlEncoder encoder, IOptions
options) { _next = next; _options = options.Value; // 若未指定 FileProvider,则默认使用 hostingEnv.WebRootFileProvider _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv); _formatter = _options.Formatter ?? new HtmlDirectoryFormatter(encoder); _matchUrl = _options.RequestPath; } public Task Invoke(HttpContext context) { // 若已匹配到 Endpoint,则跳过 // 若HTTP请求方法不是 Get,也不是 Head,则跳过 // 如果请求路径不匹配,则跳过 // 若文件目录不存在,则跳过 if (context.GetEndpoint() == null && Helpers.IsGetOrHeadMethod(context.Request.Method) && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath) && TryGetDirectoryInfo(subpath, out var contents)) { if (_options.RedirectToAppendTrailingSlash && !Helpers.PathEndsInSlash(context.Request.Path)) { Helpers.RedirectToPathWithSlash(context); return Task.CompletedTask; } // 生成文件浏览视图 return _formatter.GenerateContentAsync(context, contents); } return _next(context); } private bool TryGetDirectoryInfo(PathString subpath, out IDirectoryContents contents) { contents = _fileProvider.GetDirectoryContents(subpath.Value); return contents.Exists; } } ``` #### DefaultFilesMiddleware 通过UseDefaultFiles扩展方法,可以方便的注册DefaultFilesMiddleware中间件: ```csharp public static class DefaultFilesExtensions { public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app) { return app.UseMiddleware
(); } public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, string requestPath) { return app.UseDefaultFiles(new DefaultFilesOptions { RequestPath = new PathString(requestPath) }); } public static IApplicationBuilder UseDefaultFiles(this IApplicationBuilder app, DefaultFilesOptions options) { return app.UseMiddleware
(Options.Create(options)); } } ``` 紧接着查看DefaultFilesMiddleware的Invoke方法: ```csharp public class DefaultFilesMiddleware { private readonly DefaultFilesOptions _options; private readonly PathString _matchUrl; private readonly RequestDelegate _next; private readonly IFileProvider _fileProvider; public DefaultFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions
options) { _next = next; _options = options.Value; // 若未指定 FileProvider,则默认使用 hostingEnv.WebRootFileProvider _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv); _matchUrl = _options.RequestPath; } public Task Invoke(HttpContext context) { // 若已匹配到 Endpoint,则跳过 // 若HTTP请求方法不是 Get,也不是 Head,则跳过 // 如果请求路径不匹配,则跳过 if (context.GetEndpoint() == null && Helpers.IsGetOrHeadMethod(context.Request.Method) && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath)) { var dirContents = _fileProvider.GetDirectoryContents(subpath.Value); if (dirContents.Exists) { for (int matchIndex = 0; matchIndex < _options.DefaultFileNames.Count; matchIndex++) { string defaultFile = _options.DefaultFileNames[matchIndex]; var file = _fileProvider.GetFileInfo(subpath.Value + defaultFile); // 找到了默认页 if (file.Exists) { if (_options.RedirectToAppendTrailingSlash && !Helpers.PathEndsInSlash(context.Request.Path)) { Helpers.RedirectToPathWithSlash(context); return Task.CompletedTask; } // 重写为默认页的Url,后续通过 StaticFileMiddleware 提供该页面 context.Request.Path = new PathString(Helpers.GetPathValueWithSlash(context.Request.Path) + defaultFile); break; } } } } return _next(context); } } ``` #### FileServer FileServer并不是某个具体的中间件,它的实现还是依赖了StaticFileMiddleware、DirectoryBrowserMiddleware和DefaultFilesMiddleware这3个中间件。不过,我们可以看一下UseFileServer里的逻辑: ```csharp public static class FileServerExtensions { public static IApplicationBuilder UseFileServer(this IApplicationBuilder app) { return app.UseFileServer(new FileServerOptions()); } public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, bool enableDirectoryBrowsing) { return app.UseFileServer(new FileServerOptions { EnableDirectoryBrowsing = enableDirectoryBrowsing }); } public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, string requestPath) { return app.UseFileServer(new FileServerOptions { RequestPath = new PathString(requestPath) }); } public static IApplicationBuilder UseFileServer(this IApplicationBuilder app, FileServerOptions options) { // 启用默认页 if (options.EnableDefaultFiles) { app.UseDefaultFiles(options.DefaultFilesOptions); } // 启用目录浏览 if (options.EnableDirectoryBrowsing) { app.UseDirectoryBrowser(options.DirectoryBrowserOptions); } return app.UseStaticFiles(options.StaticFileOptions); } } ``` ### FileProvider in IWebHostingEnvironment 在接口IHostingEnvironment中,包含ContentRootFileProvider和WebRootFileProvider两个文件提供程序。下面我们就看一下他们是如何被初始化的。 ```csharp internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider { private WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext context) { if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal)) { var options = new WebHostOptions(context.Configuration, Assembly.GetEntryAssembly()?.GetName().Name); var webHostBuilderContext = new WebHostBuilderContext { Configuration = context.Configuration, HostingEnvironment = new HostingEnvironment(), }; // 重点在这里,看这个 Initialize 方法 webHostBuilderContext.HostingEnvironment.Initialize(context.HostingEnvironment.ContentRootPath, options); context.Properties[typeof(WebHostBuilderContext)] = webHostBuilderContext; context.Properties[typeof(WebHostOptions)] = options; return webHostBuilderContext; } var webHostContext = (WebHostBuilderContext)contextVal; webHostContext.Configuration = context.Configuration; return webHostContext; } } internal static class HostingEnvironmentExtensions { internal static void Initialize(this IWebHostEnvironment hostingEnvironment, string contentRootPath, WebHostOptions options) { hostingEnvironment.ApplicationName = options.ApplicationName; hostingEnvironment.ContentRootPath = contentRootPath; // 初始化 ContentRootFileProvider hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(hostingEnvironment.ContentRootPath); var webRoot = options.WebRoot; if (webRoot == null) { // 如果 /wwwroot 目录存在,则设置为Web根目录 var wwwroot = Path.Combine(hostingEnvironment.ContentRootPath, "wwwroot"); if (Directory.Exists(wwwroot)) { hostingEnvironment.WebRootPath = wwwroot; } } else { hostingEnvironment.WebRootPath = Path.Combine(hostingEnvironment.ContentRootPath, webRoot); } if (!string.IsNullOrEmpty(hostingEnvironment.WebRootPath)) { hostingEnvironment.WebRootPath = Path.GetFullPath(hostingEnvironment.WebRootPath); if (!Directory.Exists(hostingEnvironment.WebRootPath)) { Directory.CreateDirectory(hostingEnvironment.WebRootPath); } // 初始化 WebRootFileProvider hostingEnvironment.WebRootFileProvider = new PhysicalFileProvider(hostingEnvironment.WebRootPath); } else { hostingEnvironment.WebRootFileProvider = new NullFileProvider(); } hostingEnvironment.EnvironmentName = options.Environment ?? hostingEnvironment.EnvironmentName; } } ``` ### 注意 - 使用UseDirectoryBrowser和UseStaticFiles提供文件浏览和访问时,URL 受大小写和基础文件系统字符的限制。例如,Windows 不区分大小写,但 macOS 和 Linux 区分大小写。 - 如果使用 IIS 托管应用,那么 IIS 自带的静态文件处理器是不工作的,均是使用 ASP.NET Core Module 进行处理的,包括静态文件处理。 ### 小结 - 使用UseFileServer扩展方法提供文件浏览和访问,其集成了UseStaticFiles、UseDirectoryBrowser和UseDefaultFiles三个中间件的功能。 - UseStaticFiles:注册StaticFilesMiddleware,提供文件访问 - UseDirectoryBrowser:注册DirectoryBrowserMiddleware,提供文件目录浏览 - UseDefaultFiles:注册DefaultFilesMiddleware,当Url未指定访问的文件名时,提供默认页。 - 文件提供程序均实现了接口IFileProvider,常用的文件提供程序有以下三种: - PhysicalFileProvider:提供物理文件系统的访问 - ManifestEmbeddedFileProvider:提供嵌入在程序集中的文件的访问 - CompositeFileProvider:用于将多种文件提供程序进行集成。 - 可通过IWebHostingEnvironment获取ContentRootFileProvider(默认目录为项目根目录)和WebRootFileProvider(默认目录为Web根目录)。
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024