理解ASP.NET Core-文件服務器(File Server)
↓推薦關注↓
提供靜態(tài)文件
靜態(tài)文件默認存放在 Web根目錄(Web Root) 中,路徑為 項目根目錄(Content Root) 下的wwwroot文件夾,也就是{Content Root}/wwwroot。
如果你調(diào)用了Host.CreateDefaultBuilder方法,那么在該方法中,會通過UseContentRoot方法,將程序當前工作目錄(Directory.GetCurrentDirectory())設置為項目根目錄。
public?static?IHostBuilder?CreateHostBuilder(string[]?args)?=>
????Host.CreateDefaultBuilder(args)
????????.ConfigureWebHostDefaults(webBuilder?=>
????????{
????????????webBuilder.UseStartup();
????????});
當然,你也可以通過UseWebRoot擴展方法將默認的路徑{Content Root}/wwwroot修改為自定義目錄(不過,你改它干啥捏?)
public?static?IHostBuilder?CreateHostBuilder(string[]?args)?=>
????Host.CreateDefaultBuilder(args)
????????.ConfigureWebHostDefaults(webBuilder?=>
????????{
????????????//?配置靜態(tài)資源的根目錄為?mywwwroot,?默認為?wwwroot
????????????webBuilder.UseWebRoot("mywwwroot");
????????????webBuilder.UseStartup();
????????});
為了方便,后面均使用 wwwroot 來表示W(wǎng)eb根目錄
首先,我們先在 wwwroot 文件夾下創(chuàng)建一個名為 config.json 的文件,內(nèi)容隨便填寫
注意,確保 wwwroot 下的文件的屬性為“如果較新則復制”或“始終復制”。
接著,我們通過UseStaticFiles擴展方法,來注冊靜態(tài)文件中間件StaticFileMiddleware:
public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env)
{
????app.UseStaticFiles();
}
現(xiàn)在,嘗試一下通過 http://localhost:5000/config.json 來獲取 wwwroot/config.json 的文件內(nèi)容吧
如果你的項目中啟用SwaggerUI,那么你會發(fā)現(xiàn),即使你沒有手動通過調(diào)用
UseStaticFiles()添加中間件,你也可以訪問 wwwroot 文件下的文件,這是因為 SwaggerUIMiddleware 中使用了 StaticFileMiddleware
提供Web根目錄之外的文件
上面我們已經(jīng)能夠提供 wwwroot 文件夾內(nèi)的靜態(tài)文件了,那如果我們的文件不在 wwwroot 文件夾內(nèi),那如何提供呢?
很簡單,我們可以針對StaticFileMiddleware中間件進行一些額外的配置,了解一下配置項:
public?abstract?class?SharedOptionsBase
{
????//?用于自定義靜態(tài)文件的相對請求路徑
????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?時,若出現(xiàn)無法識別的文件類型,則將該屬性的值作為此文件的類型
????//?當?ServeUnknownFileTypes?=?true?時,必須賦值該屬性,才會生效
????public?string?DefaultContentType?{?get;?set;?}
????
????//?當注冊了HTTP響應壓縮中間件時,是否對文件進行壓縮
????public?HttpsCompressionMode?HttpsCompression?{?get;?set;?}?=?HttpsCompressionMode.Compress;
????
????//?在HTTP響應的?Status?Code?和?Headers?設置完畢之后,Body?寫入之前進行調(diào)用
????//?用于添加或更改?Headers
????public?Action?OnPrepareResponse?{?get;?set;?}
}
假設我們現(xiàn)在有這樣一個文件目錄結構:
wwwroot
config.json
files
file.json
然后,除了用于提供 wwwroot 靜態(tài)文件的中間件外,我們還要注冊一個用于提供 files 靜態(tài)文件的中間件:
public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env)
{
????//?提供?wwwroot?靜態(tài)文件
????app.UseStaticFiles();
????//?提供?files?靜態(tài)文件
????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(為了后續(xù)示例的良好運行,建議先不要配置該Header)
????????????ctx.Context.Response.Headers.Add(HeaderNames.CacheControl,?"public,max-age=600");
????????}
????});
}
建議將公開訪問的文件放置到 wwwroot 目錄下,而將需要授權訪問的文件放置到其他目錄下(在調(diào)用
UseAuthorization之后調(diào)用UseStaticFiles并指定文件目錄)
提供目錄瀏覽
上面,我們可以通過Url訪問某一個文件的內(nèi)容,而通過UseDirectoryBrowser,注冊DirectoryBrowserMiddleware中間件,可以讓我們在瀏覽器中以目錄的形式來訪問文件列表。
另外,DirectoryBrowserMiddleware中間件的可配置項除了SharedOptionsBase中的之外,還有一個Formatter,用于自定義目錄視圖。
public?class?DirectoryBrowserOptions?:?SharedOptionsBase
{
????public?IDirectoryFormatter?Formatter?{?get;?set;?}
}
示例如下:
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?中提供的文件目錄,雖然可以瀏覽文件列表,但是無法訪問文件內(nèi)容
????????FileProvider?=?new?PhysicalFileProvider(Path.Combine(env.ContentRootPath,?"files")),
????????//?這里一定要和?StaticFileOptions?中的?RequestPath?一致,否則會無法訪問文件
????????RequestPath?=?"/files"
????});
}
提供默認頁
通過UseDefaultFiles,注冊DefaultFilesMiddleware中間件,允許在訪問靜態(tài)文件、但未提供文件名的情況下(即傳入的是一個目錄的路徑),提供默認頁的展示。
注意:
UseDefaultFiles必須在UseStaticFiles之前進行調(diào)用。因為DefaultFilesMiddleware僅僅負責重寫Url,實際上默認頁文件,仍然是通過StaticFilesMiddleware來提供的。
默認情況下,該中間件會按照順序搜索文件目錄下的HTML頁面文件:
default.htm default.html index.htm index.html
另外,DefaultFilesMiddleware中間件的可配置項除了SharedOptionsBase中的之外,還有一個DefaultFileNames,是個列表,用于自定義默認頁的文件名,里面的默認值就是上面提到的4個文件名。
public?class?DefaultFilesOptions?:?SharedOptionsBase
{
????public?IList<string>?DefaultFileNames?{?get;?set;?}
}
示例如下:
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:
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;?}
????//?默認啟用默認頁(在構造函數(shù)中初始化的)
????public?bool?EnableDefaultFiles?{?get;?set;?}
}
可以看到,FileServerOptions包含了StaticFileOptions、DirectoryBrowserOptions和DefaultFilesOptions三個選項,可以針對StaticFileMiddleware、DirectoryBrowserMiddleware和DefaultFilesMiddleware進行自定義配置。另外,其默認啟用了靜態(tài)文件和默認頁,禁用了目錄瀏覽。
下面舉個例子熟悉一下:
假設文件目錄:
files
images
1.jpg
file.json
myindex.html
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,所以可以找到默認頁,此時會顯示默認頁的內(nèi)容。
假如我們沒有在DefaultFilesOptions.DefaultFileNames中添加文件名myindex.html,那么便找不到默認頁,但由于啟用了DirectoryBrowsing,所以此時會展示文件列表。
核心配置項
FileProvider
上面我們已經(jīng)見過PhysicalFileProvider了,它僅僅是眾多文件提供程序中的一種。所有的文件提供程序均實現(xiàn)了IFileProvider接口:
public?interface?IFileProvider
{
????//?獲取給定路徑的目錄信息,可枚舉該目錄中的所有文件
????IDirectoryContents?GetDirectoryContents(string?subpath);
????//?獲取給定路徑的文件信息
????IFileInfo?GetFileInfo(string?subpath);
????//?創(chuàng)建指定?filter?的?ChangeToken
????IChangeToken?Watch(string?filter);
}
public?interface?IDirectoryContents?:?IEnumerable<IFileInfo>,?IEnumerable
{
????bool?Exists?{?get;?}
}
public?interface?IFileInfo
{
????bool?Exists?{?get;?}
????bool?IsDirectory?{?get;?}?
????DateTimeOffset?LastModified?{?get;?}
????//?字節(jié)(bytes)長度
????//?如果是目錄或文件不存在,則是?-1
????long?Length?{?get;?}
????//?目錄或文件名,純文件名,不包括路徑
????string?Name?{?get;?}
????//?文件路徑,包含文件名
????//?如果文件無法直接訪問,則返回?null
????string?PhysicalPath?{?get;?}
????//?創(chuàng)建該文件只讀流
????Stream?CreateReadStream();
}
常用的文件提供程序有以下三種:
PhysicalFileProvider ManifestEmbeddedFileProvider CompositeFileProvider
glob模式
在介紹這三種文件提供程序之前,先說一下glob模式,即通配符模式。兩個通配符分別是*和**。
*:匹配當前目錄層級(不包含子目錄)下的任何內(nèi)容、任何文件名或任何文件擴展名,可以通過/、\和.進行分隔。**:匹配目錄多層級(包含子目錄)的任何內(nèi)容,用于遞歸匹配多層級目錄的多個文件。
PhysicalFileProvider
PhysicalFileProvider用于提供物理文件系統(tǒng)的訪問。該提供程序需要將文件路徑范圍限定在一個目錄及其子目錄中,不能訪問目錄外部的內(nèi)容。
當實例化該文件提供程序時,需要提供一個絕對的目錄路徑,作為文件目錄的root。
PhysicalFileProvider目錄或文件路徑不支持glob(通配符)模式。
ManifestEmbeddedFileProvider
ManifestEmbeddedFileProvider用于提供嵌入在程序集中的文件的訪問。
可能你對這個嵌入文件比較陌生,沒關系,請按照下面的步驟來:
安裝Nuget包:Install-Package Microsoft.Extensions.FileProviders.Embedded
編輯.csproj文件:
添加,并設置為true
使用添加要嵌入的文件
以下是 .csproj 文件的示例:
<Project?Sdk="Microsoft.NET.Sdk.Web">
??<PropertyGroup>
????<TargetFramework>net5.0TargetFramework>
????<GenerateEmbeddedFilesManifest>trueGenerateEmbeddedFilesManifest>
??PropertyGroup>
??<ItemGroup>
????<PackageReference?Include="Microsoft.Extensions.FileProviders.Embedded"?Version="5.0.11"?/>
??ItemGroup>
??<ItemGroup>
????<EmbeddedResource?Include="files\**"?/>
??ItemGroup>
Project>
現(xiàn)在我們通過ManifestEmbeddedFileProvider來提供嵌入到程序集的 files 目錄下文件的訪問:
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);
}
現(xiàn)在,你可以通過 http://localhost:5000/files/file.json 來訪問文件了。
CompositeFileProvider
CompositeFileProvider用于將多種文件提供程序進行集成。
如:
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);
}
現(xiàn)在,你可以通過 http://localhost:5000/composite/file.json 來訪問文件了。
ContentTypeProvider
Http請求頭中的Content-Type大家一定很熟悉,ContentTypeProvider就是用來提供文件擴展名和MIME類型映射關系的。
若我們沒有顯示指定ContentTypeProvider,則框架默認使用FileExtensionContentTypeProvider,其實現(xiàn)了接口IContentTypeProvider:
public?interface?IContentTypeProvider
{
????//?嘗試根據(jù)文件路徑,獲取對應的?MIME?類型
????bool?TryGetContentType(string?subpath,?out?string?contentType);
}
public?class?FileExtensionContentTypeProvider?:?IContentTypeProvider
{
????public?FileExtensionContentTypeProvider()
????????:?this(new?Dictionary<string,?string>(StringComparer.OrdinalIgnoreCase)
????????{
????????????//?...此處省略一萬字
????????}
????{
????}
????public?FileExtensionContentTypeProvider(IDictionary<string,?string>?mapping)
????{
????????Mappings?=?mapping;
????}
????public?IDictionary<string,?string>?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的無參構造函數(shù)中,默認添加了380種已知的文件擴展名和MIME類型的映射,存放在Mappings屬性中。你也可以添加自定義的映射,或移除不想要的映射。
核心中間件
StaticFileMiddleware
通過UseStaticFiles擴展方法,可以方便的注冊StaticFileMiddleware中間件:
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方法:
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
????????{
????????????//?嘗試提供靜態(tài)文件
????????????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
????????{
????????????//?若文件存在,則提供該靜態(tài)文件
????????????return?fileContext.ServeStaticFile(context,?_next);
????????}
????????return?_next(context);
????}
}
DirectoryBrowserMiddleware
通過UseDirectoryBrowser擴展方法,可以方便的注冊DirectoryBrowserMiddleware中間件:
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方法:
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中間件:
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方法:
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?????????????????{
????????????????????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,后續(xù)通過?StaticFileMiddleware?提供該頁面
????????????????????????context.Request.Path?=?new?PathString(Helpers.GetPathValueWithSlash(context.Request.Path)?+?defaultFile);
????????????????????????break;
????????????????????}
????????????????}
????????????}
????????}
????????return?_next(context);
????}
}
FileServer
FileServer并不是某個具體的中間件,它的實現(xiàn)還是依賴了StaticFileMiddleware、DirectoryBrowserMiddleware和DefaultFilesMiddleware這3個中間件。不過,我們可以看一下UseFileServer里的邏輯:
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兩個文件提供程序。下面我們就看一下他們是如何被初始化的。
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 受大小寫和基礎文件系統(tǒng)字符的限制。例如,Windows 不區(qū)分大小寫,但 macOS 和 Linux 區(qū)分大小寫。如果使用 IIS 托管應用,那么 IIS 自帶的靜態(tài)文件處理器是不工作的,均是使用 ASP.NET Core Module 進行處理的,包括靜態(tài)文件處理。
小結
使用UseFileServer擴展方法提供文件瀏覽和訪問,其集成了UseStaticFiles、UseDirectoryBrowser和UseDefaultFiles三個中間件的功能。 UseStaticFiles:注冊StaticFilesMiddleware,提供文件訪問UseDirectoryBrowser:注冊DirectoryBrowserMiddleware,提供文件目錄瀏覽UseDefaultFiles:注冊DefaultFilesMiddleware,當Url未指定訪問的文件名時,提供默認頁。文件提供程序均實現(xiàn)了接口IFileProvider,常用的文件提供程序有以下三種: PhysicalFileProvider:提供物理文件系統(tǒng)的訪問ManifestEmbeddedFileProvider:提供嵌入在程序集中的文件的訪問CompositeFileProvider:用于將多種文件提供程序進行集成。可通過 IWebHostingEnvironment獲取ContentRootFileProvider(默認目錄為項目根目錄)和WebRootFileProvider(默認目錄為Web根目錄)。
轉(zhuǎn)自:xiaoxiaotank
鏈接:cnblogs.com/xiaoxiaotank/p/15496538.html
- EOF -
看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人
推薦關注「DotNet」,提升.Net技能?
點贊和在看就是最大的支持??
