十六、OWIN 和中间件

在本章中,我们将介绍:

  • 了解 OWIN, Katana 和新的 ASP.NET Core HTTP 管道
  • 使用内联中间件代码作为匿名方法—使用、运行、映射和映射时
  • 创建一个可重用的中间件组件
  • 将 HTTP 处理程序迁移到中间件
  • 将 HTTP 模块迁移到中间件

了解 OWIN, Katana 和新的 ASP.NET Core HTTP 管道

在本教程中,我们将深入了解 OWIN、Katana 和 ASP 中的中间件管道.NET 的核心。

一个解耦的抽象

开放的。net Web 接口(OWIN)是一个标准,它定义了 Web 服务器和。net Web 应用之间的接口。 实现 OWIN 允许我们在 web 应用、主机和服务器之间创建一个抽象,并将。net web 应用与 IIS 分离。 例如,要将 WebAPI 中的主机更改为在控制台应用或 Windows 服务中自托管的主机,请更改 ASP。 asp.net MVC 或 WebAPI 与 NancyFx。

主机是操作系统中加载服务器并创建管道的进程。 服务器在特定的端口上监听请求,将其重定向到管道,并返回对应用生成的请求的响应。 应用对收到的请求进行线程化并生成响应。

Katana -微软 OWIN 的实现

在 ASP.NET Core,为了从 IIS 中分离。NET 应用,我们可以使用 OWIN。 为了宿主 ASP.NET Web API 之外的 IIS,我们使用 Katana 项目(微软 OWIN 实现)。

使用 Katana,使用特定于 win 的库,我们仍然使用 Windows 环境。 ASP.NET MVC 5 与 IIS 太紧密了,然而,OWIN 为我们提供了一个新的 HTTP 管道来与 IIS 解耦。 自 ASP.NET Web API 2,将我们的 API 从 IIS 和 asp.net 中解耦.NET 管道生命周期,我们可以使用 OWIN。

为了实现这一点,使用 Katana 管道和类,我们使用一个Startup.cs文件而不是经典的Global.asax文件来配置我们的 API,并将主机更改为自托管(控制台、Windows 服务或 Azure worker 角色)。

另一种从服务器上解耦。net 应用的方法是使用 OWIN 作为 HTTP 管道,使用 NancyFx 替代 ASP.NET MVC 或 Web API。 我们可以使用一个不可知的方法来抽象 MVC 和 WebAPI,用 Nancy 来代替它们。 Nancy 是一个轻量级的框架,可以在 Mono、。net Core 或。net framework 上创建 HTTP 服务。

当请求到达主机时,在它到达应用代码之前,它要经过一个管道,该管道由 ASP。 由HttpApplication类定义的 NET/IIS 管道。 它通过 HTTP 处理程序和模块的组合。 我们可以通过创建新的 HTTP 模块和处理程序来在这个管道上聚合处理,但是我们对它没有太多的控制。

一个新的基于 ASP 的 OWIN 实现.NET Core

ASP.NET Core,我们现在不知道主机和 web 服务器。 我们使用与 OWIN 相同的原理,这意味着在应用、服务器和主机之间使用抽象。 它的新特点是,我们可以通过向管道中添加中间件组件来为应用组成管道。

ASP.NET Core 尊重 OWIN 原则,但不是 OWIN。 区别之一是,它将来自请求的所有传入数据映射到所有中间件(内置的、行创建的或组件创建的)可用的 HttpContext 对象。 另一个不同于以前版本的 ASP。 HttpContext对象现在与 IIS 和 System 无关了。 Web,并且更轻量级。 然而,我们总是有机会使用 OWIN 库,因此在 ASP 中使用 OWIN 管道.NET MVC 核心,就像我们使用 ASP.NET Web API 2.2。

ASP.NET Core 管道

在 ASP.NET Core 管道,我们可以通过两种方式添加中间件:匿名函数中的内联代码,或者可重用的中间件组件。 两者都在Startup.cs中的Configure()方法中以顺序的方式添加到管道中。

