iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 26
1

ASP.NET Core MVC 跟 ASP.NET MVC 一樣也提供了 Filter 的功能,以 AOP 的觀念在執行 Action 的前後處理資料。第一次看到 Middleware 的時候有想說阿這不是跟 Filter 一樣嗎,但其實兩者的執行順序是有差別的。Middleware 是 ASP.NET Core 應用程式對 HTTP 請求跟回應的處理,如果應用程式剛好有加入 MVC 的中介層,在 ASP.NET Core MVC 的 Action 執行前後就會有不同的 Filter 來處理請求跟回應。說這麼多不如直接看張圖,藍色虛線框框內的才是 MVC 的 Filter pipeline,框框上方的則是 Middleware pipeline:
https://ithelp.ithome.com.tw/upload/images/20181109/20107875ClaOhSLkBD.png

圖片來源:https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters

不同類型的 Filter

ASP.NET Core MVC 框架中的 Filter 有幾種類型 (Stage),會依照順序來執行,不像 Middleware 可以這麼自由發揮。

  • Authorization Filter:最先執行,用來驗證請求是否合法,若否就會跳過後面的步驟(直接回傳 401 之類的)。
  • Resource Filter:執行時間在 Authorization 之後和剩下的 Filters 都執行完之後。通常用來處理快取或模型繫結。
  • Action Filter:在 Action 前後執行,通常用來處理傳入 Action 的參數和回應的結果。
  • Exception Filter:處理應用程式中沒有被處理 (catch) 的例外。
  • Result Filter:應用程式執行無誤後就會執行。

所有 Filter 的執行流程大概是這樣:
https://ithelp.ithome.com.tw/upload/images/20181109/20107875Ei58bFmHTL.png

圖片來源依然是:https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters

實作 Filters

不同類型的 Filter 需要實作個別的介面,實作各自的 On{Stage} 方法,都有分為同步與非同步的方式:

  • Authorization Filter
public class AuthorizationFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        // 如果驗證失敗,可以讓 pipeline 短路
        // context.Result = new UnauthorizedResult();

        context.HttpContext.Response.WriteAsync("Executing AuthorizationFilter. \r\n");
    }
}

public class AsyncAuthorazationFilter : IAsyncAuthorizationFilter
{
    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        await context.HttpContext.Response.WriteAsync("Executing AsyncAuthorizationFilter. \r\n");
    }
}
  • Resource Filter
public class ResourceFilter : IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.HttpContext.Response.WriteAsync("Executing ResourceFilter. \r\n");
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
        context.HttpContext.Response.WriteAsync("ResourceFilter is executed. \r\n");
    }
}

public class AsyncResouceFilter : IAsyncResourceFilter
{
    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        await context.HttpContext.Response.WriteAsync("Executing AsyncResouceFilter. \r\n");

        await next();

        await context.HttpContext.Response.WriteAsync("AsyncResouceFilter is executed. \r\n");
    }
}
  • Action Filter
public class ActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        context.HttpContext.Response.WriteAsync("Executing AsyncActionFilter. \r\n");
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        context.HttpContext.Response.WriteAsync("ActionFilter is executed. \r\n");
    }
}

public class AsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        await context.HttpContext.Response.WriteAsync("AsyncActionFilter AsyncActionFilter. \r\n");

        await next();

        await context.HttpContext.Response.WriteAsync("AsyncActionFilter is executed. \r\n");
    }
}
  • Exception Filter
public class ExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        context.HttpContext.Response.WriteAsync("Executing ExceptionFilter. \r\n");
    }
}

public class AsyncExceptionFilter : IAsyncExceptionFilter
{
    public async Task OnExceptionAsync(ExceptionContext context)
    {
        await context.HttpContext.Response.WriteAsync("AsyncExceptionFilter ExceptionFilter. \r\n");
    }
}
  • Result Filter
public class ResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.WriteAsync("Executing ResultFilter. \r\n");
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        context.HttpContext.Response.WriteAsync("ResultFilter is executed. \r\n");
    }
}

public class AsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
    {
        await context.HttpContext.Response.WriteAsync("AsyncActionFilter AsyncResultFilter. \r\n");

        await next();

        await context.HttpContext.Response.WriteAsync("AsyncResultFilter is executed. \r\n");
    }
}

一個自訂的 Filter 類別也可以同時實作多種不同 {Stage}Filter

註冊 Filter

要把 Filter 加進 MVC 執行過程中有兩種方式:

  1. Startup.ConfigureServices 加入 MVC 服務時註冊,這邊註冊的 Filter 會影響到所有的請求跟回應:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
        {
            options.Filters.Add(new ResourceFilter());
            options.Filters.Add<ExceptionFilter>();
            options.Filters.Add<ResultFilter>();
        });
}
  1. 在需要使用 FilterControllerAction 註冊。可以用 [TypeFilter(Type)] 註冊,或者讓原本的 ActionFilter 額外繼承 Attribute
[TypeFilter(typeof(AuthorizationFilter))]
public class FiltersController : Controller
{
    [ActionFilter]
    public IActionResult Index()
    {
        return Content("ASP.NET Core MVC Filters Example");
    }
}

public class ActionFilter : ActionFilterAttribute
// or this
// public class ActionFilter : Attribute, IActionFilter
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        context.HttpContext.Response.WriteAsync("Executing AsyncActionFilter. \r\n");
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        context.HttpContext.Response.WriteAsync("ActionFilter is executed. \r\n");
    }
}

執行結果:
https://ithelp.ithome.com.tw/upload/images/20181109/20107875ZFcl0xUQNW.png

執行順序

Filter 執行順序除了依上面看到五種 Stage 以先進後出的順序執行外,還會依 Global > Controller > Action 的註冊順序來執行。

參考資料


上一篇
Tag Helpers 標籤輔助程式
下一篇
Areas 區域
系列文
.Net Core 網站開發 10131

尚未有邦友留言

立即登入留言