.NET 過濾器(Filter) 與中間件與AOP面向切面 與攔截器及其應用


(注意 如果在單個控制器添加特性 需要注入 單獨 [GlobalExceptionFilter] 不行 需要加上TypeFilter(typeof(GlobalExceptionFilter)))

為什么filter可以依賴注入 因為繼承了IFilterFactory

Filter(過濾器)

總共有五種,Authorization Filter,Resource Filter,Exception Filter,Action Filter,Result Filter

Exception Filter 

新增全局異常過濾器GlobalExceptionFilter.cs,

當出現異常時進入此方法,可在這針對不同的異常做相關處理並返回指定數據,避免直接把錯誤暴露給用戶

public class GlobalExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        Exception ex = context.Exception;
        string errMsg = "GlobalExceptionFilter-OnException:" +                 ex.Message;
        if (context.Exception.GetType() == typeof(ExecuteException))
     {
        //針對不同的自定義異常,做不同處理
        MsgModel<string> msgModel = new MsgModel<string>()
        {
            Status = false,
            Msg = errMsg,
            Errcode = "AA001"
        };
        context.Result = new JsonResult(msgModel);
        context.ExceptionHandled = true;
    }
    else
    {
      context.Result = new JsonResult(errMsg);
      context.ExceptionHandled = true;
    }

    LogHelper.Error(errMsg);
  }
}                

然后在Startup.cs 注入過濾器

 Action Filter 

新增全局過濾器GlobalActionFilter.cs 

在方法執行前后,會跳轉至以下兩個方法,方便追蹤接口執行情況 

過去返回的參數

var result = context.Result;
            ObjectResult objectResult= result as ObjectResult; 
            var resultLog = $"{DateTime.Now} 調用 {context.RouteData.Values["action"]} api 完成;執行結果:{Newtonsoft.Json.JsonConvert.SerializeObject(objectResult.Value)}"; 

獲取請求的參數

var actionLog = $"{DateTime.Now} 開始調用 {context.RouteData.Values["action"]} api;參數為:{Newtonsoft.Json.JsonConvert.SerializeObject(context.ActionArguments)}";
public class GlobalActionFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
    //LogHelper.Info("OnActionExecuted");
    //執行方法后執行這
   }

  public void OnActionExecuting(ActionExecutingContext context)
  {
  //LogHelper.Info("OnActionExecuting");
  //執行方法前先執行這
  }
}    

Authonization Filter

權限控制過濾器
通過 Authonization Filter 可以實現復雜的權限角色認證登陸授權等操作

/// <summary>
    /// 實現自定義授權
    /// </summary>
    public class AuthorizeFilter : IAuthorizationFilter
    {
        /// <summary>
        /// 請求驗證,當前驗證部分不要拋出異常,ExceptionFilter不會處理
        /// </summary>
        /// <param name="context"></param>
        public void OnAuthorization(AuthorizationFilterContext context)
        {

        //這里可以做復雜的權限控制操作
        //if (context.HttpContext.User.Identity.Name != "1") //簡單的做一個示范
        //{
        // //未通過驗證則跳轉到無權限提示頁
        // RedirectToActionResult content = new RedirectToActionResult("NoAuth", "Exception", null);
        // context.Result = content;
        //


        }
    }

Resource Filter

資源過濾器
可以通過Resource Filter 進行資源緩存防盜鏈等操作。
使用Resource Filter 要求實現IResourceFilter 抽象接口

public class ResourceFilter : Attribute,IResourceFilter
    {
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            // 執行完后的操作
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            // 執行中的過濾器管道
        }
    }

Result Filter

結果過濾器,可以對結果進行格式化、大小寫轉換等一系列操作。

使用Result Filter 需要實現IResultFilter 抽象接口,接口要求實現
OnResultExecuting 方法 和OnResultExecuted 方法

    • OnResultExecuting :Called before the action result executes. 在操作結果執行之前調用
    • OnResultExecuted :Called after the action result executes. 在操作結果執行之后調用