当调用Configure()方法时,就准备好 HTTP 管道并设置它来处理传入的请求。 这些传入请求被传递到添加到管道中的每个中间件中,并由它们执行线程。

在下一篇文章中,我们将学习中间件如何与内联代码一起工作。 中间件的内联代码并不是最佳实践。 更重要的是创建中间件类并使用app.UseMiddleware方法将它们添加到管道中,原因多种多样(可重用性、清晰性等等)。 它们的工作原理是一样的。

中间件

在计算机世界中,中间件是一种软件组件,它将在不同的应用之间以不同的方式交互、互操作、交换和共享数据。 有服务、消息或面向对象的中间件。

ASP.NET Core,中间件将是添加到 HTTP 管道中的处理和组件,由Startup.cs中的Configure()方法表示。

这个中间件使我们能够完全控制替代传统 ASP 的管道.NET 应用生命周期以及所有 HTTP 模块和处理程序。 我们将选择他们将被执行的顺序。

已经有一些内置的中间件,但是我们必须创建自己的,或者创建一些来以更细的粒度管理现有的中间件(授权、身份验证、会话、缓存、日志、路由、异常处理等等)。

HTTP 模块和 HTTP 处理程序

与 HttpHandlers 和 HttpModules 不同,中间件是通过编程方式创建和使用的,无需配置文件。 我们现在不受 IIS 和System.Web的影响。

让我们来看看一些关于HttpHandlersHttpModules的事情:

  • 在到达生成响应的处理程序之前和/或在生成响应之后,为每个请求运行HttpModule
  • 处理程序处理请求并为给定的文件扩展名生成响应

使用内联中间件代码作为匿名方法—使用、运行、映射和映射时

在本教程中,我们将学习如何在Startup.csConfigure()方法中使用内联中间件代码。

准备

我们创建一个空的 ASP.NET Core web 应用与 Visual Studio,使用。NET Core 或。NET Framework。

怎么做……

我们将创建一个新项目,并使用不同类型的中间件:

  1. 首先,让我们使用app.Use:
public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.Use(async (context, next) => 
  { 
    await context.Response.WriteAsync 
    ("First Inline middleware before Handlen"); 
    await next.Invoke(); 
    await context.Response.WriteAsync 
    ("First Inline middleware after Handle"); 
  }); 
  ... 
} 

next.Invoke()将调用管道中的下一个中间件。

我们可以添加任意数量的app.Use方法。 它们将按照添加到管道上的顺序执行。

与 HTTP 模块进行类比,代码执行前等待next.Invoke()BeginRequest方法中的代码在一个类实现IHttpModule,和代码执行后等待next.Invoke()EndRequest方法中的代码。

Use()方法中的匿名函数有两个参数:当前 HTTP 上下文和按约定接下来调用的委托。 此委托用于调用下一个委托。 如果我们不调用app.Run中的next.Invoke()方法,我们就会短路管道,接下来的中间件(内联的、静态的或自定义的)将不会被执行。

  1. 第二,让我们用app.Run:
public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.Use(async (context, next) => 
  { 
    await context.Response.WriteAsync 
    ("First Inline middleware before Handlen");  
    await next.Invoke(); 
    await context.Response.WriteAsync 
    ("First Inline middleware after Handle");  
  }); 

  app.Run(async (context) => 
  { 
    await context.Response.WriteAsync("Run middleware n"); 
  }); 
  ... 
} 

app.Run使管道短路。 管道知道app.Run方法中的代码将是在管道上执行的最后一个中间件代码,并且将生成响应。 即使在代码之后添加了一些中间件(内联或类),它也不会被执行。

  1. 第三,我们使用app.Map:
public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.Map("/oneurlsegment", (appBuilder) => 
  { 
    appBuilder.Use(async (context, next) => 
    { 
      await context.Response.WriteAsync 
      ("Third Inline middleware before Handle n");  
      await next.Invoke(); 
      await context.Response.WriteAsync 
      ("Third Inline middleware after Handle n");  
    }); 
    ... 
  } 
} 

