本文转自:https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/

One of the new features from ASP.NET Core 1.0 is the idea of Middleware. Middleware are components of an application that examine the requests responses coming in to and going out from an ASP.NET Core application. It's a long-overdue piece of customization for ASP.NET apps that gives us developers total control over the HTTP pipeline. It might even help you avoid endless questions from annoying coworkers (maybe!).

As part of my ongoing learning process about ASP.NET Core, I decided to build a sample application that has a few pieces of middleware, so that I could play around with order, tasks, and so forth. It helped me understand what Middleware does, and why it might be important, so hopefully it'll help you too. Let's get started!

NOTE: This post was written using RC1 (Release Candidate 1) of ASP.NET Core, and as the framework gets closer to final release, some things may change. If they do, I'll do my best to keep this post up-to-date. If you notice anything that needs to be updated, let me know in the comments.

What Is Middleware?

Middleware, as explained above, are components that "sit" on the HTTP pipeline and examine requests and responses. This means that they are effectively "gateway" classes that can decide whether or not to continue allowing the request to be processed by other Middleware components further down the stream. The pipeline looks like this:

Image is © Microsoft, taken from docs.asp.net

The idea of Middleware, at least in ASP.NET, comes from the Open Web Interface for .NET (OWIN) project. OWIN's stated goal is to

...decouple server and application, encourage the development of simple modules for .NET web development, and, by being an open standard, stimulate the open source ecosystem of .NET web development tools.

Considering their ideas have been fully integrated into ASP.NET Core 1.0, I'd say they've more than achieved this goal.

At any rate, Middleware defines a system by which we can exert complete control over the HTTP pipeline into and out of our apps. It effectively replaces the functionality that was formerly covered by things like HttpModules and HttpHandlers.

Default Middleware

In some default ASP.NET Core 1.0 apps, particularly the ones created by Visual Studio, you are already using Middleware. Here's a sample from my earlier post covering the Startup.cs file in ASP.NET Core 1.0:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug(); if (env.IsDevelopment())
{
app.UseBrowserLink(); //Middleware
app.UseDeveloperExceptionPage(); //Middleware
}
else
{
app.UseExceptionHandler("/Home/Error"); //Middleware
} app.UseIISPlatformHandler(); //Middleware app.UseStaticFiles(); //Middleware app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}); //Middleware
}

In this method, any time you see app.UseX(), you are using some of ASP.NET's default Middleware components. The concept of Middleware and the HTTP pipeline is central to how ASP.NET Core apps are developed. You can check out the other built-in Middleware by reading the docs.

Defining a Custom Middleware Component

A Middleware component, in an ASP.NET Core project, is a class like any other. The difference is that the Middleware component needs to have a private property of type RequestDelegate, like so:

public class AuthorizationMiddleware
{
private readonly RequestDelegate _next; public AuthorizationMiddleware(RequestDelegate next)
{
_next = next;
}
}

The _next property represents a delegate for the next component in the pipeline. Each component must also implement an async task called Invoke:

public async Task Invoke(HttpContext context)
{
await _next.Invoke(context);
}

At the moment, this method does nothing other than call the next Middleware component in the pipeline. Let's see a simple task that we can do with our new Middleware component.

Middleware Tasks

Since each piece of Middleware can examine the incoming request and the corresponding response, one possible task Middleware could accomplish is that of authorization.

For a (trivial) example, let's say that each request must NOT have a header "X-Not-Authorized", and if it does, the response must be returned immediately with a 401 Unauthorized status code. Our Middleware component's Invoke method would now look like this:

public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Not-Authorized"))
{
context.Response.StatusCode = 401; //Unauthorized
return;
} await _next.Invoke(context);
}

If we call any method in our app with a tool such as Postman, we'll get the following response:

Middleware can accomplish many sorts of things. For example:

  • You might want to examine each incoming request to see if they have requested an image, and if they have, redirect them to an image handler, much like you would when we used HttpHandlers.
  • You might have a component that logs all requests.
  • You might have a component that checks to see if you annoying coworker Doug from down the hall is making a request, and if he is, redirect all his requests to Mary just so you don't have to deal with him anymore.

The possibilities are endless!

Other Example Middleware

For our sample project, we've already got one Middleware component (AuthorizationMiddleware) defined. Now we will define two more. First up is RequestHeaderMiddleware:

public class RequestHeaderMiddleware
{
private readonly RequestDelegate _next; public RequestHeaderMiddleware(RequestDelegate next)
{
_next = next;
} public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Cancel-Request"))
{
context.Response.StatusCode = 500;
return;
} await _next.Invoke(context); if (context.Request.Headers.Keys.Contains("X-Transfer-By"))
{
context.Response.Headers.Add("X-Transfer-Success", "true");
}
}
}

