Filter是延續ASP.NET MVC的產物,同樣保留了五種的Filter,分別是Authorization Filter、Resource Filter、Action Filter、Exception Filter及Result Filter。通過不同的Filter可以有效處理封包進出的加工,本 ...
Filter是延續ASP.NET MVC的產物,同樣保留了五種的Filter,分別是Authorization Filter、Resource Filter、Action Filter、Exception Filter及Result Filter。
通過不同的Filter可以有效處理封包進出的加工,本篇將介紹ASP.NET Core的五種Filter運作方式。
Filter 介紹
Filter的作用是在Action 執行前或執行後做一些加工處理。
某種程度來看,會跟Middleware很像,但執行的順序略有不同,用對Filter不僅可以減少代碼,還可以提高執行效率。
ASP.NET Core 有以下五種Filter 可以使用:
- Authorization Filter
Authorization是五種Filter中優先順序最高的,通常用於驗證Request合不合法,不合法後面就直接跳過。 - Resource Filter
Resource是第二優先,會在Authorization之後,Model Binding之前執行。通常會是需要對Model加工處理才用。 - Action Filter
最常使用的Filter,封包進出都會經過它,使用上沒什麼需要特別註意的。跟Resource Filter很類似,但並不會經過Model Binding。 - Exception Filter
異常處理的Filter。 - Result Filter
當Action完成後,最終會經過的Filter。
Filter 運作方式
ASP.NET Core的每個Request都會先經過已註冊的Middleware接著才會執行Filter,除了會依照上述的順序外,同類型的Filter預設都會以先進後出的方式處里封包。
Response在某些Filter並不會做處理,會直接被pass。Request及Response的運作流程如下圖:
- 黃色箭頭是正常情況流程
- 灰色箭頭是異常處理流程
建立Filter
ASP.NET Core的Filter基本上跟ASP.NET MVC的差不多。
上述的五種Filter範例分別如下:
Authorization Filter
AuthorizationFilter.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyWebsite.Filters
{
public class AuthorizationFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
}
}
}
非同步的方式:
// ...
public class AuthorizationFilter : IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
}
}
Resource Filter
ResourceFilter.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyWebsite.Filters
{
public class ResourceFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n");
}
}
}
非同步的方式:
// ...
public class ResourceFilter : IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
await next();
await context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n");
}
}
Action Filter
ActionFilter.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyWebsite.Filters
{
public class ActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
}
public void OnActionExecuted(ActionExecutedContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n");
}
}
}
非同步的方式:
// ...
public class ActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
await next();
await context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n");
}
}
Result Filter
ResultFilter.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyWebsite.Filters
{
public class ResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
}
public void OnResultExecuted(ResultExecutedContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n");
}
}
}
非同步的方式:
// ...
public class ResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
await next();
await context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n");
}
}
Exception Filter
ExceptionFilter.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyWebsite.Filters
{
public class ExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
context.ExceptionHandled = true; // 表明異常已處理,客戶端可得到正常返回
context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
}
}
}
非同步的方式:
// ...
public class ExceptionFilter : IAsyncExceptionFilter
{
public Task OnExceptionAsync(ExceptionContext context)
{
context.ExceptionHandled = true;// 表明異常已處理,客戶端可得到正常返回
context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n");
return Task.CompletedTask;
}
}
註冊Filter
Filter有兩種註冊方式,一種是全局註冊,另一種是用[Attribute]局部
註冊的方式,只套用在特定的Controller或Action。
全局註冊
在Startup.ConfigureServices
的MVC服務中註冊Filter,這樣就可以套用到所有的Request。如下:
// ...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.Filters.Add(new ResultFilter());
config.Filters.Add(new ExceptionFilter());
config.Filters.Add(new ResourceFilter());
});
}
}
局部註冊
ASP.NET Core在局部註冊Filter的方式跟ASP.NET MVC有一點不一樣,要通過[TypeFilter(type)]
。
在Controller或Action上面加上[TypeFilter(type)]
就可以局部註冊Filter。如下:
// ...
namespace MyWebsite.Controllers
{
[TypeFilter(typeof(AuthorizationFilter))]
public class HomeController : Controller
{
[TypeFilter(typeof(ActionFilter))]
public void Index()
{
Response.WriteAsync("Hello World! \r\n");
}
[TypeFilter(typeof(ActionFilter))]
public void Error()
{
throw new System.Exception("Error");
}
}
}
[TypeFilter(type)]
用起來有點冗長,想要像過去ASP.NET MVC用[Attribute]
註冊Filter的話,只要將Filter繼承Attribute
即可。如下:
public class AuthorizationFilter : Attribute, IAuthorizationFilter
{
// ...
}
public class ActionFilter : Attribute, IActionFilter
{
// ...
}
[Attribute]
註冊就可以改成如下方式:
// ...
namespace MyWebsite.Controllers
{
[AuthorizationFilter]
public class HomeController : Controller
{
[ActionFilter]
public void Index()
{
Response.WriteAsync("Hello World! \r\n");
}
[ActionFilter]
public void Error()
{
throw new System.Exception("Error");
}
}
}
執行結果
http://localhost:5000/Home/Index
輸出結果如下:
AuthorizationFilter in.
ResourceFilter in.
ActionFilter in.
Hello World!
ActionFilter out.
ResultFilter in.
ResultFilter out.
ResourceFilter out.
http://localhost:5000/Home/Error
輸出結果如下:
AuthorizationFilter in.
ResourceFilter in.
ActionFilter in.
ActionFilter out.
ExceptionFilter in.
ResourceFilter out.
執行順序
預設註冊同類型的Filter 是以先進後出的方式處里封包,註冊層級也會影響執行順序。
但也可以通過實現 IOrderedFilter 更改執行順序。例如:
public class ActionFilter : Attribute, IActionFilter, IOrderedFilter
{
public string Name { get; set; }
public int Order { get; set; } = 0;
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name}({Name}) in. \r\n");
}
public void OnActionExecuted(ActionExecutedContext context)
{
context.HttpContext.Response.WriteAsync($"{GetType().Name}({Name}) out. \r\n");
}
}
在註冊Filter 時帶上Order,數值越小優先權越高。
// ...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.Filters.Add(new ActionFilter() { Name = "Global", Order = 3 });
});
}
}
// ...
namespace MyWebsite.Controllers
{
[ActionFilter(Name = "Controller", Order = 2)]
public class HomeController : Controller
{
[ActionFilter(Name = "Action", Order = 1)]
public void Index()
{
Response.WriteAsync("Hello World! \r\n");
}
}
}
變更執行順序後的輸出內容:
ActionFilter(Action) in.
ActionFilter(Controller) in.
ActionFilter(Global) in.
Hello World!
ActionFilter(Global) out.
ActionFilter(Controller) out.
ActionFilter(Action) out.
參考
老司機發車啦:https://github.com/SnailDev/SnailDev.NETCore2Learning