ASP .NET Core Api使用過濾器

簡介

過濾器說明

過濾器與中間件很相似,過濾器(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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章