This Middleware component does two things:

  1. If the request contains a header called "X-Cancel-Request" then the server returns 500 Internal Server Error as the response.
  2. If the request contains a header called "X-Transfer-By" then the server adds a header to the response called "X-Transfer-Success".

Finally, let's add a third piece of middleware, called ProcessingTimeMiddleware:

public class ProcessingTimeMiddleware
{
private readonly RequestDelegate _next; public ProcessingTimeMiddleware(RequestDelegate next)
{
_next = next;
} public async Task Invoke(HttpContext context)
{
var watch = new Stopwatch();
watch.Start(); await _next(context); context.Response.Headers.Add("X-Processing-Time-Milliseconds", new[] { watch.ElapsedMilliseconds.ToString() });
}
}

This component starts an instance of Stopwatch to record how long each request takes, then returns a header called "X-Processing-Time-Milliseconds" with that time in the response.

Now that we've got all the Middleware components we're going to use, let's see how we can insert them into our ASP.NET Core 1.0 app's pipeline!

Adding Middleware to the HTTP Pipeline

The environment for an ASP.NET Core 1.0 is set up in that app's Startup.cs file. In order to use our newly-created Middleware, we need to register them with the environment created by Startup.

There are two ways we can register the Middleware in the pipeline. One way is to go to the Startup file's Configure method and call a method on the IApplicationBuilder interface (simplified):

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseMiddleware<AuthorizationMiddleware>();
...
}

Another way, and the way we're going to use, is to create extension methods for each piece of Middleware you want to register, and then call those methods from the Configure() method. Here's our Extensions class:

public static class MiddlewareExtensions
{
public static IApplicationBuilder UseRequestHeaderMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestHeaderMiddleware>();
} public static IApplicationBuilder UseAuthorizationMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<AuthorizationMiddleware>();
} public static IApplicationBuilder UseProcessingTimeMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProcessingTimeMiddleware>();
}
}

Now we can call these extensions in Startup like so (the below code contains a bug, read it carefully):

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug(); if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
} app.UseIISPlatformHandler();
app.UseStaticFiles();
app.UseProcessingTimeMiddleware();
app.UseRequestHeaderMiddleware();
app.UseAuthorizationMiddleware(); app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}

Did you spot it?

When you are registering Middleware with your environment, the order in which you add the Middleware components is very important. In the above code, we add ProcessingTimeMiddleware first, then RequestHeaderMiddleware, then AuthorizationMiddleware. This is the exact opposite order of what we should be using. The correct order looks like this (simplified):

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseAuthorizationMiddleware();
app.UseRequestHeaderMiddleware();
app.UseProcessingTimeMiddleware();
...
}

Testing With Postman

Now that we've got our Middleware components in place, let's see some example request/response calls to a controller action in our app. The controller action we're using is very simple:

[HttpGet]
public IActionResult TestMiddleware()
{
return Ok();
}

NOTE: Don't forget that, in ASP.NET Core 1.0 and MVC 6, MVC and Web API use the same stack.

Now let's make some Postman calls. First, let's make a call with no headers at all.

This is what we would expect to see.  Neither the Authorization nor the RequestHeader Middleware components prevents execution, so the ProcessingTime component added the header and we got back the Action's 200 OK response.

Now let's see what happens if we add in the X-Not-Authorized header:

Great!  The AuthorizationMiddleware component returned 401 Unauthorized, and because there's no "X-Processing-Time-Milliseconds" header isn't included in the response, the ProcessingTimeMiddleware component didn't fire.  This is what we expect.

I'm sure you see where this is going by now.  For the last test, let's see a request without "X-Not-Authorized" header but including "X-Transfer-By":

In this case, we see that both "X-Processing-Time-Milliseconds" and "X-Transfer-Success" returned, which means that both RequestHeaderMiddleware and ProcessingTimeMiddleware successfully fired.  Looks like our scenarios work!

Let's try one more request.

Perfect!

Summary

Remember these three major points about Middleware in ASP.NET Core 1.0:

  1. Middleware components allow you complete control over the HTTP pipeline in your app.
  2. Among other things, Middleware components can handle authorization, redirects, headers, and even cancel execution of the request.
  3. The order in which Middleware components are registered in the Startup file is very important.

You can see the sample code for this project in the GitHub repository.  As always, let me know what you think about this post in the comments, particularly if I missed something important.

Happy Coding!

Matthew Jones's Picture