Map方法将对 URL 分支执行一个处理。

  1. 第四,让我们用app.MapWhen:
public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.MapWhen(context =>  
  context.Request.Query.ContainsKey("q1"), (appBuilder) => 
  { 
    appBuilder.Use(async (context, next) => 
    { 
      await context.Response.WriteAsync 
      ("Fourth Inline middleware before Handle n ");  
      await next.Invoke(); 
      await context.Response.WriteAsync 
      ("Fourth Inline middleware after Handle n ");  
    }); 
    ... 
  } 
} 

方法将执行条件代码检查请求参数。

创建一个可重用的中间件组件

在这个食谱中,我们将学习如何使用环境、脚本和链接标签助手。

准备

我们创建一个空的 ASP.NET Core web 应用与 Visual Studio 和。NET Core 或。NET Framework。

怎么做……

我们将创建一个新项目并编写第一个中间件。 通过这个项目,我们可以观察中间件的执行机制。

  1. 首先,让我们创建一个middleware类:
public class MyMiddleware1 
{ 
  private readonly RequestDelegate _next; 

  public MyMiddleware1(RequestDelegate next) 
  { 
    _next = next; 
  } 

  public async Task Invoke(HttpContext httpContext) 
  { 
    await httpContext.Response.WriteAsync 
    ("Hello from first middleware before Request n"); 
    await _next(httpContext); 
    await httpContext.Response.WriteAsync 
    ("Hello from first middleware after Request n"); 
  } 
} 

一个middleware类不继承任何类或接口,但必须遵守一些规则:

  1. 现在我们通过app.UseMiddleware<MyMiddleware>()方法将其添加到管道中:
public class Startup 
{ 
  ... 
  public void Configure 
  (IApplicationBuilder app, IHostingEnvironment env) 
  { 
    app.UseMiddleware<MyMiddleware1>(); 
  } 
  ... 
} 
  1. 然而,更优雅的方法是为中间件创建一个extension方法:
public static class MiddlewareExtensions 
{ 
  public static IApplicationBuilder UseMiddleware1 
  (this IApplicationBuilder builder) 
  { 
    return builder.UseMiddleware<MyMiddleware1>(); 
  } 
} 

将它添加到管道中,替换之前的语法:

public class Startup 
{ 
  ... 
  public void Configure 
  (IApplicationBuilder app, IHostingEnvironment env) 
  { 
    //app.UseMiddleware<MyMiddleware1>(); 
    app.UseMiddleware1(); 
  } 
  ... 
} 
  1. 在我们创建的这个可重用中间件中,我们可以通过Startup.csConfigureServices方法将依赖注入注入的任何服务或类添加到中间件构造函数中:
public class Product 
{ 
  public int Id { get; set; } 
  public string Name { get; set; } 
} 

public interface IDataRepository 
{ 
  List<Product> GetAll(); 
} 

public class ProductRepository : IDataRepository 
{ 
  public List<Product> GetPromotedProducts() 
  { 
    ... 
  } 
} 

public class StoredDataMiddleware 
{ 
  RequestDelegate _next; 
  IMemoryCache _cache; 
  IRepository _repo; 

  public StoredDataMiddleware(RequestDelegate next,  
  IMemoryCache cache, IRepository repo) 
  { 
    _next = next; 
    _cache = cache; 
    _repo = repo; 
  } 

  public async Task Invoke(HttpContext context) 
  { 
    var products = _repo.GetPromotedProducts(); 
    _cache.Set<List<Product>>("PromotedProducts", products); 
    await context.Response.WriteAsync 
    ("I put in cache datas in a middleware n"); 
    await _next.Invoke(context); 
  } 
} 

在本例中,我们通过中间件在缓存中存储一个产品列表。

  1. 最后,我们来看看如何配置Startup.cs:
public class Startup 
{ 
  ... 
  public void ConfigureServices(IServiceCollection services) 
  { 
    services.AddMemoryCache(); 
    services.AddScoped<IDataRepository, ProductRepository>(); 
  } 

