在webform中,验证的流程大致如下图:

 

 

 在aop中:

 

 

 在filter中:

 

 

authorizeattribute权限验证 

登录后有权限控制,有的页面是需要用户登录才能访问的,需要在访问页面增加一个验证,也不能每个action都一遍。

1、写一个customauthorattribute,继承自authorizeattribute,重写onauthorization方法,在里面把逻辑写成自己的。

2、有方法注册和控制器注册。

3、有全局注册,全部控制器全部action都生效。

但是在这个里面,首先要验证登录首页,首页没有邓丽,就跑到登录页面了,但是登录页面也要走特性里面的逻辑,又重定向到邓丽。。。循环了。。。。

这里有一个alloanonymous,这个标签就可以解决这个循环的问题,匿名支持,不需要登录就可以,但是单单加特性是没有用的,其实需要验证时支持,甚至可以说自己自定义一个特性也是可以的,这个特性里面是空的,只是为了用来做标记。

特性的使用范围,希望特性通用,在不同的系统,不同的地址登录,==》在特性上面加个传参的构造函数。

 public class customallowanonymousattribute : attribute
 {
 }

customauthorattribute类

[attributeusage(attributetargets.class | attributetargets.method)]
public class customauthorizeattribute : authorizeattribute
{
    private logger logger = new logger(typeof(customauthorizeattribute));
    private string _loginurl = null;
    public customauthorizeattribute(string loginurl = "~/home/login")
    {
        this._loginurl = loginurl;
    }
    //public customauthorizeattribute(icompanyuserservice service)
    //{
    //}
    //不行


    public override void onauthorization(authorizationcontext filtercontext)
    {
        var httpcontext = filtercontext.httpcontext;//能拿到httpcontext 就可以为所欲为

        if (filtercontext.actiondescriptor.isdefined(typeof(customallowanonymousattribute), true))
        {
            return;
        }
        else if (filtercontext.actiondescriptor.controllerdescriptor.isdefined(typeof(customallowanonymousattribute), true))
        {
            return;
        }
        else if (httpcontext.session["currentuser"] == null
            || !(httpcontext.session["currentuser"] is currentuser))//为空了,
        {
            //这里有用户,有地址 其实可以检查权限
            if (httpcontext.request.isajaxrequest())
                //httpcontext.request.headers["xxx"].equals("xmlhttprequst")
            {
                filtercontext.result = new newtonjsonresult(
                    new ajaxresult()
                    {
                        result = doresult.overtime,
                        debugmessage = "登陆过期",
                        retvalue = ""
                    });
            }
            else
            {
                httpcontext.session["currenturl"] = httpcontext.request.url.absoluteuri;
                filtercontext.result = new redirectresult(this._loginurl);
                //短路器:指定了result,那么请求就截止了,不会执行action
            }
        }
        else
        {
            currentuser user = (currentuser)httpcontext.session["currentuser"];
            //this.logger.info($"{user.name}登陆了系统");
            return;//继续
        }
        //base.onauthorization(filtercontext);
    }
}

filter生效机制

为什么加个标签,继承authorizeattribute,重写onauthorization方法就可以了呢?控制器已经实例化,调用executecore方法,找到方法名字,controlleractioninvokee.invokeaction,找到全部的filter特性,invokeauthorize–result不为空,直接invokeactionresult,为空就正常执行action。

有一个实例类型,有一个方法名称,希望你反射执行

在找到方法后,执行方法前,可以检测下特性,来自全局的、来自控制器的、来自方法的。价差特性,特性是自己预定义的,按类执行,定个标识,为空就正常,不为空就跳转,正常就继续执行。

filter原理和aop面向切面编程

filter是aop思想的一种实现,其实就是controlleractioninvoke这个类中,有个invokeaction方法,控制器实例化之后,actioninvoke前后,通过检测预定义filter并且执行它,达到aop的目的。

下面是invokeaction的源码:

public virtual bool invokeaction(controllercontext controllercontext, string actionname)
        {
            if (controllercontext == null)
            {
                throw new argumentnullexception("controllercontext");
            }
            if (string.isnullorempty(actionname) && !controllercontext.routedata.hasdirectroutematch())
            {
                throw new argumentexception(mvcresources.common_nullorempty, "actionname");
            }
            controllerdescriptor controllerdescriptor = this.getcontrollerdescriptor(controllercontext);
            actiondescriptor actiondescriptor = this.findaction(controllercontext, controllerdescriptor, actionname);
            if (actiondescriptor != null)
            {
                filterinfo filters = this.getfilters(controllercontext, actiondescriptor);
                try
                {
                    authenticationcontext authenticationcontext = this.invokeauthenticationfilters(controllercontext, filters.authenticationfilters, actiondescriptor);
                    if (authenticationcontext.result != null)
                    {
                        authenticationchallengecontext authenticationchallengecontext = this.invokeauthenticationfilterschallenge(controllercontext, filters.authenticationfilters, actiondescriptor, authenticationcontext.result);
                        this.invokeactionresult(controllercontext, authenticationchallengecontext.result ?? authenticationcontext.result);
                    }
                    else
                    {
                        authorizationcontext authorizationcontext = this.invokeauthorizationfilters(controllercontext, filters.authorizationfilters, actiondescriptor);
                        if (authorizationcontext.result != null)
                        {
                            authenticationchallengecontext authenticationchallengecontext2 = this.invokeauthenticationfilterschallenge(controllercontext, filters.authenticationfilters, actiondescriptor, authorizationcontext.result);
                            this.invokeactionresult(controllercontext, authenticationchallengecontext2.result ?? authorizationcontext.result);
                        }
                        else
                        {
                            if (controllercontext.controller.validaterequest)
                            {
                                controlleractioninvoker.validaterequest(controllercontext);
                            }
                            idictionary<string, object> parametervalues = this.getparametervalues(controllercontext, actiondescriptor);
                            actionexecutedcontext actionexecutedcontext = this.invokeactionmethodwithfilters(controllercontext, filters.actionfilters, actiondescriptor, parametervalues);
                            authenticationchallengecontext authenticationchallengecontext3 = this.invokeauthenticationfilterschallenge(controllercontext, filters.authenticationfilters, actiondescriptor, actionexecutedcontext.result);
                            this.invokeactionresultwithfilters(controllercontext, filters.resultfilters, authenticationchallengecontext3.result ?? actionexecutedcontext.result);
                        }
                    }
                }
                catch (threadabortexception)
                {
                    throw;
                }
                catch (exception exception)
                {
                    exceptioncontext exceptioncontext = this.invokeexceptionfilters(controllercontext, filters.exceptionfilters, exception);
                    if (!exceptioncontext.exceptionhandled)
                    {
                        throw;
                    }
                    this.invokeactionresult(controllercontext, exceptioncontext.result);
                }
                return true;
            }
            return false;
        }

全局异常处理handleerrorattribute

关于异常处理的建议:

  1、避免ui层直接看到异常,每个控制器里面try-catch一下?不是很麻烦吗?

  2、这个时候,aop就登场了,handleerrorattribute,自己写一个特性,继承之handleerrorattribute,重写onexception,在发生异常之后,会跳转到这个方法。

在这边,一定要

 public class customhandleerrorattribute : handleerrorattribute
 {
     private logger logger = new logger(typeof(customhandleerrorattribute));

     /// <summary>
     /// 会在异常发生后,跳转到这个方法
     /// </summary>
     /// <param name="filtercontext"></param>
     public override void onexception(exceptioncontext filtercontext)
     {
         var httpcontext = filtercontext.httpcontext;//"为所欲为"
         if (!filtercontext.exceptionhandled)//没有被别的handleerrorattribute处理
         {
             this.logger.error($"在响应 {httpcontext.request.url.absoluteuri} 时出现异常,信息:{filtercontext.exception.message}");//
             if (httpcontext.request.isajaxrequest())
             {
                 filtercontext.result = new newtonjsonresult(
                 new ajaxresult()
                 {
                     result = doresult.failed,
                     debugmessage = filtercontext.exception.message,
                     retvalue = "",
                     promptmsg = "发生错误,请联系管理员"
                 });
             }
             else
             {
                 filtercontext.result = new viewresult()//短路器
                 {
                     viewname = "~/views/shared/error.cshtml",
                     viewdata = new viewdatadictionary<string>(filtercontext.exception.message)
                 };
             }
             filtercontext.exceptionhandled = true;//已经被我处理了
         }
     }
 }

这个是要重新跳转的地址:

 

 

 一定要考虑到是不是ajax请求的

 

 

 

 

 

 多种异常情况,能不能进入自定义的异常呢?

1、action异常,没有被catch

2、action异常,被catch

3、action调用service异常

4、action正常视图出现异常了

5、控制器构造出现异常

6、action名称错误

7、任意地址错误

8、权限filter异常

答案:

1、可以

2、不可以

