经过前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. 用Backbone.js创建一个联系人管理系统(四)

    原文: Build a Contacts Manager Using Backbone.js: Part 4 这一系列教程的第四部分,教我们如何完成对已经存在的Contacts进行编辑和保存. 本教程 ...

  2. UIActivityIndicatorViewStyle

    UIActivityIndicatorView *indicatorV = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyl ...

  3. 实现十进制无符号整数m到十六进制数的转换功能

    /*利用顺序栈结构,编写算法函数void Dto16(unsigned int m)实现十进制无符号整数m到十六进制数的转换功能.*//******************************** ...

  4. java8 ArrayList源码阅读

    转载自 java8 ArrayList源码阅读 本文基于jdk1.8 JavaCollection库中有三类:List,Queue,Set 其中List,有三个子实现类:ArrayList,Vecto ...

  5. 图片浏览(CATransition)转场动画

    Main.storyboard ViewController.m // //  ViewController.m //  8A04.图片浏览(转场动画) // //  Created by huan ...

  6. css中怎么设置透明度的问题

    小伙伴们是不是在找怎么样去设置页面的透明度的方法呢...别找了,我这儿就有,而且肯定够用了. 我自己会用到的就有两种,可以和大家分享一下. 1.用opcity的方法去设置透明度.代码如下: .div ...

  7. linux修改主机名的方法

    linux修改主机名的方法 用hostname命令可以临时修改机器名,但机器重新启动之后就会恢复原来的值. #hostname   //查看机器名#hostname -i  //查看本机器名对应的ip ...

  8. STL源码--序列式容器

    1. vector: vector的内存管理,动态分配内存,不需要程序员来维护存储空间,是与array最大的区别,程序员只需从逻辑上关注代码,而不需要对内部的存储空间的分配和回收分心.首先,vecto ...

  9. 神经网络hopfield的学习

    Hopfield神经网络使用说明. 该神经网络有两个特点: 1,输出值只有0,1 2,Hopfield没有输入(input) 这里解释一下第二个特点,什么叫没有输入?因为在使用Hopfield网络的时 ...

  10. 基于VC的ACM音频编程接口压缩Wave音频(一)

    (一)概述 音频数据一般都具有较高的采样率,经过压缩的原始数据才具有实用价值,否则不仅要占用大量存储空间而且在播放或进行网络传输时效率也是非常低下的,所以音频数字压缩编码在多媒体应用中有着广泛而又重要 ...