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. SQL Server 分区表补充说明

    分区教程参阅:http://database.9sssd.com/mssql/art/951 切换分区(归档):http://technet.microsoft.com/zh-cn/library/m ...

  2. DOS 下 mysql 导入.SQL

  3. js 事件处理程序 事件对象

    事件:用户或浏览器自身执行的动作: 事件处理程序:响应某个事件的函数: 事件流:从页面中接收事件的顺序. 1.DOM事件流 "DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段 ...

  4. asp.net开发中常见公共捕获异常方式总结(附源码)

    本文实例总结了asp.net开发中常见公共捕获异常方式.分享给大家供大家参考,具体如下: 前言:在实际开发过程中,对于一个应用系统来说,应该有自己的一套成熟的异常处理框架,这样当异常发生时,也能得到统 ...

  5. (视频) 《快速创建网站》1. 网站管理平台WordPress & 微软Azure 云计算简介

    网站并不神秘,过节了,在家闲的没事的,自己建个网站玩玩吧.每段视频不超过15分钟,地铁/公交/睡前/醒来看一段,几天之后变身建站专家,找老板加薪去! 在普通人眼里,创建网站是专业开发人员和IT工程师才 ...

  6. spring annotation简述

    一.Annotation基本概念 Annotation是jdk5以后出现的新特性,在jdk中,其内置了许多自己的Annotation,例如@Override,@SuppresWarning,@Depr ...

  7. 编写Java应用程序,定义Animal类,此类中有动物的属性:名称 name,腿的数量legs,统计动物的数量 count;方法:设置动物腿数量的方法 void setLegs(),获得腿数量的方法 getLegs(),设置动物名称的方法 setKind(),获得动物名称的方法 getKind(),获得动物数量的方法 getCount()。定义Fish类,是Animal类的子类,统计鱼的数量 co

    package com.hanqi.test; public class Animal { private String name; private int legs; private int cou ...

  8. php反射机制

    PHP5添加了一项新的功能:Reflection.这个功能使得phper可以reverse-engineer class, interface,function,method and extensio ...

  9. android handler runnable使用实例(关键是内部run中停止)

    .java package com.example.mydemo; import android.app.Activity; import android.os.Bundle; import andr ...

  10. Java基础语法总结2

    三.运算符 Java基 本 的 运 算 符 按功能分有 下 面 几 类 : 1.算 术 运 算 符 (+,-,*,/,%,++,--) Java对 加 运 算 符 进 行 了 扩 展 ,使 它 能 够 ...