Asp.Net Core 中的“虚拟目录”

  • A+
所属分类:.NET技术
摘要

  现在部署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 Core 中的“虚拟目录”

虚拟目录可以直接定位到非项目的其他路径,将路径作为网站的一部分,可实现上传文件保存到其他盘符或间接的使用项目以外的静态文件。在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();             }         }     }

访问接口,接口读取文件之后,返回内容:

Asp.Net Core 中的“虚拟目录”

 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:test1mybook.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/ 。设置好之后,就可以访问项目以外的路径了。

Asp.Net Core 中的“虚拟目录”

Asp.Net Core 中的“虚拟目录”

Asp.Net Core 中的“虚拟目录”

如在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)公众号发布