DirectoryBrowserMiddleware中间件如何呈现目录结构

和StaticFileMiddleware中间件一样,DirectoryBrowserMiddleware中间本质上还是定义了一个请求地址与某个物理目录之间的映射关系,而目标目录体现为一个FileProvider对象。当这个中间件接收到匹配的请求后,会根据请求地址解析出对应目录的相对路径,并利用这个FileProvider获取目录的内容。目录的内容最终会以一个HTML文档的形式被定义,而此HTML最终会被这个中间件作为响应的内容,“目录浏览器”的实现原理就这么简单。 [本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、DirectoryBrowserMiddleware
二、DirectoryFormatter
三、具体请求处理逻辑
四、自定义DirectoryFormatter

一、DirectoryBrowserMiddleware

接下来我们来看看DirectoryBrowserMiddleware的定义。如下面的代码片段所示,DirectoryBrowserMiddleware的第二个构造函数具有四个参数,其中第二个参数是代表当前执行环境的HostingEnvironment。作为第三个参数的是一个HtmlEncoder对象,当目标目录被呈现为一个HTML文档的时候,它被用于实现针对HTML的编码,如果没有显式指定(调用第一个构造函数),默认的HtmlEncoder(HtmlEncoder.Default)会被使用。至于第四个类型为IOptions<DirectoryBrowserOptions>的参数,则承载了针对DirectoryBrowserMiddleware的配置选项,DirectoryBrowserOptions与前面介绍的StaticFileOptions一样,它们都是SharedOptionsBase的子类。

   1: public class DirectoryBrowserMiddleware
   2: {
   3:     public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment env, IOptions<DirectoryBrowserOptions> options)
   4:     public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, HtmlEncoder encoder, IOptions<DirectoryBrowserOptions> options);
   5:     public Task Invoke(HttpContext context);
   6: }
   7:  
   8: public class DirectoryBrowserOptions : SharedOptionsBase
   9: {
  10:     public IDirectoryFormatter Formatter { get; set; }
  11:  
  12:     public DirectoryBrowserOptions();
  13:     public DirectoryBrowserOptions(SharedOptions sharedOptions);
  14: }

二、DirectoryFormatter

DirectoryBrowserMiddleware中间件的目的很明确,就是将目录下的内容(文件和子目录)格式化成一种可读的形式响应给客户端,针对目录内容的响应最终实现在一个DirectoryFormatter对象上。DirectoryFormatter是我们对所有实现了IDirectoryFormatter接口的类型与对应对象的统称,DirectoryBrowserOptions的Formatter属性设置和返回的就是这个一个对象。

如下面的代码片段所示,IDirectoryFormatter接口仅仅包含一个GenerateContentAsync方法。当实现这个方法的时候,我们可以利用第一个类型为HttpContext的参数获取当前请求上下文的信息。该方法的另一个参数返回一组FileInfo的集合,每个FileInfo代表目标下的某个以文件或者子目录。

   1: public interface IDirectoryFormatter
   2: {
   3:     Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents);
   4: }

我们知道默认情况下请求目录的内容在页面上是以一个表格的形式被呈现的,包含这个表格的HTML文档是默认使用的DirectoryFormatter生成的,它是一个类型为HtmlDirectoryFormatter的对象。如下面的代码片段所示,我们在构造一个HtmlDirectoryFormatter对象的时候需要指定一个HtmlEncoder对象,该对象最初来源于构造DirectoryBrowserMiddleware时指定的那个HtmlEncoder对象。

   1: public class HtmlDirectoryFormatter : IDirectoryFormatter
   2: {
   3:     public HtmlDirectoryFormatter(HtmlEncoder encoder);
   4:     public virtual Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents);
   5: }

三、具体请求处理逻辑