public class ResultFilter : Attribute, IResultFilter
 {
        public void OnResultExecuted(ResultExecutedContext context)
        { 
            // 在結果執行之后調用的操作...
        }

        public void OnResultExecuting(ResultExecutingContext context)
        {
            // 在結果執行之前調用的一系列操作
        }
    }

完畢 可以在全局注入

 中間件

中間件是一種裝配到應用管道以處理請求和響應的軟件。 每個組件:

  • 選擇是否將請求傳遞到管道中的下一個組件。
  • 可在管道中的下一個組件前后執行工作。

請求委托用於生成請求管道。 請求委托處理每個 HTTP 請求。

/// <summary>
    /// 中間件
    /// 記錄請求和響應數據
    /// </summary>
    public class RequestMiddleware
    {

        private readonly RequestDelegate _next;

        /// <summary>
        /// 日志接口
        /// </summary>
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private Stopwatch _stopwatch;

        public RequestMiddleware(RequestDelegate next)
        {
            _stopwatch = new Stopwatch();
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            // 過濾,只有接口
            if (context.Request.Path.Value.ToLower().Contains("api"))
            {
                context.Request.EnableBuffering();
                Stream originalBody = context.Response.Body;

                _stopwatch.Restart();

                // 獲取 Api 請求內容
                var requestContent = await GetRequesContent(context);


                // 獲取 Api 返回內容
                using (var ms = new MemoryStream())
                {
                    context.Response.Body = ms;

                    await _next(context);
                    ms.Position = 0;

                    await ms.CopyToAsync(originalBody);
                }

                context.Response.Body = originalBody;

                _stopwatch.Stop();

                var eventInfo = new LogEventInfo();
                eventInfo.Message = "Success";
                eventInfo.Properties["Elapsed"] = _stopwatch.ElapsedMilliseconds;
                eventInfo.Properties["RequestBody"] = requestContent;

                logger.Trace(eventInfo);
            }
            else
            {
                await _next(context);
            }
        }

        private async Task<string> GetRequesContent(HttpContext context)
        {
            var request = context.Request;
            var sr = new StreamReader(request.Body);

            var content = $"{await sr.ReadToEndAsync()}";

            if (!string.IsNullOrEmpty(content))
            {
                request.Body.Position = 0;
            }

            return content;
        }
    }

然后在Startup

// 請求日志監控
            app.UseMiddleware<RequestMiddleware>();

 AOP(面向切面編程)

面向切面編程(AOP是Aspect Oriented Program的首字母縮寫) ,我們知道,面向對象的特點是繼承、多態和封裝。而封裝就要求將功能分散到不同的對象中去,這在軟件設計中往往稱為職責分配。實際上也就是說,讓不同的類設計不同的方法。這樣代碼就分散到一個個的類中去了。這樣做的好處是降低了代碼的復雜程度,使類可重用。

      但是人們也發現,在分散代碼的同時,也增加了代碼的重復性。什么意思呢?比如說,我們在兩個類中,可能都需要在每個方法中做日志。按面向對象的設計方法,我們就必須在兩個類的方法中都加入日志的內容。也許他們是完全相同的,但就是因為面向對象的設計讓類與類之間無法聯系,而不能將這些重復的代碼統一起來。

    也許有人會說,那好辦啊,我們可以將這段代碼寫在一個獨立的類獨立的方法里,然后再在這兩個類中調用。但是,這樣一來,這兩個類跟我們上面提到的獨立的類就有耦合了,它的改變會影響這兩個類。那么,有沒有什么辦法,能讓我們在需要的時候,隨意地加入代碼呢?這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。

       一般而言,我們管切入到指定類指定方法的代碼片段稱為切面,而切入到哪些類、哪些方法則叫切入點。有了AOP,我們就可以把幾個類共有的代碼,抽取到一個切片中,等到需要時再切入對象中去,從而改變其原有的行為。這樣看來,AOP其實只是OOP的補充而已。OOP從橫向上區分出一個個的類來,而AOP則從縱向上向對象中加入特定的代碼。有了AOP,OOP變得立體了。如果加上時間維度,AOP使OOP由原來的二維變為三維了,由平面變成立體了。從技術上來說,AOP基本上是通過代理機制實現的。      AOP在編程歷史上可以說是里程碑式的,對OOP編程是一種十分有益的補充。