  public void Configure(IApplicationBuilder app,  
  IHostingEnvironment env, IMemoryCache cache) 
  { 
    app.UseStoredDataMiddleware(); 
    //app.UseMiddleware<MyMiddleware2>(); 
  } 
  ... 
} 

将 HTTP 处理程序迁移到中间件

在这个食谱中,我们将学习如何转换一个 ASP。 asp.net HTTP 处理程序.NET Core 的中间件。

在开始之前,让我们回顾一下什么是 HTTP 处理程序。 HTTP 处理程序处理来自 ASP 的传入请求.NET/IIS 管道,并为给定的扩展文件(.aspx.html.jpg,等等)生成响应。 ASP.NET MVC 中,我们使用了一个名为MVCHandler的特定处理程序,它确保在请求 URL 中指定路由的控制器中存在action方法。

准备

我们创建一个空的 ASP.NET Core web 应用与 Visual Studio 和。NET Core 或。NET Framework。

怎么做……

我们将创建一个新项目并编写第一个中间件。 通过这个项目,我们可以观察中间件的执行机制。

  1. 首先,让我们看一下 HTTP 处理程序的结构:
public class MyHttpHandler : IHttpHandler 
{ 
  public bool IsReusable {  get {  return false;   }  } 

  public void ProcessRequest(HttpContext context) 
  { 
    context.Response.Write("This page pass by MyHttpHandler <br />"); 
  } 
} 
  1. 现在,让我们看看Web.config配置:
<system.webServer> 
    <handlers> 
      <add name="MyReportHandler" verb="*" path="*.report" type="MyApp.HttpHandlers.MyReportHandler" resourceType="Unspecified" /> 
    </handlers> 
</system.webServer> 

我们必须指定名称、此处理程序的授权 HTTP 谓词、路径(即已命名的扩展文件)和类型(即名称空间的处理程序)。

  1. 在 ASP 中等效的代码.NET Core 可能如下:首先,我们创建与处理程序相对应的中间件。 我们可以通过四种方式做到这一点:
public class MyReportMiddleware 
{ 
  private readonly RequestDelegate _next; 

  public MyReportMiddleware(RequestDelegate next) 
  { 
    _next = next; 
  } 

  public Task Invoke(HttpContext httpContext) 
  { 
    return httpContext.Response.WriteAsync("this .report file will be           executed and short circuit the pipeline"); 
    // we doesn't call next because we want to short circuit the  
    // pipeline in order to act as a classic HttpHandler 
    //return _next(httpContext); 
  } 
} 

// Extension method used to add the middleware  
// to the HTTP request pipeline. 
public static class MyReportMiddlewareExtensions 
{ 
  public static IApplicationBuilder UseMyReportMiddleware 
  (this IApplicationBuilder builder) 
  { 
    return builder.UseMiddleware<MyReportMiddleware>(); 
  } 
} 

public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.MapWhen( 
  context =>  
  context.Request.Path.ToString().EndsWith(".report"),  
  appBranch =>  
  { 
     appBranch.UseMyReportMiddleware(); 
  }); 
  ... 
} 
private static void HandleBranchForReport 
(IApplicationBuilder app) 
{ 
  app.Run(async context => 
  { 
    await context.Response.WriteAsync 
    ("this .report file will be executed and short circuit the pipeline"); 
  }); 
} 

public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.MapWhen( 
  context =>  
  context.Request.Path.ToString().EndsWith(".report"),  
  HandleBranchForReport); 
  ... 
} 
public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.MapWhen( 
  context => 
  context.Request.Path.ToString().EndsWith(".report"), 
  appBranch =>  
  { 
    appBranch.Run(async (context) => 
    { 
      await context.Response.WriteAsync 
      ("this .report file will be executed and short circuit the pipeline"); 
    });   
  });  
  ... 
} 
// Middleware class 
public class MySecondReportMiddleware 
{ 
  private readonly RequestDelegate _next; 

