[netCore] Filter

篩選條件可以方便讓我們針對request processing pipeline之前或之後進行額外的邏輯處理。

上一篇 how to capture data of coreprofiler then store data in sql server我透過ResultFilter並在startup.cs全域註冊,

但我為了要滿足DI存取相依性,我預先透過serviceProvider取相關interface物件後,

傳入該class的constructor,但這方法有點硬幹,這篇就來改的優雅點~XD。

 

Filter如何運作

From Microsoft

 

上面兩張圖,我們可以看出五大Filter類型在Filter pipeline的流程,

每個Filter類型皆會在filter pipeline中的不同階段執行(有順序)。

例如,當一個httprequest進入會先執行Authorization Filters,

接續Resource Filters、Model Binding、Action Filters(subEntry: Action Execution和Action Result Conversion )、

Exception Filters、Result Filters及最終的Result Execution,

而我上一篇就是在Result Filters實作把coreProfiler的效能監控資料給新增至SQL Server。

所以了解每個Filter類型和先後順序我覺得滿重要的。

 

Authorization filters

用於驗證request邏輯。

 

Resource filters

會在model binding之前執行,當需要額外處理model binding才需要。

 

 

Action filters

很常使用的filter,使用上和以前Asp.net MVC沒差多少([C#][ASP.NET MVC]自訂Action Filter)。

 

 

Exception filters

將通用的Exception處理,套用在response之前。

 

 

Result filters

當Action方法執行成功後,我們可以在結果前後加工額外處理邏輯。

 

Filter同時支援同步和非同步實作,同步定義在OnStageExecuting 以及 OnStageExecuted 方法,

非同步只定義單一OnStageExecutionAsync 方法(接受FilterTypeExecutionDelegate 委派),

如我之前的CoreProfilerResultFilter.cs

public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
           {
               // do something before the action executes
               await next();
               //after the action executes
               var timingSession = ProfilingSession.Current.Profiler.GetTimingSession();
               if (timingSession != null)
               {
                   string sessionId = timingSession.Id.ToString();
                   List<CoreProfilerModulecs> coreProfilerModulecs = new List<CoreProfilerModulecs>();
                   foreach (var timing in timingSession.Timings)
                   {
                       long duration = 0;
                       if (timing.Name.ToLowerInvariant() == "root" && timing.DurationMilliseconds <= 0)
                           duration = timingSession.DurationMilliseconds;
                       coreProfilerModulecs.Add(new CoreProfilerModulecs
                       {
                           SessionId = sessionId,
                           ParentId = timing.ParentId.HasValue ? timing.ParentId.Value.ToString() : "",
                           Machine = timingSession.MachineName,
                           Type = timing.Type,
                           CurrentId = timing.Id.ToString(),
                           Name = timing.Name,
                           Start = timing.StartMilliseconds,
                           Duration = duration > 0 ? duration : timing.DurationMilliseconds,
                           Sort = timing.Sort,
                           Started = timing.Started
                       });
                   }
                   await _coreProfilerRepository.BulkInsertAsync(coreProfilerModulecs);
               }
           }

Note:請勿同時實作同步和非同步,預設會先呼叫非同步介面,避免非同步方法複寫同步方法。

 

假設現在我需要在每一個response加入HTTP header,可繼承內建ResultFilterAttribute覆寫實作

public class AddHeaderResultFilterAttribute : ResultFilterAttribute
   {
       private readonly string _name;
       private readonly string _value;
       public AddHeaderResultFilterAttribute(string name, string value)
       {
           _name = name;
           _value = value;
       }
 
       public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
       {
           context.HttpContext.Response.Headers.Add(
               _name, new string[] { _value });
           return base.OnResultExecutionAsync(context, next);
       }
   }

 

Controller透過AOP加上AddHeaderResultFilter並傳入相關參數

Note:你也可以在startup.cs全域註冊。

 

View Http Header in Chrome

目前內建Filter attribute有以下

 

現在我將透過內建TypeFilterAttribute來把之前的CoreProfilerResultFilter改得更優雅一點

public class CoreProfilerResultFilterAttribute : TypeFilterAttribute
   {
       public CoreProfilerResultFilterAttribute() : base(typeof(CoreProfilerResultFilterImpl))
       {
       }
 
       private class CoreProfilerResultFilterImpl : IAsyncResultFilter
       {
           private readonly ILogger<CoreProfilerResultFilter> _logger;
           private readonly ICoreProfilerRepository _coreProfilerRepository;
           public CoreProfilerResultFilterImpl(ILogger<CoreProfilerResultFilter> logger, ICoreProfilerRepository coreProfilerRepository)
           {
               _logger = logger;
               _coreProfilerRepository = coreProfilerRepository;
           }
 
           public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
           {
               // do something before the action executes
               await next();
               //after the action executes
               var timingSession = ProfilingSession.Current.Profiler.GetTimingSession();
               if (timingSession != null)
               {
                   string sessionId = timingSession.Id.ToString();
                   List<CoreProfilerModulecs> coreProfilerModulecs = new List<CoreProfilerModulecs>();
                   foreach (var timing in timingSession.Timings)
                   {
                       long duration = 0;
                       if (timing.Name.ToLowerInvariant() == "root" && timing.DurationMilliseconds <= 0)
                           duration = timingSession.DurationMilliseconds;
                       coreProfilerModulecs.Add(new CoreProfilerModulecs
                       {
                           SessionId = sessionId,
                           ParentId = timing.ParentId.HasValue ? timing.ParentId.Value.ToString() : "",
                           Machine = timingSession.MachineName,
                           Type = timing.Type,
                           CurrentId = timing.Id.ToString(),
                           Name = timing.Name,
                           Start = timing.StartMilliseconds,
                           Duration = duration > 0 ? duration : timing.DurationMilliseconds,
                           Sort = timing.Sort,
                           Started = timing.Started
                       });
                   }
                   await _coreProfilerRepository.BulkInsertAsync(coreProfilerModulecs);
               }
           }
       }
   }

TyperFilterAttribute很類似ServiceFilterAttribute(也會實作 IFilterFactory),

但其類型不會直接從 DI 容器解析。 相反地,

它會使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 來實體化類型。

如果我們的Filter只需要DI建構函示相依性,那透過TyperFilterAttribute既可滿足要求,又不破壞既有ConfigServices。

 

Startup.cs修改如下

 

參考

Filters in ASP.NET Core

ASP.NET CORE 2.0 ACTION FILTERS

Working With Filters In ASP.NET Core MVC