当前位置 博文首页 > Asp.Net Core 中的“虚拟目录”实现

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

    作者:山治先生 时间:2021-09-08 17:48

    目录
    • 写在前面
    • 实操
      • 读取虚拟目录文件
      • 静态文件访问
      • 简化配置
    • 最后

      写在前面

        现在部署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虚拟目录功能。搞定,今晚不加班!

      jsjbwy
      下一篇:没有了