重新整理 .net core 實踐篇——— filter[四十四]

敖毛毛發表於2021-11-06

前言

簡單介紹一下filter

正文

filter 的種類,微軟文件中寫道:

每種篩選器型別都在篩選器管道中的不同階段執行:

授權篩選器最先執行,用於確定是否已針對請求為使用者授權。 如果請求未獲授權,授權篩選器可以讓管道短路。

資源篩選器:授權後執行。

OnResourceExecuting 在篩選器管道的其餘階段之前執行程式碼。 例如,OnResourceExecuting 在模型繫結之前執行程式碼。

OnResourceExecuted 在管道的其餘階段完成之後執行程式碼。

操作篩選器:

在呼叫操作方法之前和之後立即執行程式碼。

可以更改傳遞到操作中的引數。

可以更改從操作返回的結果。

Pages 中 Razor 不支援 。

異常篩選器在向響應正文寫入任何內容之前,對未經處理的異常應用全域性策略。

結果篩選器在執行操作結果之前和之後立即執行程式碼。 僅當操作方法成功執行時,它們才會執行。 對於必須圍繞檢視或格式化程式的執行的邏輯,它們很有用。

互動方式:

一般我們每個專案(單體或者微服務)都會用到授權篩選器和異常篩選器。

授權篩選器 是因為我們要保證使用者訪問的資源是其能夠訪問的。

異常篩選器 一般處理一些未捕獲的異常進行處理,當然現在為了程式碼的簡潔性性,一般我們在程式碼裡面一般情況下不進行try catch。

如果是某段程式碼是已知的異常,比如說知道某個引數不符合規則,那麼會主動丟擲自定義的異常,然後在異常篩選器統一處理。

舉個例子:

如果我們要進行錯誤碼處理。

那麼程式碼中會直接丟擲。

throw CustomException("errorCode");

然後再在異常篩選器中:

if(Exception is CustomException customException)
{
   // 進行錯誤碼處理 獲取其錯誤message
}

當然我們也可以使用異常處理中介軟體:

public class CustomExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly CustomExceptionHandlerOptions _options;

    public CustomExceptionHandlerMiddleware(RequestDelegate next, IOptions<CustomExceptionHandlerOptions> options)
    {
        _next = next;
        _options = options.Value;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (System.Exception ex)
        {
            var logger = context.RequestServices.GetRequiredService<ILoggerFactory>()
                .CreateLogger<CustomExceptionHandlerMiddleware>();
            if (context.RequestAborted.IsCancellationRequested && (ex is TaskCanceledException || ex is OperationCanceledException))
            {
                _options.OnRequestAborted?.Invoke(context, logger);
            }
            else
            {
                _options.OnException?.Invoke(context, logger, ex);
            }
        }
    }
}

異常處理中介軟體的好處也是不言而喻的,公司封裝自己的異常中介軟體,那麼每個服務使用該中介軟體處理方式會得到統一風格的log,且處理方式相同,那麼各服務的整體風格也差不多。

這些想必非常多的開發都是比較熟悉的,然後在閘道器中一般會使用結果篩選器。

如果我們需要加入一個引數是requestId進行追蹤的話,方便查詢日誌的話,那麼可以這樣:

public class AddRequestIdResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddRequestIdResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddRequestIdResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
		if (context.Result is ObjectResult objectResult)
		{
			if (objectResult.Value is DtoBaseModel dtoModel)
			{
				dtoModel.RequestId = context.HttpContext.TraceIdentifier;
			}
		}else if (context.Result is EmptyResult emptyResult)
		{
			context.Result = new ObjectResult(new DtoBaseModel{RequestId = context.HttpContext.TraceIdentifier });
		}
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddRequestIdResultServiceFilter.OnResultExecuted");
    }
}

當然這裡並不是真正的requestId,而是日誌追蹤的id,而是TraceId。

一般會在閘道器中增加一個requestId是為了日誌查詢相關的,一般僅在閘道器中新增這個requestId,因為一般會做分散式追蹤,也就是說閘道器後面的訪問鏈的traceId都會一樣,這個後面單獨總結一下。

僅當操作或操作篩選器生成操作結果時,才會執行結果篩選器。 不會在以下情況下執行結果篩選器:

授權篩選器或資源篩選器使管道短路。

異常篩選器通過生成操作結果來處理異常。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以將 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 設定為 true,使操作結果和後續結果篩選器的執行短路。

設定短路時寫入響應物件,以免生成空響應。 如果在 IResultFilter.OnResultExecuting 中引發異常,則會導致:

阻止操作結果和後續篩選器的執行。

結果被視為失敗而不是成功。

有一個值得注意的地方:

當 Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted 方法執行時,響應可能已傳送到客戶端。 如果響應已傳送到客戶端,則無法更改。

如果操作結果執行已被另一個篩選器設定短路,則 ResultExecutedContext.Canceled 設定為 true。

如果操作結果或後續結果篩選器引發了異常,則 ResultExecutedContext.Exception 設定為非 NULL 值。

將 Exception 設定為 NULL 可有效地處理異常,並防止在管道的後續階段引發該異常。 處理結果篩選器中出現的異常時,沒有可靠的方法來將資料寫入響應。 如果在操作結果引發異常時標頭已重新整理到客戶端,則沒有任何可靠的機制可用於傳送失敗程式碼。

對於 IAsyncResultFilter,通過呼叫 ResultExecutionDelegate 上的 await next 可執行所有後續結果篩選器和操作結果。 若要短路,請設定為 ResultExecutingContext.Cancel true ,不呼叫 ResultExecutionDelegate :

public class MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

操作篩選器

實現 IActionFilter 或 IAsyncActionFilter 介面。

它們的執行圍繞著操作方法的執行。

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

ActionExecutingContext 提供以下屬性:

ActionArguments - 用於讀取操作方法的輸入。

Controller - 用於處理控制器例項。

Result - 設定 Result 會使操作方法和後續操作篩選器的執行短路。

在操作方法中引發異常:

防止執行後續篩選器。

與設定 Result 不同,結果被視為失敗而不是成功。

例子:

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext 
                                          context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

這個一般記錄使用者的訪問情況居多,比如說全域性註冊,統計action的訪問次數等。

資源篩選器

資源篩選器:

實現 IResourceFilter 或 IAsyncResourceFilter 介面。
執行會覆蓋篩選器管道的絕大部分。
只有 授權篩選器 才會在資源篩選器之前執行。
如果要使大部分管道短路,資源篩選器會很有用。 例如,如果快取命中,則快取篩選器可以繞開管道的其餘階段。

資源篩選器示例:

之前顯示的短路資源篩選器。

DisableFormValueModelBindingAttribute:

可以防止模型繫結訪問表單資料。

用於上傳大型檔案,以防止表單資料被讀入記憶體。

如:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
	public void OnResourceExecuting(ResourceExecutingContext context)
	{
		var formValueProviderFactory = context.ValueProviderFactories
				.OfType<FormValueProviderFactory>()
				.FirstOrDefault();
		if (formValueProviderFactory != null)
		{
			context.ValueProviderFactories.Remove(formValueProviderFactory);
		}

		var jqueryFormValueProviderFactory = context.ValueProviderFactories
			.OfType<JQueryFormValueProviderFactory>()
			.FirstOrDefault();
		if (jqueryFormValueProviderFactory != null)
		{
			context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
		}
	}

	public void OnResourceExecuted(ResourceExecutedContext context)
	{
	}
}

下一節簡單整理一下分散式日誌鏈。

相關文章