既然最复杂的工作(呈现目录内容)都已经交给DirectoryFormatter来完成了,DirectoryBrowserMiddleware自身的工作其实就没有多少了。为了更好的说明这个中间件在处理请求是具体做了些什么,我们采用一种比较好理解的方式对DirectoryBrowserMiddleware类型进行了重新定义,具体的实现体现在如下所示的代码片段中。

   1: public class DirectoryBrowserMiddleware
   2: {
   3:     private RequestDelegate _next;
   4:     private DirectoryBrowserOptions _options;
   5:  
   6:     public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment env, IOptions<DirectoryBrowserOptions> options) : this(next, env, HtmlEncoder.Default,options)
   7:     { }
   8:  
   9:     public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment env, HtmlEncoder encoder, IOptions<DirectoryBrowserOptions> options)
  10:     {
  11:         _next                      = next;
  12:         _options                   = options.Value;
  13:         _options.FileProvider      = _options.FileProvider ?? env.WebRootFileProvider;
  14:         _options.Formatter         = _options.Formatter ?? new HtmlDirectoryFormatter(encoder);
  15:     }
  16:  
  17:     public async Task Invoke(HttpContext context)
  18:     {
  19:         //只处理GET和HEAD请求
  20:         if (!new string[] { "GET", "HEAD" }.Contains(context.Request.Method, StringComparer.OrdinalIgnoreCase))
  21:         {
  22:             await _next(context);
  23:             return;
  24:         }
  25:  
  26:        //检验当前路径是否与注册的请求路径相匹配
  27:         PathString path = new PathString(context.Request.Path.Value.TrimEnd('/') + "/");
  28:         PathString subpath;
  29:         if (!path.StartsWithSegments(_options.RequestPath, out subpath))
  30:         {
  31:             await _next(context); 
  32:             return;
  33:         }
  34:  
  35:         //检验目标目录是否存在
  36:         IDirectoryContents directoryContents = _options.FileProvider.GetDirectoryContents(subpath);
  37:         if (!directoryContents.Exists)
  38:         {
  39:             await _next(context); 
  40:             return;
  41:         }
  42:  
  43:         //如果当前路径不以"/"作为后缀,会响应一个针对“标准”URL的重定向
  44:         if (!context.Request.Path.Value.EndsWith("/"))
  45:         {
  46:             context.Response.StatusCode = 302;
  47:             context.Response.GetTypedHeaders().Location = new Uri(path.Value + context.Request.QueryString);
  48:             return;
  49:         }
  50:  
  51:         //利用DirectoryFormatter响应目录内容
  52:         await _options.Formatter.GenerateContentAsync(context, directoryContents);
  53:     }
  54: }

如上面的代码片段所示,当DirectoryBrowserMiddleware最终利用注册的DirectoryFormatter来响应目标目录的内容之前,它会做一系列的前期工作。比如它会验证当前请求是否是GET或者HEAD请求,以及当前的URL是否与注册的请求路径相匹配,在匹配的情况下还需要验证目标目录是否存在。除此之外,这个中间件要求访问目录的请求路劲必须以字符“/”作为后缀,否则会在目前的路径上添加这个后缀并针对最终的路径发送一个重定向。所以我们利用浏览器发送针对某个目录的请求的时候,URL明明没有指定“/”作为后缀,这个后缀会自动给我们加上,这就是重定向的作用。

四、自定义DirectoryFormatter

由于目录的内容在浏览器中的呈现方式完全由DirectoryFormatter完成,如果实现在HtmlDirectoryFormatter的默认呈现方式不能满足需求(比如我们需要这个页面与现有网站保持相同的风格),这可以通过注册一个自定义的DirectoryFormatter来完成。接下来我们通过一个简单的实例来演示如何定义这么一个DirectoryFormatter。我们将自定义的DirectoryFormatter命名为ListDirectoryFormatter,应为它仅仅将所有文件或者子目录显示为一个简单的列表。

   1: public class ListDirectoryFormatter : IDirectoryFormatter
   2: {
   3:     public async Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents)
   4:     {
   5:         context.Response.ContentType = "text/html";
   6:         await context.Response.WriteAsync("<html><head><title>Index</title><body><ul>");
   7:         foreach (var file in contents)
   8:         {
   9:             string href = $"{context.Request.Path.Value.TrimEnd('/')}/{file.Name}";
  10:             await context.Response.WriteAsync($"<li><a href='{href}'>{file.Name}</a></li>");
  11:         }
  12:         await context.Response.WriteAsync("</ul></body></html>");
  13:     }
  14: }
  15:  
  16: public class Program
  17: {
  18:     public static void Main()
  19:     {
  20:         new WebHostBuilder()
  21:             .UseContentRoot(Directory.GetCurrentDirectory())
  22:             .UseKestrel()
  23:             .Configure(app => app.UseDirectoryBrowser(new DirectoryBrowserOptions {Formatter = new ListDirectoryFormatter()}))
  24:             .Build()
  25:             .Run();
  26:     }
  27: }

如上面的代码片段,ListDirectoryFormatter最终响应的是一个完整的HTML文档,它的主体部分只包含一个通过<ul>…</ul>表示的无序列表。列表元素(<li>)是一个针对文件或者子目录的链接。在调用扩展方法UseDirectoryBrowser注册DirectoryBrowserMiddleware中间件的时候,我们为将一个ListDirectoryFormatter对象设置为DirectoryBrowserOptions的Formatter属性。目录内容最终将会采用如图9所示的形式呈现在浏览器上。


ASP.NET Core应用针对静态文件请求的处理[1]: 以Web的形式发布静态文件
ASP.NET Core应用针对静态文件请求的处理[2]: 条件请求与区间请求
ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求
ASP.NET Core应用针对静态文件请求的处理[4]: DirectoryBrowserMiddleware中间件如何呈现目录结构
ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面

作者:蒋金楠 
微信公众账号:大内老A
微博:www.weibo.com/artech

