簡介
過濾器說明
過濾器與中間件很相似,過濾器(Filters)可在管道(pipeline)特定階段(particular stage)前後執行操作。可以將過濾器視爲攔截器(interceptors)。
過濾器級別範圍
過濾器有多個級別,分別是:
- 全局級別過濾器(Global scope),通過
Program.cs
全局添加Filter
- 控制器級別過濾器(Controller scope),通過
AttributeUsage
特性配置 - 動作級別過濾器(Action scope),通過
AttributeUsage
特性配置
過濾器類型
Asp.Net Core 過濾器:
- IResourceFilter
- IAuthorizationFilter
- IPageFilter
- ExceptionFilterAttribute
- ActionFilterAttribute
過濾器類型 | 接口 | 對應特性 | 含義 |
---|---|---|---|
授權過濾器 | IAuthorizationFilter、IAsyncAuthorizationFilter | 沒有提供特性類 | 最先執行,用於判斷用戶是否授權。如果未授權,則直接結束當前請求。這種類型的過濾器實現了 IAsyncAuthorizationFilter 或IAuthorizationFilter 接口。 |
資源過濾器 | IResourceFilter、IAsyncResourceFilter | 沒有提供特性類 | 在Authorization過濾器後執行,並在執行其他過濾器 (除Authorization過濾器外)之前和之後執行。由於它在Action之前執行,因而可以用來對請求判斷,根據條件來決定是否繼續執行Action。這種類型過濾器實現了 IAsyncResourceFilter 或 IResourceFilter 接口。 |
操作過濾器 | IActionFilter、IAsyncActionFilter | ActionFilterAttribute | 在Action執行的前後執行。與Resource過濾器不一樣,它在模型綁定後執行。這種類型的過濾器實現了 IAsyncActionFilter 或 IActionFilter 接口。 |
頁面過濾器 | IPageFilter、IAsyncPageFilter | 沒有提供特性類 | 頁面過濾器是 Razor Pages 等效的操作過濾器 |
結果過濾器 | IResultFilter、IAsyncResultFilter、 IAlwaysRunResultFilter、IAsyncAlwaysRunResultFilter | ResultFilterAttribute | 在 IActionResult 執行的前後執行,使用它能夠控制Action的執行結果,比如:格式化結果等。需要注意的是,它只有在Action方法成功執行完成後纔會運行。這種類型過濾器實現了 IAsyncResultFilter 或 IResultFilter 接口。 |
異常過濾器 | IExceptionFilter、IAsyncExceptionFilter | ExceptionFilterAttribute | 異常過濾器用於管理未處理的異常,比如:用於捕獲異常。這種類型的過濾器實現了 IAsyncExceptionFilter 或 IExceptionFilter 接口。 |
不同類型的過濾器在ASP.NET Core中的位置。可以看到不同類型的過濾器在不同階段起作用。授權過濾器先於其他所有操作,並在任何授權錯誤時阻止請求。 資源過濾器在模型驗證和模型綁定請求之前運行,也在我們的請求結果從服務器返回時運行。 動作過濾器類型在動作調用之前和之後起作用。 此外,如果操作引發異常,則會觸發異常過濾器。 在管道的末尾,結果過濾器對 IActionResult 最終對象實例進行操作。
ActionFilter
ActionFilterAttribute 攔截器通過 重寫 OnActionExecuting,來 攔截action的請求消息,當執行OnActionExecuting完成以後才真正進入請求的action中,action運行完後又把控制權給了 OnActionExecuted,這個管道機制可以使我們用它來輕鬆實現 權限認證、日誌記錄 ,跨域以及很多需要對全局或者部分請求做手腳的的功能。
大概的流程如下:
ActionFilter全稱是ActionFilterAttribute,我們根據微軟的命名規範可以看出這是一個特性類,看一下它的聲明:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata, IAsyncActionFilter, IAsyncResultFilter, IOrderedFilter, IResultFilter
這是一個允許標註在類和方法上的特性類,允許多個標記,標註之後子類會繼承父類的特性。然後,這個類是一個抽象類,所以我們可以通過繼承ActionFilterAttribute來編寫自己的ActionFilter。
ActionFilter 四個方法
public virtual void OnActionExecuted(ActionExecutedContext context);
public virtual void OnActionExecuting(ActionExecutingContext context);
public virtual void OnResultExecuted(ResultExecutedContext context);
public virtual void OnResultExecuting(ResultExecutingContext context);
上圖是這四個方法在一次請求中執行的順序。在一次請求真正執行之前,想要攔截這個請求,應該使用OnActionExecuting
。
爲什麼單獨說這個呢?因爲這個方法的出鏡率很高,大多數時候都會使用這個方法進行請求過濾。
獲取Api請求相關信息
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Text;
using System.Text.Json;
namespace WebApplication1
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class ApiFilter : ActionFilterAttribute
{
private string ActionArguments { get; set; }
private readonly ILogger<ApiFilter> _logger;
private readonly IServiceProvider _serviceProvider;
public ApiFilter(ILogger<ApiFilter> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
/// <summary>
/// 執行方法體之後,返回result前
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuted(ActionExecutedContext context)
{
var request = context?.HttpContext?.Request;
//請求地址
string url = request.Host + request.Path + request.QueryString;
var descriptor = (ControllerActionDescriptor)context.ActionDescriptor;
//獲取控制器名稱
var controllerName = descriptor.ControllerName;
//獲取action名稱
var actionName = descriptor.ActionName;
//獲取request參數
var requestArguments = ActionArguments;
//請求方式
string method = request.Method;
//請求Header
var headrs = request.Headers;
//context.HttpContext.Request.Form
//Response
var Response = context?.HttpContext?.Response;
var result = context.Result;
if (result is JsonResult json)
{
var x = json.Value;
var status = json.StatusCode;
var content = JsonSerializer.Serialize(x);
}
if (result is ViewResult view)
{
var status = view.StatusCode;
var x = view.ViewData;
var name = view.ViewName;
}
if (result is ObjectResult ob)
{
var x = ob.Value;
var status = ob.StatusCode;
var content = JsonSerializer.Serialize(x);
}
base.OnActionExecuted(context);
}
/// <summary>
/// 執行方法體之前
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
try
{
if (context.ActionArguments != null && context.ActionArguments.Count > 0)
{
ActionArguments = JsonSerializer.Serialize(context.ActionArguments);
}
else
{
ActionArguments = string.Empty;
}
}
catch (Exception ex)
{
_logger.LogError(ex.StackTrace);
}
base.OnActionExecuting(context);
}
/// <summary>
/// 返回result之前
/// </summary>
/// <param name="context"></param>
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
}
/// <summary>
/// 返回result之後
/// </summary>
/// <param name="context"></param>
public override void OnResultExecuted(ResultExecutedContext context)
{
base.OnResultExecuted(context);
}
}
}
ActionFilter 記錄異常日誌
using Microsoft.AspNetCore.Mvc.Filters;
using System.Text.Json;
namespace WebApplication1
{
public class ExceptionFilter : ActionFilterAttribute
{
private string ActionArguments { get; set; }
private readonly ILogger<ApiFilter> _logger;
private readonly IServiceProvider _serviceProvider;
public ExceptionFilter(ILogger<ApiFilter> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
/// <summary>
/// 執行方法體之後,返回result前
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
LoggerError(context, context.Exception);
}
base.OnActionExecuted(context);
}
/// <summary>
/// 執行方法體之前
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
try
{
if (context.ActionArguments != null && context.ActionArguments.Count > 0)
{
ActionArguments = JsonSerializer.Serialize(context.ActionArguments);
}
else
{
ActionArguments = string.Empty;
}
}
catch (Exception ex)
{
_logger.LogError(ex.StackTrace);
}
base.OnActionExecuting(context);
}
private void LoggerError(ActionExecutedContext context, Exception exception)
{
try
{
string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
string method = context.HttpContext.Request.Method;
string message = $"\n" + $"地址:{url} \n " +
$"方式:{method} \n " +
$"參數:{ActionArguments}\n " +
$"錯誤描述:{context.Exception.Message}\n " +
$"錯誤堆棧:{context.Exception.StackTrace}\n ";
if (exception.InnerException != null)
{
message += "\n InnerException異常Message:" + exception.InnerException.Message;
message += "\n InnerException異常StackTrace:" + exception.InnerException.StackTrace;
}
_logger.LogError(message);
}
catch (Exception ex)
{
_logger.LogError(ex.StackTrace);
}
}
}
}
ExceptionFilter 記錄異常日誌
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using System;
using System.Net;
using System.Text.Json;
namespace WebApplication1
{
public class ExceptionFilte2r : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
Exception ex = context.Exception;
string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
string method = context.HttpContext.Request.Method;
string message = $"\n " + $"地址:{url} \n " +
$"方式:{method} \n " +
$"錯誤描述:{ex.Message}\n " +
$"錯誤堆棧:{ex.StackTrace}\n ";
//while (ex.InnerException != null) { ex = ex.InnerException; }
if (ex.InnerException != null)
{
message += "\n InnerException異常Message:" + ex.InnerException.Message;
message += "\n InnerException異常StackTrace:" + ex.InnerException.StackTrace;
}
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Result = new ObjectResult(message);
// 表明異常已處理,客戶端可得到正常返回
context.ExceptionHandled = true;
base.OnException(context);
}
}
}
全局添加過濾器
在Program.cs
添加Filter
builder.Services.Configure<MvcOptions>(opts => opts.Filters.Add<ExceptionFilter>());
builder.Services.AddControllersWithViews(options =>
{
//options.Filters.Add(new ApiFilter(null,null));
options.Filters.Add<ApiFilter>();
});
//或者
builder.Services.Configure<MvcOptions>(opts => opts.Filters.Add<ExceptionFilter>());
參考文檔
https://www.cnblogs.com/cqpanda/p/16907950.html