经过前2篇文章的介绍,相信大家已经对OWIN和Katana有了基本的了解,那么这篇文章我将继续OWIN和Katana之旅——创建自定义的Middleware中间件。

何为Middleware中间件

Middleware中间件从功能上可以理解为用来处理Http请求,当Server将Http请求封装成符合OWIN规范的字典后,交由Middleware去处理,一般情况下,Pipeline中的Middleware以链式的形式处理Http请求,即每一个Middleware都是最小的模块化,彼此独立、高效。

从语法上理解Middleware的话,他是一个应用程序委托(Func<IDictionary<string, object>, Task>)的实例,通过使用IAppBuilder 接口的Use或者Run方法将一个Middleware插入到Pipeline中,不同的是使用Run方法不需要引用下一个Middleware,即他是Pipeline中最后的处理元素。

使用Inline方式注册Middleware

使用Use方法可以将一个Middleware插入到Pipeline中,值得注意的是需要传入下一个Middleware的引用,代码如下所示:

  1. app.Use(new Func<Func<IDictionary<string, object>, Task>/*Next*/,
  2.              Func<IDictionary<string, object>/*Environment Dictionary*/, Task>>(next => async env =>
  3.              {
  4.                  string before = "Middleware1--Before(inline)"+Environment.NewLine;
  5.                  string after = "Middleware1--After(inline)"+Environment.NewLine;
  6.                  var response = env["owin.ResponseBody"] as Stream;
  7.                  await response.WriteAsync(Encoding.UTF8.GetBytes(before), 0, before.Length);
  8.                  await next.Invoke(env);
  9.                  await response.WriteAsync(Encoding.UTF8.GetBytes(after), 0, after.Length);
  10.          }));

上述代码中,实例化了一个委托,它需要传入下一个Pipeline中的Middleware引用同时返回一个新的Middleware并插入到Pipeline中。因为是异步的,所以别忘了async、await关键字。

使用Inline+ AppFunc方式注册Middleware

为了简化书写,我为应用程序委托(Func<IDictionary<string, object>, Task>)类型创建了别名AppFunc:

  1. using AppFunc=Func<IDictionary<string,object>/*Environment Dictionary*/,Task/*Task*/>;

所以又可以使用如下方式来讲Middleware添加到Pipeline中:

  1. app.Use(new Func<AppFunc, AppFunc>(next => async env =>
  2. {
  3.     string before = "\tMiddleware2--Before(inline+AppFunc)" + Environment.NewLine;
  4.     string after = "\tMiddleware2--After(inline+AppFunc)" + Environment.NewLine;
  5.     var response = env["owin.ResponseBody"] as Stream;
  6.     await response.WriteAsync(Encoding.UTF8.GetBytes(before), 0, before.Length);
  7.     await next.Invoke(env);
  8.     await response.WriteAsync(Encoding.UTF8.GetBytes(after), 0, after.Length);
  9. }));

考虑到业务逻辑的增长,有必要将Lambda表达式中的处理逻辑给分离开来,所以对上述代码稍作修改,提取到一个名为Invoke的方法内:

  1. app.Use(new Func<AppFunc, AppFunc>(next => env => Invoke(next, env)));
  2. private async Task Invoke(Func<IDictionary<string, object>, Task> next, IDictionary<string, object> env)
  3.         {
  4.             var response = env["owin.ResponseBody"] as Stream;
  5.             string pre = "\t\tMiddleware 3 - Before (inline+AppFunc+Invoke)" + Environment.NewLine;
  6.             string post = "\t\tMiddleware 3 - After (inline+AppFunc+Invoke)" + Environment.NewLine;
  7.             await response.WriteAsync(Encoding.UTF8.GetBytes(pre), 0, pre.Length);
  8.             await next.Invoke(env);
  9.             await response.WriteAsync(Encoding.UTF8.GetBytes(post), 0, post.Length);
  10.         }

虽然将业务逻辑抽取到一个方法中,但Inline这种模式对于复杂的Middleware还是显得不够简洁、易懂。我们更倾向于创建一个单独的类来表示。

定义原生Middleware类的形式来注册Middleware