[转]Writing Custom Middleware in ASP.NET Core 1.0的更多相关文章

  1. [译]Writing Custom Middleware in ASP.NET Core 1.0

    原文: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ Middleware是ASP. ...

  2. ASP.NET Core 3.0 自动挡换手动挡:在 Middleware 中执行 Controller Action

    最近由于发现奇怪的 System.Data.SqlClient 性能问题(详见之前的博文),被迫提前了向 .NET Core 3.0 的升级工作(3.0 Preview 5 中问题已被修复).郁闷的是 ...

  3. ASP.NET Core 1.0中的管道-中间件模式

    ASP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline).日志记录.用户认证.MVC等模块都以中间件(Middleware)的方式注册在管道中.显而易见这样的设计非常松耦合 ...

  4. 初识ASP.NET Core 1.0

    本文将对微软下一代ASP.NET框架做个概括性介绍,方便大家进一步熟悉该框架. 在介绍ASP.NET Core 1.0之前有必要澄清一些产品名称及版本号.ASP.NET Core1.0是微软下一代AS ...

  5. ASP.NET Core 1.0 静态文件、路由、自定义中间件、身份验证简介

    概述 ASP.NET Core 1.0是ASP.NET的一个重要的重新设计. 例如,在ASP.NET Core中,使用Middleware编写请求管道. ASP.NET Core中间件对HttpCon ...

  6. 从头编写 asp.net core 2.0 web api 基础框架 (1)

    工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...

  7. 【转载】从头编写 asp.net core 2.0 web api 基础框架 (1)

    工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...

  8. ASP.NET Core 2.0 : 三. 项目结构

    本章我们一起来对比着ASP.NET Framework版本看一下ASP.NET Core 2.0的项目结构.(此后的文章也尽量这样对比着, 方便学习理解.) 关注差异, 也为项目迁移做准备. 新建项目 ...

  9. ASP.NET Core 2.0中如何更改Http请求的maxAllowedContentLength最大值

    Web.config中的maxAllowedContentLength这个属性可以用来设置Http的Post类型请求可以提交的最大数据量,超过这个数据量的Http请求ASP.NET Core会拒绝并报 ...

随机推荐

  1. 理清JavaScript正则表达式--下篇

    紧接:"理清JavaScript正则表达式--上篇". 正则在String类中的应用 类String支持四种利用正则表达式的方法.分别是search.replace.match和s ...

  2. Log4net入门(控制台篇)

    Log4net是Apache公司的log4j™的.NET版本,用于帮助.NET开发人员将日志信息输出到各种不同的输出源(Appender),常见的输出源包括控制台.日志文件和数据库等.本篇主要讨论如何 ...

  3. abstract与interface之房祖名张默版

    最近把java基础知识拿出来看看,看到abstract与interface的时候,觉得有点模糊,好像面试官也喜欢问这个问题.我在百度了查了好长时间,觉得讲算比较清楚的是那篇讲 Door,然后想要带个报 ...

  4. C# 复制幻灯片(包括格式、背景、图片等)到同/另一个PPT文档

    C# 复制幻灯片(包括格式.背景.图片等)到同/另一个PPT文档 复制幻灯片是使用PowerPoint过程中的一个比较常见的操作,在复制一张幻灯片时一般有以下两种情况: 在同一个PPT文档内复制 从一 ...

  5. 推荐13款javascript模板引擎

    javaScript 在生成各种页面内容时如果能结合一些模板技术,可以让逻辑和数据之间更加清晰,本文介绍 X 款 JavaScript 的模板引擎.(排名不分先后顺序) 1. Mustache 基于j ...

  6. SQL Tuning 基础概述05 - Oracle 索引类型及介绍

    一.B-Tree索引 三大特点:高度较低.存储列值.结构有序 1.1利用索引特性进行优化 外键上建立索引:不但可以提升查询效率,而且可以有效避免锁的竞争(外键所在表delete记录未提交,主键所在表会 ...

  7. 安装MYSQL详细教程 版本:mysql-installer-community-5.7.16.0 免安装版本和安装版本出现错误的解决

    一.版本的选择 之前安装的Mysql,现在才来总结,好像有点晚,后台换系统了,现在从新装上Mysql,感觉好多坑,我是来踩坑,大家看到坑就别跳了,这样可以省点安装时间,这个折腾了两天,安装了好多个版本 ...

  8. scrapy cookies:将cookies保存到文件以及从文件加载cookies

    我在使用scrapy模拟登录新浪微博时,想将登录成功后的cookies保存到本地,下次加载它实现直接登录,省去中间一系列的请求和POST等.关于如何从本次请求中获取并在下次请求中附带上cookies的 ...

  9. 导出BOM表

    1.Report->Bill of Materials for Project 将Value拖上左上角的Grouped Columns 2.在Excel表中全选器件,右键设置"设置单元 ...

  10. .NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用

    在我们开发的很多分布式项目里面(如基于WCF服务.Web API服务方式),由于数据提供涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发式增长,如果数据库服务器 ...