DirectoryBrowserMiddleware中间件如何呈现目录结构的更多相关文章

  1. ASP.NET Core应用针对静态文件请求的处理[4]: DirectoryBrowserMiddleware中间件如何呈现目录结构

    和StaticFileMiddleware中间件一样,DirectoryBrowserMiddleware中间本质上还是定义了一个请求地址与某个物理目录之间的映射关系,而目标目录体现为一个FilePr ...

  2. react第十八单元(redux中间件redux-thunk,redux工程目录的样板代码,规范目录结构)

    第十八单元(redux中间件redux-thunk,redux工程目录的样板代码,规范目录结构) #课程目标 中间件:中间件增强redux的可扩展性,实现功能复用的目的. redux-thunk异步逻 ...

  3. DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”

    DeveloperExceptionPageMiddleware中间件如何呈现"开发者异常页面" 在<ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式&g ...

  4. Laravel项目目录结构说明

    Laravel项目目录结构说明: |- vendor 目录包含你的 Composer 依赖模块及laravel框架. |- bootstrap 目录包含几个框架启动跟自动加载配置的文件. |- app ...

  5. Hadoop阅读笔记(五)——重返Hadoop目录结构

    常言道:男人是视觉动物.我觉得不完全对,我的理解是范围再扩大点,不管男人女人都是视觉动物.某些场合(比如面试.初次见面等),别人没有那么多的闲暇时间听你诉说过往以塑立一个关于你的完整模型.所以,第一眼 ...

  6. TP学习笔记一(tp的目录结构 , tp的输出方式)

    一.ThinkPHP的介绍 //了解 MVC M - Model 模型 工作:负责数据的操作 V - View 视图(模板) 工作:负责前台页面显示 C - Controller 控制器(模块) 工作 ...

  7. Vue学习(一)Vue目录结构

    安装教程网上一大把,可以自己搜索.记录下学习过程. 认识下Vue的目录结构,取自:https://www.cnblogs.com/dragonir/p/8711761.html vue 文件目录结构详 ...

  8. Linux 目录结构详解

    Linux目录详解 Linux目录详解(RHEL5.4) 由于linux是开放源代码,各大公司和团体根据linux的核心代码做各自的操作,编程.这样就造成在根下的目录的不同.这样就造成个人不能使用他人 ...

  9. 比起Windows,怎样解读Linux的文件系统与目录结构?

    比起Windows,怎样解读Linux的文件系统与目录结构? Linux 和Windows的文件系统有些不同,在学习使用 Linux 之前,若能够了解这些不同,会有助于后续学习. 本文先对Window ...

随机推荐

  1. Mac OS 文件、文件夹重命名的方法

    在Mac OS中,文件和文件名重命名的方法非常简单 选中你想要命名的文件或者文件夹,按回车,可以直接重命名,输入你要修改的内容,确认后,再按回车就OK啦--- 希望能对你有所帮助^_^

  2. uniqid函数产生唯一id,减少碰撞几率

    $uuid = str_replace(".","",uniqid(mt_rand(100000,999999),true)); //基于当前时间微妙数,与mt ...

  3. php设计模式 适配器模式

    适配器模式,可以将截然不同的函数接口封装成统一的API: 应用举例,PHP的数据库操作有Mysql.Mysqli.pdo三种,可以用适配器模式统一成一致,类似的场景还有cache适配器,将memcac ...

  4. android Gui系统之SurfaceFlinger(5)---Vsync(2)

    9.Vsync第二部分 在上一篇中我们讲到,视图的刷新需要很多步骤, void SurfaceFlinger::handleMessageRefresh() { ATRACE_CALL(); preC ...

  5. 熟练掌握js中this的用法,解析this在不同应用场景的作用

    由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象.当前对象或者任意对象,这完全取决于函数的调用方式. JavaScript 中函数的调用有以下几种方式:作 ...

  6. yii2整合百度编辑器umeditor

    作者:白狼 出处:www.manks.top/article/yii2_umeditor 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责 ...

  7. jQuery简单入门(四)

    4.表单应用 表单是HTML的重要组成部分,在采集.提交用户输入的信息和显示列表数据等需求中有重要作用 表单应用 一个简单的表单HTML示例: <form action=”url” method ...

  8. mysql截取longblob类型字段内一小块数据的方法

    由于longblob类型的字段内容一般都好大,最大限制是4G,所以在数据查询中读取一整块数据的方式是不现实的,这需要要截取的方法来获取需要的数据. 方法如下: hex(substring(A, ind ...

  9. Xcode同时兼容Xcode7和Xcode8,两个版本并存,也适用于先升8再安装7

    先吐槽一下之前看到的一个教程,如下: 先在应用程序内,拷贝一份之前的xcode,然后再安装新版本,发现这种安装完成就是在之前上面迭代了  有木有?等于没任何作用 我这边就是不小心先升级了8,然后再安装 ...

  10. Android gdb 调试

    [1].终端目录设置到: proj.android[2].make文件的编译选项加上: -g -gstabs+[3].执行编译脚本: sh ./build_native.sh NDK_DEBUG=1[ ...