如果你只想简单的跟踪一下请求,使用Inline也是可行的,但对于复杂的Middleware,我倾向于创建一个单独的类,如下所示:

  1. public class RawMiddleware
  2.   {
  3.       private readonly AppFunc _next;
  4.       public RawMiddleware(AppFunc next)
  5.       {
  6.           this._next = next;
  7.       }
  8.       public async Task Invoke(IDictionary<string,object> env )
  9.       {
  10.           var response = env["owin.ResponseBody"] as Stream;
  11.           string pre = "\t\t\tMiddleware 4 - Before (RawMiddleware)" + Environment.NewLine;
  12.           string post = "\t\t\tMiddleware 4 - After (RawMiddleware)\r\n" + Environment.NewLine;
  13.           await response.WriteAsync(Encoding.UTF8.GetBytes(pre), 0, pre.Length);
  14.           await _next.Invoke(env);
  15.           await response.WriteAsync(Encoding.UTF8.GetBytes(post), 0, post.Length);
  16.       }
  17.   }

最后,依旧是通过Use方法来将Middleware添加到Pipeline中:

  1. //两者方式皆可
  2. //app.Use<RawMiddleware>();
  3. app.Use(typeof (RawMiddleware));

上述代码中,IAppBuilder实例的Use方法添加Middleware至Pipeline与Inline方式有很大不同,它接受一个Type而非Lambda表达式。在这种情形下,创建了一个Middleware类型的实例,并将Pipeline中下一个Middleware传递到构造函数中,最后当Middleware被执行时调用Invoke方法。

注意Middleware是基于约定的形式定义的,需要满足如下条件:

  • 构造函数的第一个参数必须是Pipeline中下一个Middleware
  • 必须包含一个Invoke方法,它接收Owin环境字典,并返回Task

使用Katana Helper来注册Middleware

程序集Microsoft.Owin包含了Katana为我们提供的Helper,通过他,可以简化我们的开发,比如IOwinContext封装了Owin的环境字典,强类型对象可以通过属性的形式获取相关数据,同时为IAppBuilder提供了丰富的扩展方法来简化Middleware的注册,如下所示:

  1. app.Use(async (context, next) =>
  2.            {
  3.                await context.Response.WriteAsync("\t\t\t\tMiddleware 5--Befone(inline+katana helper)"+Environment.NewLine);
  4.                await next();
  5.                await context.Response.WriteAsync("\t\t\t\tMiddleware 5--After(inline+katana helper)"+Environment.NewLine);
  6.            });

当然我们也可以定义一个Middleware类并继承OwinMiddleware,如下所示:

  1. public class MyMiddleware : OwinMiddleware
  2.    {
  3.        public MyMiddleware(OwinMiddleware next)
  4.            : base(next)
  5.        {
  6.  
  7.        }
  8.        public override async Task Invoke(IOwinContext context)
  9.        {
  10.            await context.Response.WriteAsync("\t\t\t\t\tMiddleware 6 - Before (Katana helped middleware class)"+Environment.NewLine);
  11.            await this.Next.Invoke(context);
  12.            await context.Response.WriteAsync("\t\t\t\t\tMiddleware 6 - After (Katana helped middleware class)"+Environment.NewLine);
  13.        }
  14.    }

然后将其添加到Pipeline中:

  1. app.Use<MyMiddleware>();

Middleware的执行顺序

在完成上面Middleware注册之后,在Configuration方法的最后添加最后一个的Middleware中间件,注意它并不需要对下一个Middleware的引用了,我们可以使用Run方法来完成注册:

  1. app.Run(context => context.Response.WriteAsync("\t\t\t\t\t\tHello World"+Environment.NewLine));

值得注意的是,Pipeline中Middleware处理Http Request顺序同注册顺序保持一致,即和Configuration方法中书写的顺序保持一致,Response顺序则正好相反,如下图所示:

最后,运行程序,查看具体的输出结果是否和我们分析的保持一致:

小结

在这篇文章中,我为大家讲解了自定义Middleware的创建,Katana为我们提供了非常多的方式来创建和注册Middleware,在下一篇文章中,我将继续OWIN和Katana之旅,探索Katana和其他Web Framework的集成。