AOP是是OOP(面向對象)的補充和完善

攔截器是在面向切面編程中應用的,就是在你的service或者一個方法前調用一個方法,或者在方法后調用一個方法。

AOP說白了就是在運行時,動態的將代碼切入到類的指定方法的指定位置上,這種思想就是面向切面的編程思想。

就是 在不修改源代碼的基礎上 添加新業務,比如 日志 性能檢測

從思想上來說,Filter(過濾器)跟AOP(攔截器)極其接近
但是區別明顯:
1、Filter只能攔截request的請求
2、Spring中的AOP,一般而言,是在Service層,攔截Bean(實例)的訪問。
例如使用@Transcational 來攔截dao的應用,讓它實現事務的管理。從思想上來說,Filter跟AOP極其接近

1、攔截器是基於java的反射機制,過濾器是基於java的函數回調

  2、攔截器不依賴於servlet容器,而過濾器依賴於servlet容器

  3、攔截器只能對action請求起作用,過濾器幾乎對所有的請求起作用

  4、攔截器可以訪問action上下文,值棧里的對象,而過濾器不能訪問

  5、在action生命周期中,攔截器可以被多次調用,過濾器只能在servlet初始化時調用一次

  6、攔截器可以獲取IOC容器中的各個bean,過濾器不行,在攔截器中注入一個service可以調用邏輯業務

Aop”其實就是動態代理

Interceptor(攔截器)

1,攔截器的概念
java里的攔截器是動態攔截Action調用的對象,它提供了一種機制可以使開發者在一個Action執行的前后執行一段代碼,也可以在一個Action
執行前阻止其執行,同時也提供了一種可以提取Action中可重用部分代碼的方式。在AOP中,攔截器用於在某個方法或者字段被訪問之前,進行攔截
然后再之前或者之后加入某些操作。目前,我們需要掌握的主要是Spring的攔截器,Struts2的攔截器不用深究,知道即可。

2,攔截器的原理
大部分時候,攔截器方法都是通過代理的方式來調用的。Struts2的攔截器實現相對簡單。當請求到達Struts2的ServletDispatcher時,Struts2
會查找配置文件,並根據配置實例化相對的攔截器對象,然后串成一個列表(List),最后一個一個的調用列表中的攔截器。Struts2的攔截器是可
插拔的,攔截器是AOP的一個實現。Struts2攔截器棧就是將攔截器按一定的順序連接成一條鏈。在訪問被攔截的方法或者字段時,Struts2攔截器鏈
中的攔截器就會按照之前定義的順序進行調用。

3,自定義攔截器的步驟
第一步:自定義一個實現了Interceptor接口的類,或者繼承抽象類AbstractInterceptor。
第二步:在配置文件中注冊定義的攔截器。
第三步:在需要使用Action中引用上述定義的攔截器,為了方便也可以將攔截器定義為默認的攔截器,這樣在不加特殊說明的情況下,所有的
Action都被這個攔截器攔截。

4,過濾器與攔截器的區別
過濾器可以簡單的理解為“取你所想取”,過濾器關注的是web請求;攔截器可以簡單的理解為“拒你所想拒”,攔截器關注的是方法調用,比如攔截
敏感詞匯。
4.1,攔截器是基於java反射機制來實現的,而過濾器是基於函數回調來實現的。(有人說,攔截器是基於動態代理來實現的)
4.2,攔截器不依賴servlet容器,過濾器依賴於servlet容器。
4.3,攔截器只對Action起作用,過濾器可以對所有請求起作用。
4.4,攔截器可以訪問Action上下文和值棧中的對象,過濾器不能。
4.5,在Action的生命周期中,攔截器可以多次調用,而過濾器只能在容器初始化時調用一次。