  public MySecondReportMiddleware(RequestDelegate next) 
  { 
    _next = next; 
  } 

  public Task Invoke(HttpContext httpContext) 
  { 
    if(httpContext.Request.Path.ToString() 
    .EndsWith(".report")) 
    return httpContext.Response.WriteAsync 
    ("this .report file will be executed and  
    short circuit the pipeline"); 
    else 
    return _next(httpContext); 
  } 
} 

// Extension method used to add the middleware  
// to the HTTP request pipeline. 
public static class MySecondReportMiddlewareExtensions 
{ 
  public static IApplicationBuilder UseMySecondReportMiddleware 
  (this IApplicationBuilder builder) 
  { 
    return builder.UseMiddleware<MySecondReportMiddleware>(); 
  } 
} 

Startup.cs文件:

public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.UseMySecondReportMiddleware(); 
  ... 
} 

将 HTTP 模块迁移到中间件

在这个食谱中,我们将学习如何转换一个 ASP。 asp.net HTTP 模块.NET Core 的中间件。

在开始之前,让我们温习一下什么是 HTTP 处理程序。 HTTP 模块可以为来自 ASP 的传入请求添加一个处理.NET/IIS 管道,在它到达处理程序之前,并/或向生成的响应添加处理。 它甚至可以转换或生成自己的响应。

准备

我们创建一个空的 ASP.NET Core web 应用与 Visual Studio 和。NET Core 或。NET Framework。

怎么做……

我们将创建一个新的 HTTP 模块,并跟踪请求的起始/结束点。 我们还将在响应的开始和结束处添加信息。

  1. 首先,让我们看看 HTTP 模块的结构:
public class MyHttpModule : IHttpModule 
{ 
  public void Dispose(){} 

  public void Init(HttpApplication context) 
  { 
    context.BeginRequest += (source, args) => 
    { 
      context.Response.Write("MyHttpModule BeginRequest"); 
    }; 

    context.EndRequest += (source, args) => 
    { 
      context.Response.Write("MyHttpModule EndRequest"); 
    }; 
  } 
} 

HttpModule 必须在Web.config文件中配置如下:

<system.webServer> 
    <modules> 
      <add name="myModule" type="MyApp.HttpModules.MyHttpModule"/> 
    </modules> 
</system.webServer>
  1. 现在,让我们看看在 ASP 中等效的代码.NET 的核心。 对该模块进行转换,使其与 asp.net 兼容.NET core,我们可以创建自定义中间件并调用它,或者创建内联中间件:
// Middleware class 
public class MyModuleMiddleware 
{ 
  private readonly RequestDelegate _next; 

  public MyModuleMiddleware(RequestDelegate next) 
  { 
    _next = next; 
  } 

  public async Task Invoke(HttpContext httpContext) 
  { 
    await httpContext.Response.WriteAsync 
    ("MyHttpModule BeginRequest"); 
    await _next(httpContext); 
    await httpContext.Response.WriteAsync 
    ("MyHttpModule EndRequest"); 
  } 
} 

// Extension method used to add the middleware  
// to the HTTP request pipeline. 
public static class MyModuleMiddlewareExtensions 
{ 
  public static IApplicationBuilder UseMyModuleMiddleware 
  (this IApplicationBuilder builder) 
  { 
    return builder.UseMiddleware<MyModuleMiddleware>(); 
  } 
} 

Startup.cs:

public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.UseMyModuleMiddleware (); 
  ... 
} 
public void Configure(IApplicationBuilder app) 
{ 
  ... 
  app.Use(async (context, next) => 
  { 
    // This code is equivalent to call the BeginRequest  
    // delegate in a HttpModule 
    await context.Response.WriteAsync 
    ("MyHttpModule BeginRequest"); 
    await next.Invoke(); 
    // This code is equivalent to call the EndRequest  
    // delegate in a HttpModule 
    await context.Response.WriteAsync 
    ("MyHttpModule EndRequest"); 
  }); 
  ... 
}