ASP.NET MVC随想录——创建自定义的Middleware中间件的更多相关文章

  1. 创建自定义的Middleware中间件

    创建自定义的Middleware中间件 阅读目录 何为Middleware中间件 使用Inline方式注册Middleware 使用Inline+ AppFunc方式注册Middleware 定义原生 ...

  2. 在 ASP.NET MVC 中创建自定义 HtmlHelper

    在ASP.NET MVC应用程序的开发中,我们常碰到类似Html.Label或Html.TextBox这样的代码,它将在网页上产生一个label或input标记.这些HtmlHelper的扩展方法有些 ...

  3. 在ASP.NET MVC中创建自定义模块

    创建模块 module是实现了System.Web.IHttpModule接口的类.该接口定义了两个方法: Init:当模块初始化时被调用,传入的参数为HttpApplication对象,用于注册请求 ...

  4. ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇(转)

    ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇   阅读目录 ASP.NET Identity 前世今生 建立 ASP.NET Identity 使用ASP.NET ...

  5. ASP.NET MVC 随想录—— 使用ASP.NET Identity实现基于声明的授权,高级篇

    在这篇文章中,我将继续ASP.NET Identity 之旅,这也是ASP.NET Identity 三部曲的最后一篇.在本文中,将为大家介绍ASP.NET Identity 的高级功能,它支持声明式 ...

  6. ASP.NET MVC如何实现自定义验证(服务端验证+客户端验证)

    ASP.NET MVC通过Model验证帮助我们很容易的实现对数据的验证,在默认的情况下,基于ValidationAttribute的声明是验证被使用,我们只需 要将相应的ValidationAttr ...

  7. asp.net mvc Route 使用自定义条件(constraints)禁止某ip登陆

    asp.net mvc Route 使用自定义条件(constraints)禁止某ip登陆 前言 本文的目的是利用Mvc route创建一个自定义约束来控制路由跳转实现禁止ip登陆,当然例子可能不合理 ...

  8. ASP.NET MVC:创建 ModelBinder 自动 Trim 所有字符串

    ASP.NET MVC:创建 ModelBinder 自动 Trim 所有字符串 2010-12-29 21:32 by 鹤冲天, 4289 阅读, 14 评论, 收藏, 编辑 用户输入的字符串前后的 ...

  9. ASP.NET MVC 5 - 创建连接字符串(Connection String)并使用SQL Server LocalDB

    您创建的MovieDBContext类负责处理连接到数据库,并将Movie对象映射到数据库记录的任务中.你可能会问一个问题,如何指定它将连接到数据库? 实际上,确实没有指定要使用的数据库,Entity ...

随机推荐

  1. MongoDB基本命令用

    成功启动MongoDB后,再打开一个命令行窗口输入mongo,就可以进行数据库的一些操作. 输入help可以看到基本操作命令: show dbs:显示数据库列表  show collections:显 ...

  2. asp.net(C#) Excel导出类 导出.xls文件

    ---恢复内容开始--- using Microsoft.Office.Interop.Excel; 针对office 2003需添加引用Microsoft   Excel   11.0   Obje ...

  3. js获取焦点

    <input type = "text" id="football" /> 对上面获取焦点. 1.获取dom对象 var domFocus = do ...

  4. JavaScript-BOM-history:保存当前窗口打开后成功访问过的url历史记录栈

    history:保存当前窗口打开后成功访问过的url历史记录栈history.go(n):前进n步前进一步:history.go(1);后退一步:history.go(-1);刷新:history.g ...

  5. springboot使用之一:连接生产数据库,添加连接池

    项目中,难免遇到连接数据库的情形,目前来说springboot连接mybatis有两种,我这边使用的是mybatis官方提供XML方式的整合. 后面,对项目进行完善,引入了连接池,PageHelper ...

  6. 部署额外域控制器,Active Directory

      部署额外域控制器 转自:http://yuelei.blog.51cto.com/202879/117599 如果域中只有一台域控制器,一旦出现物理故障,我们即使可以从备份还原AD,也要付出停机等 ...

  7. MemCached 安装笔记

    安装步骤: 1. 下载libevent & memcached 源码包 分别把memcached和libevent下载回来,放到 /tmp 目录下: # cd /tmp     # wget ...

  8. To Use FTP Command in Linux

    Yesterday I was asked to upload a file in Linux to the corresponding server. I said "oops" ...

  9. 学习django之正则表达式re模块

    re(regular expression)模块 正则表达式(regular expression)主要功能是从字符串(string)中通过特定的模式(pattern),搜索想要找到的内容. 一.re ...

  10. 远程连接oracle服务器

    用户名 / 密码@172.16.16.203:1521/orcl