天天看點

ASP.NET Core 2 學習筆記(十四)Filters

原文: ASP.NET Core 2 學習筆記(十四)Filters

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的運作流程如下圖:

ASP.NET Core 2 學習筆記(十四)Filters
  • 黃色箭頭是正常情況流程
  • 灰色箭頭是異常處理流程

建立Filter

ASP.NET Core的Filter基本上跟ASP.NET MVC的差不多。

上述的五種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");
    }
}
      

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");
    }
}
      

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");
    }
}
      

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");
    }
}
      

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 是以先進後出的方式處裡封包,注冊層級也會影響執行順序。

ASP.NET Core 2 學習筆記(十四)Filters

但也可以通過實作 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.
      

參考

ASP.NET Core Filters

老司機發車啦:

https://github.com/SnailDev/SnailDev.NETCore2Learning