经过前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. Java Web1

    Java Web应用的核心技术是Java Server Page和Servlet.此外,开发一个完整的Java Web应该涉及一下几种概念及技术. 1.Servlet组件       Servlet响 ...

  2. LeetCode(115) Distinct Subsequences

    题目 Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequen ...

  3. MFC修改初始窗口大小和窗口名字禁止窗口最大,最小化

    2,在里面就可以修改初始窗口大小和窗口名字 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs){if( !CFrameWnd::PreCrea ...

  4. The Template method pattern

    public class TemplateMethodDemo { public static void main(String[] args) { Teacher test=new Javatrea ...

  5. nodejs研究笔记

    首先呢,安装 1:安装mongodb-win32-x86_64-3.2.5-signed.msi 2:手动创建目录 如 C:\data\db 及 C:\data\dbConf 3:管理员身份运行 cm ...

  6. 微信小程序 wx.uploadFile在安卓手机上面the same task is working问题解决

    微信小程序上传图片的时候,如果是多图片上传,一般都是直接用一个循环进行wx.uploadFile 这个在电脑上面测试与苹果手机上面都不会有什么问题 但当用安卓测试的时候,你会发现小程序会提示一个the ...

  7. c#用正则表达式判断字符串是否全是数字、小数点、正负号组成 Regex reg = new Regex(@"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$");

    Regex reg = new Regex(@"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][ ...

  8. 数据库SQL语句中根据当前日期计算其他日期小结

    问题描述:我们在写存储过程和函数的时候经常会碰到利用当前日期计算出上周开始日期.结束日期,或者计算上个月的开始日期结束日期等问题.最近写了几个存储过程和函数,其中都涉及到了日期计算问题,在这里简单做一 ...

  9. HTML URL 编码

    转自:http://www.w3school.com.cn/tags/html_ref_urlencode.htmlURL 编码 - 从 %00 到 %8f ASCII Value URL-encod ...

  10. HTML5的文档结构和新增标签

    一.HTML5 文档结构1.第一步:打开 开发工具,打开指定文件夹:2.第二步:保存 index.html 文件到磁盘中,.html 是网页后缀:3.第三步:开始编写 HTML5 的基本格式.< ...