LogInterceptor

安裝Castle.Core,Autofac.Extras.DynamicProxy

新建LogInterceptor.cs ,繼承IInterceptor

   public class LogInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            try
            {
                invocation.Proceed();
                Dapper.Logger.LogHelper.logger.Info(invocation.Method.Name);
            }
            catch (Exception ex)
            {
                Dapper.Logger.LogHelper.logger.Error(invocation.Method.Name + " " + ex.ToString());
            }
        }
    }

在Startup.cs 新增以下代碼

針對某個類或者某個方法做攔截時

首先新建一個攔截器 MyInterceptor

    public class MyInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            try
            {
                invocation.Proceed();
                NLogHelper.logger.Info(invocation.Method.Name);
            }
            catch (Exception ex)
            {
                NLogHelper.logger.Error(invocation.Method.Name + " " + ex.ToString());
            }
        }
    }

然后Startup.cs 中ConfigureContainer代碼如下

把LogInterceptor 代碼注釋,但是要保留接口攔截EnableInterfaceInterceptors() ,注入MyInterceptor

public void ConfigureContainer(ContainerBuilder builder)
    {
        //builder.RegisterType<LogInterceptor>();

        builder.RegisterType<MyInterceptor>();

        builder.RegisterType<DbFactory>().As<IDbFactory>();

        //業務邏輯層所在程序集命名空間
        Assembly service = Assembly.Load("Summer.Service"); //注:webapi要引用接口和類,不然這里讀不到
                                                            //接口層所在程序集命名空間
        Assembly repository = Assembly.Load("Summer.IService");
        //自動注入
        builder.RegisterAssemblyTypes(service, repository)
        .Where(t => t.Name.EndsWith("Service"))
        .AsImplementedInterfaces()
        .InstancePerLifetimeScope()
        .EnableInterfaceInterceptors() //開啟接口攔截
                                       //.InterceptedBy(typeof(LogInterceptor)) //設置全局攔截器,統一由LogInterceptor攔截所有接口的調用
        ;
    }

然后在需要攔截的接口中添加以下代碼

 攔截器設置完畢,當調用ITestService 的全部方法都會跳轉攔截器

 這里是一個示例 利用反射查看權限

public class AuthInterceptor : IInterceptor
    {

        public void Intercept(IInvocation invocation)
        {

            Type tt = invocation.InvocationTarget.GetType();
            DoMain.Model.SYS.SYS_User userinfo = null;

            var p = tt.GetProperties().FirstOrDefault(q => q.Name == "UserInfo");

            userinfo = (DoMain.Model.SYS.SYS_User)p.GetValue(invocation.InvocationTarget);
            var authkey = invocation.TargetType.ToString() + "." + invocation.Method.Name;

            if (AuthChecker.isAuth(userinfo, authkey))
            {
                invocation.Proceed();
            }
            else
            {
                throw new Core.AuthException("沒有操作權限");
            }

            //方法執行后的操作


        }

Filter和 LogInterceptor 可以同時共存,執行順序是:

ActionFilter 的OnActionExecuting =》LogInterceptor 的Intercept =》ActionFilter 的OnActionExecuted 

如果接口有異常,不會跳轉LogInterceptor ,而是進入ExceptionFilter,順序是:

ActionFilter 的OnActionExecuting =》ActionFilter 的OnActionExecuted =》ExceptionFilter 的OnException

AOP的應用

引入三個包,通過Nuget安裝,Autofac開頭,如下

 注: 其中Autofac.Extras.DynamicProxy就是AOP相關組件,其中包含了Castle.Core,所以不用單獨安裝Castle.Core.

 

 

 

 

總結:

  AOP在做一些業務前置或后置處理上時很有用的,使用比較靈活,無需修改原有代碼邏輯,比起修改原有代碼維護相對好多啦!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM