篩選條件可以方便讓我們針對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修改如下
參考