3、可以,异常冒泡

4、可以,为什么呢?因为executeresult是包裹在try里面的

5、不可以的,filter是在构造完成控制之后方法执行之前完成的

6、不可以的,因为请求都没进mvc流程

7、不可以的,因为请求都没进mvc

8、可以的,权限filter也是在try里面的。

那这些没有被捕获的异常怎么办?还有一个方法

在global中增加一个事件

 public class mvcapplication : system.web.httpapplication
 {
     private logger logger = new logger(typeof(mvcapplication));
     protected void application_start()
     {
         arearegistration.registerallareas();//注册区域
         filterconfig.registerglobalfilters(globalfilters.filters);//注册全局的filter
         routeconfig.registerroutes(routetable.routes);//注册路由
         bundleconfig.registerbundles(bundletable.bundles);//合并压缩 ,打包工具 combres
         controllerbuilder.current.setcontrollerfactory(new elevencontrollerfactory());

         this.logger.info("网站启动了。。。");
     }
     /// <summary>
     /// 全局式的异常处理,可以抓住漏网之鱼
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     protected void application_error(object sender, eventargs e)
     {
         exception excetion = server.getlasterror();
         this.logger.error($"{base.context.request.url.absoluteuri}出现异常");
         response.write("system is error....");
         server.clearerror();

         //response.redirect
         //base.context.rewritepath("/home/error?msg=")
     }

handleerrorattribute+application_error,粒度不一样,能拿到的东西不一样

iactionfilter扩展定制

iactionfilter

1、onactionexecuting   方法执行前

2、onactionexecuted方法执行后

3、onresultexecuting结果执行前

4、onresultexecuted结果执行后

先执行权限filter,再执行actionfilter。

执行的顺序:

  global onactionexecuting

  controller onactionexecuting

  action onactionexecuting

  action真实执行

  action onactionexecuted

  controller onactionexecuted

  global onactionexecuted

 

 

不同位置注册的生效顺序:全局—》控制器—–》action

好像一个俄罗斯套娃,或者说洋葱模型

 

 

在同一个位置注册的生效顺序,同一个位置按照先后顺序生效,还有一个order的参数,不设置order默认是1,设置之后按照从小到大执行

 

 

 

actionfilter能干什么?

日志、参数检测、缓存、重写视图、压缩、防盗链、统计访问、不同的客户端跳转不同的页面、限流…..

浏览器请求时,会声明支持的格式,默认的iis是没有压缩的,检测了支持的格式,在响应时将数据压缩(iis服务器完成的),在响应头里面加上content-encoding,浏览器查看数据格式,按照浏览器格式解压(无论你是什么东西,都可以压缩解压的),压缩是iis,解压是浏览器的。

 public class compressactionfilterattribute : actionfilterattribute
 {
     public override void onactionexecuting(actionexecutingcontext filtercontext)
     {
         //foreach (var item in filtercontext.actionparameters)
         //{
         //    //参数检测  敏感词过滤
         //}  
         var request = filtercontext.httpcontext.request;
         var respose = filtercontext.httpcontext.response;
         string acceptencoding = request.headers["accept-encoding"];//检测支持格式
         if (!string.isnullorwhitespace(acceptencoding) && acceptencoding.toupper().contains("gzip"))
         {
             respose.addheader("content-encoding", "gzip");//响应头指定类型
             respose.filter = new gzipstream(respose.filter, compressionmode.compress);//压缩类型指定
         }
     }
 }

 public class limitactionfilterattribute : actionfilterattribute
 {
     private int _max = 0;
     public limitactionfilterattribute(int max = 1000)
     {
         this._max = max;
     }
     public override void onactionexecuting(actionexecutingcontext filtercontext)
     {
         string key = $"{filtercontext.routedata.values["controller"]}_{filtercontext.routedata.values["action"]}";
         //cachemanager.add(key,) 存到缓存key 集合 时间  
         filtercontext.result = new jsonresult()
         {
             data = new { msg = "超出频率" }
         };
     }
 }

 

 

 

 

 

 

 filter这么厉害,有没有什么局限性????

虽然很丰富,但是只能以action为单位,action内部调用别的类库,加操作就做不到!这种就得靠ioc+aop扩展。

本篇只是介绍了.net framework mvc 中的过滤器filter(权限特性、action、result、exception),其实在.net core mvc 增加了resourcefilter,加了这个特性,资源特性,action/result /exception三个特性没有什么变化。后面记录到到.net core mvc时再详细介绍。