之前ASP.NET中使用的HTTP Modules及HTTP Handlers,在ASP.NET Core中已不復存在,取而代之的是Middleware。Middleware除了簡化了HTTP Modules/Handlers的使用方式,還帶入了Pipeline的概念。本篇將介紹ASP.NET Co ...
之前ASP.NET中使用的HTTP Modules及HTTP Handlers,在ASP.NET Core中已不復存在,取而代之的是Middleware。Middleware除了簡化了HTTP Modules/Handlers的使用方式,還帶入了Pipeline的概念。
本篇將介紹ASP.NET Core的Middleware概念及用法。
Middleware 概念
ASP.NET Core在Middleware的官方說明中,使用了Pipeline這個名詞,意指Middleware像水管一樣可以串聯在一起,所有的Request及Response都會層層經過這些水管。
用圖例可以很容易理解,如下圖:
App.Use
Middleware的註冊方式是在Startup.cs的Configure
對IApplicationBuilder
使用Use
方法註冊。
大部分擴展的Middleware也都是以Use開頭的方法註冊,例如:
- UseMvc():MVC的Middleware
- UseRewriter():URL rewriting的Middleware
一個簡單的Middleware 範例。如下:
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace MyWebsite
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("First Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("First Middleware out. \r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Second Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("Second Middleware out. \r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Third Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("Third Middleware out. \r\n");
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World! \r\n");
});
}
}
}
用瀏覽器打開網站任意連結,輸出結果:
First Middleware in.
Second Middleware in.
Third Middleware in.
Hello World!
Third Middleware out.
Second Middleware out.
First Middleware out.
在Pipeline的概念中,註冊順序是很重要的事情。請求經過的順序一定是先進後出。
Request 流程如下圖:
Middleware 也可以作為攔截使用,如下:
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace MyWebsite
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("First Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("First Middleware out. \r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Second Middleware in. \r\n");
// 水管阻塞,封包不往後送
var condition = false;
if (condition)
{
await next.Invoke();
}
await context.Response.WriteAsync("Second Middleware out. \r\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Third Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("Third Middleware out. \r\n");
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World! \r\n");
});
}
}
}
輸出結果:
First Middleware in.
Second Middleware in.
Second Middleware out.
First Middleware out.
在Second Middleware 中,因為沒有達成條件,所以封包也就不在往後面的水管傳送。流程如圖:
App.Run
Run
是Middleware的最後一個行為,以上面圖例來說,就是最末端的Action。
它不像Use
能串聯其他Middleware,但Run
還是能完整的使用Request及Response。
App.Map
Map
是能用來處理一些簡單路由的Middleware,可依照不同的URL指向不同的Run
及註冊不同的Use
。
新增一個路由如下:
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace MyWebsite
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("First Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("First Middleware out. \r\n");
});
// app.Use(async (context, next) =>
// {
// await context.Response.WriteAsync("Second Middleware in. \r\n");
// // 水管阻塞,封包不往後送
// var condition = false;
// if (condition)
// {
// await next.Invoke();
// }
// await context.Response.WriteAsync("Second Middleware out. \r\n");
// });
app.Map("/second", mapApp =>
{
mapApp.Use(async (context, next) =>
{
await context.Response.WriteAsync("Second Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("Second Middleware out. \r\n");
});
mapApp.Run(async context =>
{
await context.Response.WriteAsync("Second. \r\n");
});
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Third Middleware in. \r\n");
await next.Invoke();
await context.Response.WriteAsync("Third Middleware out. \r\n");
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World! \r\n");
});
}
}
}
開啟網站任意連結,會顯示:
First Middleware in.
Third Middleware in.
Hello World!
Third Middleware out.
First Middleware out.
開啟網站http://localhost:5000/second
,則會顯示:
First Middleware in.
Second Middleware in.
Second.
Second Middleware out.
First Middleware out.
創建Middleware 類
如果Middleware全部都寫在Startup.cs,代碼將很難維護,所以應該把自定義的Middleware邏輯獨立出來。
建立Middleware類不需要額外繼承其它類或介面,一般的類即可,例子如下:
FirstMiddleware.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace MyWebsite
{
public class FirstMiddleware
{
private readonly RequestDelegate _next;
public FirstMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await context.Response.WriteAsync($"{nameof(FirstMiddleware)} in. \r\n");
await _next(context);
await context.Response.WriteAsync($"{nameof(FirstMiddleware)} out. \r\n");
}
}
}
全局註冊
在Startup.Configure
註冊Middleware就可以套用到所有的Request。如下:
Startup.cs
// ...
public class Startup
{
// ...
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<FirstMiddleware>();
// ...
}
}
局部註冊
Middleware 也可以只套用在特定的Controller 或Action。註冊方式如下:
Controllers\HomeController.cs
// ..
[MiddlewareFilter(typeof(FirstMiddleware))]
public class HomeController : Controller
{
// ...
[MiddlewareFilter(typeof(SecondMiddleware))]
public IActionResult Index()
{
// ...
}
}
Extensions
大部分擴展的Middleware都會用一個靜態方法包裝,如:UseMvc()
、UseRewriter()
等。
自定義的Middleware當然也可以透過靜態方法包,範例如下:
Extensions\CustomMiddlewareExtensions.cs
using Microsoft.AspNetCore.Builder;
namespace MyWebsite
{
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseFirstMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<FirstMiddleware>();
}
}
}
註冊Extension Middleware 的方式如下:
Startup.cs
// ...
public class Startup
{
// ...
public void Configure(IApplicationBuilder app)
{
app.UseFirstMiddleware();
// ...
}
}
參考
ASP.NET Core Middleware Fundamentals
Creating Custom Middleware In ASP.Net Core
老司機發車啦:https://github.com/SnailDev/SnailDev.NETCore2Learning