寫在前面 現在部署Asp.Net Core應用已經不再限制於Windows的IIS上,更多的是Docker容器、各種反向代理來部署。也有少部分用IIS部署的,IIS部署確實是又快又簡單,圖形化操作三下五除二就可以發佈好一個系統了。在過去Asp.Net MVC 項目部署的時候,還常常使用IIS一個功能 ...
寫在前面
現在部署Asp.Net Core應用已經不再限制於Windows的IIS上,更多的是Docker容器、各種反向代理來部署。也有少部分用IIS部署的,IIS部署確實是又快又簡單,圖形化操作三下五除二就可以發佈好一個系統了。在過去Asp.Net MVC 項目部署的時候,還常常使用IIS一個功能——虛擬目錄。
虛擬目錄可以直接定位到非項目的其他路徑,將路徑作為網站的一部分,可實現上傳文件保存到其他盤符或間接的使用項目以外的靜態文件。在Asp.Net MVC中從虛擬路徑中存取文件也很簡單,如 Server.MapPath("~/Upload/liohuang.jpg");
但在Asp.Net Core上不同,它被抽象出一個“文件系統”,也就是FileProvider。FileProvider是對所有實現了IFileProvider介面的所有類型以及對應對象的統稱,在Artech蔣老師的《.NET Core的文件系統[2]:FileProvider是個什麼東西?》文章中已經透析了,這裡不在羅里吧嗦了。
這篇文章要解決的內容是:Asp.Net Core應用中,如何優雅的使用“虛擬目錄”。
實操
首先,新建一個.Net Core WebApi空項目部署在D盤,“虛擬目錄”假設物理路徑在F盤,分別創建三個測試目錄: F:/test1 、 F:/test2 和 F:/test3 ,目錄里分別存放對應的文件 1/2/3.jpg 和 mybook.txt 。
讀取虛擬目錄文件
在 Startup.ConfigureServices 註入 IFileProvider :
services.AddSingleton<IFileProvider>(new PhysicalFileProvider("F:\\test1"));
新建一個控制器,讀取 mybook.txt 中的內容:
[ApiController] [Route("[controller]/[action]")] public class LioHuangController : ControllerBase { [HttpGet] public object GetFiles([FromServices]IFileProvider fileProvider) { var file = fileProvider.GetFileInfo("mybook.txt"); if (file.Exists) { return ReadTxtContent(file.PhysicalPath); } return 0; } /// <summary> /// 讀取文本 (原文地址:https://www.cnblogs.com/EminemJK/p/13362368.html) /// </summary> private string ReadTxtContent(string Path) { if (!System.IO.File.Exists(Path)) { return "Not found!"; } using (StreamReader sr = new StreamReader(Path, Encoding.UTF8)) { StringBuilder sb = new StringBuilder(); string content; while ((content = sr.ReadLine()) != null) { sb.Append(content); } return sb.ToString(); } } }
訪問介面,介面讀取文件之後,返回內容:
IFileProvider 介面採用目錄來組織文件,並統一使用 IFileInfo 介面來表示, PhysicalPath 表示文件的物理路徑。
public interface IFileInfo { bool Exists { get; } bool IsDirectory { get; } DateTimeOffset LastModified { get; } string Name { get; } string PhysicalPath { get; } Stream CreateReadStream(); }
如多個虛擬目錄,怎麼處理?簡單,註入多個 IFileProvider 即可,
services.AddSingleton<IFileProvider>(new PhysicalFileProvider("F:\\test1")); services.AddSingleton<IFileProvider>(new PhysicalFileProvider("F:\\test2")); services.AddSingleton<IFileProvider>(new PhysicalFileProvider("F:\\test3"));
代碼修改為:
public object GetFiles([FromServices] IEnumerable<IFileProvider> fileProviders)
IEnumerable<IFileProvider> fileProviders 介面數組將會有三個,按註入的順序對應不同的目錄。當然,註入 IFileProvider 的時候,就可以封裝一層了,下麵再講。
另外,有的說直接 ReadTxtContent("F:\test1\mybook.txt"); 不香嗎?香,Asp.Net Core的訪問許可權要比Asp.Net MVC之前老版本項目要高許多,確實是可以直接讀取項目以外的文件,但是並不適合直接去訪問,除非說你只有一個地方使用到,那麼就可以直接讀取,但靜態的文件的訪問,就訪問不到了,僅僅是後臺讀取而已。所以統一使用 IFileProvider 來約束,代碼的可維護性要高許多。
靜態文件訪問
在Startup.Configure設置靜態文件目錄,即可:
app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider("F:\\test1"), RequestPath = "/test" });; app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider("F:\\test2"), RequestPath = "/test" }); app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider("F:\\test3"), RequestPath = "/test" });
FileProvider 同上面所說的,設置好物理路徑的根目錄, RequestPath 則是訪問路徑的首碼,必須是斜桿 “/” 開頭,訪問地址首碼則為: https://localhost:5001/test/ 。設置好之後,就可以訪問項目以外的路徑了。
如在IIS部署的時候 ,可以直接忽略IIS中的虛擬目錄設置,完完全全可以通過註入的配置來設置達到“虛擬目錄”的效果。
簡化配置
為了方便達到真實項目中可以直接使用,那麼就要設置為可配置的:
在 appsettings.json 中設置:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "VirtualPath": [ { "RealPath": "F:\\test1", //真實路徑 "RequestPath": "/test", "Alias": "first" }, { "RealPath": "F:\\test2", //真實路徑 "RequestPath": "/test", "Alias": "second" }, { "RealPath": "F:\\test3", //真實路徑 "RequestPath": "/test", "Alias": "third" } ] }
創建對應的實體映射:
public class VirtualPathConfig { public List<PathContent> VirtualPath { get; set; } } public class PathContent { public string RealPath { get; set; } public string RequestPath { get; set; } public string Alias { get; set; } }
在 PhysicalFileProvider 上封裝一層,加入別名便於獲取:
public class MyFileProvider : PhysicalFileProvider { public MyFileProvider(string root, string alias) : base(root) { this.Alias = alias; } public MyFileProvider(string root, Microsoft.Extensions.FileProviders.Physical.ExclusionFilters filters, string alias) : base(root, filters) { this.Alias = alias; } /// <summary> /// 別名 /// </summary> public string Alias { get; set; } }
調整 Startup.ConfigureServices 和 Startup.Configure :
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.Configure<VirtualPathConfig>(Configuration); var config = Configuration.Get<VirtualPathConfig>().VirtualPath; config.ForEach(f => { services.AddSingleton(new MyFileProvider(f.RealPath,f.Alias)); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var config = Configuration.Get<VirtualPathConfig>().VirtualPath; config.ForEach(f => { app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(f.RealPath), RequestPath =f.RequestPath }); }); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
最後,調整調用方式,即可。
[HttpGet] public object GetFiles([FromServices] IEnumerable<MyFileProvider> fileProviders) { var file = fileProviders.FirstOrDefault(x=>x.Alias=="first").GetFileInfo("mybook.txt"); if (file.Exists) { return ReadTxtContent(file.PhysicalPath); } return 0; }
最後
物理文件系統的抽象通過 PhysicalFileProvider 這個 FileProvider 來實現,藉助 IFileProvider 的特點,其實可以擴展實現輕量“雲盤”的功能了,而不僅僅只是實現IIS虛擬目錄功能。搞定,今晚不加班!
本文同步在DotNetGeek(ID:dotNetGeek)公眾號發佈