什么是过滤器?

通过上一篇关于controller控制器的文章我们知道,mvc中的每一个请求,都会分配给相应的控制器(controller)和对应的行为方法(action)去处理,那么如果我们想要在action处理的前后加上一些额外的处理逻辑怎么办呢?这时候就用到了过滤器(filters)。

在asp.net mvc的请求处理过程中有19个管道事件,这些事件分布在请求处理的各个节点中,比如beginrequest(开始处理请求时触发)、authenticaterequest(对请求进行身份验证时触发)、authorizerequest(对请求进程授权时触发)…等等等等。而过滤器的主要作用就是将我们的附加逻辑注入到这些请求处理管道中。

在实际业务中,在action方法前后添加额外附加逻辑的情况有很多,过滤器就是用来完成此功能。通过过滤器可以将与业务逻辑无关但经常需要执行的代码分离开,使我们的代码逻辑性更加清晰,代码更加简洁。

过滤器的类型与作用

mvc给我们提供了四种过滤器,基本满足了我们实际业务中常用的需求,包括以下:

过滤器类型名称

实现的接口

默认的实现类

作用

执行的顺序与节点

授权过滤器

iauthorizationfilter

authorizeattribute

用于限制进入控制器或控制器的某个行为方法

在控制器方法调用前执行,所有过滤器中最先执行的

动作过滤器

iactionfilter

actionfilterattribute

用于进入动作方法之前或之后的处理

在控制器方法调用前/后执行

结果过滤器

iresultfilter

actionfilterattribute

用于动作方法返回结果之前或之后的处理

在控制器方法调用完,跳转至view页面前/后执行

异常处理过滤器

iexceptionfilter

handleerrorattribute

用于处理某个动作方法或某个控制器里面抛出的异常

在控制器方法抛出异常时执行

这四种类型的接口是mvc对过滤器的一个接口规范,同时mvc默认通过authorizeattribute(授权)、handleerrorattribute(异常处理)、actionfilterattribute(动作和结果)三个类实现了这四个接口。

需要注意的是actionfilterattribute类既实现了iactionfilter接口,也实现了iresultfilter接口。这是个抽象类,要求必须提供一个实现,authorizeattribute和handleerrorattribute类则包含了一些有用的特性,可以不必创建派生类进行使用。所以我们一般都会通过继承actionfilterattribute类,实现自定义的过滤器。

除以上接口之外,我们还要用到filterattribute类,这个类将我们的过滤器包装成了特性,使我们的过滤器可以方便的在action方法上方使用。

定义过滤器

过滤器有以下几个特点:

  • 可用于动作方法(action)

  • 可用于控制器(controller)

  • 可多个filter同时使用

  • 不同级别可以混搭

  • 可运用于基类的过滤器,会影响该基类的所有派生类

下面我们逐一介绍下基本过滤器的使用方法。

授权过滤器

所有实现了iauthorizationfilter接口的都可以称之为授权过滤器。它的接口定义如下:

namespace system.web.mvc
{
    //
    // 摘要:
    //     定义授权筛选器所需的方法。
    public interface iauthorizationfilter
    {
        //
        // 摘要:
        //     在需要授权时调用。
        //
        // 参数:
        //   filtercontext:
        //     筛选器上下文。
        void onauthorization(authorizationcontext filtercontext);
    }
}

授权过滤器是最先运行的过滤器,它运行在其它过滤器和action方法之前。客户端请求在调用action之前,mvc框架会检测action上是否有授权过滤器,如果有会调用onauthorization方法,如果此方法批准了请求,才会调用相应的action。流程如图:

mvc默认使用authorizeattribute实现了iauthorizationfilter接口,所以我们可以在action方法上直接添加authorize特性标签来验证授权:

打开index页面,会显示无权限:

由于使用的是mvc自带的授权验证方法,未能符合它的验证机制,所以无权限查看。通常我们需要添加一个新的派生自authorizeattribute类的授权过滤器来完成我们自己业务逻辑。

下面我们自定义一个授权过滤器。我们在mvc项目中添加一个filters文件夹,我们所有自定义的过滤器都可以放到这个文件夹下,便于管理。

在filters下创建一个类,类名为myauthorizeattribute。需要注意,过滤器要以attribute结尾,这是mvc的约定。代码如下:

public class myauthorizeattribute : authorizeattribute
{
    //重写授权检查方法,返回值为true,允许访问,false,禁止访问。
    protected override bool authorizecore(httpcontextbase httpcontext)
    {
        //请求参数user为空,禁止访问
        if (string.isnullorempty(httpcontext.current.request.querystring["user"]))
        {
            return false;
        }
        return true;
    }
}

可以看到,我们只要重写authorizecore方法就可以根据我们的业务需求判断是否有权限访问,返回值为true允许访问,返回值为false禁止访问。

回到homecontroller,我们给about方法加上我们自定义的特性:

我们看看效果:

可以看到,当about页面没有user参数时,会提示无权限,有user参数则可以访问通过。

在实际业务中我们可以使用授权过滤器来管理用户登录状态的授权验证。当然,我的这个例子只是基础的用法,实际业务比这复杂的多,那么就需要我们自己去思考设计授权过滤器方法了。

动作过滤器

动作过滤器需要实现iactionfilter接口,接口定义如下:

//
// 摘要:
//     定义操作筛选器中使用的方法。
public interface iactionfilter
{
    //
        // 摘要:
        //     在执行操作方法后调用。
        //
        // 参数:
        //   filtercontext:
        //     筛选器上下文。
        void onactionexecuted(actionexecutedcontext filtercontext);
    //
        // 摘要:
        //     在执行操作方法之前调用。
        //
        // 参数:
        //   filtercontext:
        //     筛选器上下文。
        void onactionexecuting(actionexecutingcontext filtercontext);
}

我们看到该接口里有两个方法onactionexecuting和onactionexecuted,前者在动作方法执行前调用,后者在动作方法执行后调用。

onactionexecuting方法是在action方法执行前调用的,那么我们可以利用这个方法来检测请求,并且可以在这里修改请求,取消请求等等操作。

onactionexecuting方法的参数是一个actionexecutingcontext对象,它继承自controllercontext类,属性如下:

名称

类型

说明

actiondescriptor

actiondescriptor

获取或设置操作描述符。

actionparameters

idictionary<string, object>

获取或设置操作方法参数。

result

actionresult

获取或设置由操作方法返回的结果。

我们添加一个自定义的action过滤器。由于actionfilterattribute类实现了iactionfilter接口,所以我们直接继承actionfilterattribute类即可,并且重写onactionexecuting和onactionexecuted方法。如下:

public class myactionattribute : actionfilterattribute
{
    /// <summary>
    /// action调用之前运行
    /// </summary>
    /// <param name="filtercontext"></param>
    public override void onactionexecuting(actionexecutingcontext filtercontext)
    {
        if (string.equals(filtercontext.httpcontext.request.httpmethod, "get", stringcomparison.currentcultureignorecase))
        {
            filtercontext.result = new httpnotfoundresult("只允许post请求!");
        }
    }
    /// <summary>
    /// action调用之后运行
    /// </summary>
    /// <param name="filtercontext"></param>
    public override void onactionexecuted(actionexecutedcontext filtercontext)
    {

    }
}

我们给index方法添加上myaction特性标签:

打开index页,显示如下:

可以看到,页面返回了404错误,提示信息为我们设置的message。

onactionexecuted方法在action操作方法调用之后执行,传递给onactionexecuted方法的参数是actionexecutedcontext对象。这个类比actionexecutingcontext对象多了些属性,如下:

名称

类型

说明

actiondescriptor

actiondescriptor

获取或设置操作描述符。

canceled

bool

获取或设置一个值,该值指示此actionexecutedcontext 对象已被取消。

exception

exception

获取或设置在操作方法的执行过程中发生的异常(如果有)。

exceptionhandled

bool

获取或设置一个值,该值指示是否处理异常。

result

actionresult

获取或设置由操作方法返回的结果。

我们可以通过onactionexecuted方法来执行一些跨越动作方法的任务,比如我们可以用它来获取动作方法执行的时间。我们修改 myactionattribute 过滤器代码如下:

public class myactionattribute : actionfilterattribute
{
    private stopwatch timer;

    /// <summary>
    /// action调用之前运行
    /// </summary>
    /// <param name="filtercontext"></param>
    public override void onactionexecuting(actionexecutingcontext filtercontext)
    {
        timer = stopwatch.startnew();
    }
    /// <summary>
    /// action调用之后运行
    /// </summary>
    /// <param name="filtercontext"></param>
    public override void onactionexecuted(actionexecutedcontext filtercontext)
    {
        timer.stop();
        filtercontext.httpcontext.response.write($"<div>方法执行时间:{timer.elapsed.totalseconds:f6}s</div>");
    }
}

我们在方法启动之前启动了一个计时器,在方法执行后停止了它,并且将这个时间间隔输出到我们的页面上。重新编译打开index页面,显示如下:

结果过滤器

结果过滤器,顾名思义针对的是动作方法返回的结果,它在我们的动作方法结果返回前后执行。

创建结果过滤器需要实现iresultfilter接口。actionfilterattribute类帮我们实现了iresultfilter接口,我们可以直接继承actionfilterattribute创建我们的过滤器,然后通过重写onresultexecutin和onresultexecuting(在执行操作结果后调用)方法来实现过滤器规则。

onresultexecuting方法会在执行操作结果前调用,这个方法的参数是一个resultexecutingcontext对象,属性如下:

名称

类型

说明

cancel

bool

获取或设置一个值,该值指示此 resultexecutingcontext 值是否为“cancel”。

result

actionresult

获取或设置操作结果。

onresultexecuted方法在执行操作结果后调用,这个方法的参数是一个resultexecutingcontext对象,属性如下:

名称

类型

说明

canceled

bool

获取或设置一个值,该值指示此 resultexecutingcontext 值是否为“cancel”。

exception

exception

获取或设置在操作方法的执行过程中发生的异常(如果有)。

exceptionhandled

bool

获取或设置一个值,该值指示是否处理异常。

result

actionresult

获取或设置操作结果。

我们可以使用这两个方法在action方法返回结果前后进行操作,具体操作的代码我就不赘述了。

异常处理过滤器

异常处理过滤器需要实现的接口为iexceptionfilter。我们看下接口的定义:

//
// 摘要:
//     定义异常筛选器所需的方法。
public interface iexceptionfilter
{
    //
    // 摘要:
    //     在发生异常时调用。
    //
    // 参数:
    //   filtercontext:
    //     筛选器上下文。
    void onexception(exceptioncontext filtercontext);
}

接口方法onexception可以看到它在我们的方法中出现异常时触发,mvc默认用handleerrorattribute类来实现了此接口,我们自己定义的异常过滤器可以继承此类进行扩展。

onexception方法中传递的参数是一个exceptioncontext对象,它的属性如下:

名称

类型

说明

exception

exception

获取或设置异常对象。

exceptionhandled

bool

获取或设置一个值,该值指示是否已处理异常。

result

actionresult

获取或设置操作结果。

我们来定义一个异常过滤器,代码如下:

public class myexceptionattribute : handleerrorattribute
{
    public override void onexception(exceptioncontext filtercontext)
    {
        //如果有异常,跳转到异常页面。
        if (filtercontext.exception != null)
        {
            //跳转到自定义的错误页
            actionresult view = new viewresult() { viewname = "error" };
            filtercontext.result = view;
            //异常处理结束后,一定要将exceptionhandled设置为true,否则仍然会继续抛出错误。
            filtercontext.exceptionhandled = true;
        }
    }
}

当我们的方法中出现异常时,会将views文件夹下shared中的error.cshtml页面返回到客户端,客户端页面不再显示成黄页,给用户一个良好的体验。

我们给index方法添加上我们的异常处理器,再加一段引发异常的代码看下效果。

打开index视图:

视图中显示的是error页面中的内容。

通常我们用异常处理器来记录我们的程序异常日志,或者在产生异常时给客户端返回一个友好的提示内容。

过滤器的使用方法

在上文的一些例子中,我们把过滤器的特性都定义在了action方法上,其实过滤器不仅可以应用在action方法中,还可在应用在controller和全局配置中。

应用在controller中的使用方法和action一直,在controller类名上方添加特性标签即可。如图:

全局环境下的过滤器,则需要注册到filterconfig文件中,例如mvc默认给我们注册的handleerrorattribute异常处理器:

并且我们可以注册很多个过滤器在全局环境下,那么在此注册的过滤器会应用到整个应用程序当中。

总结

本章对过滤器的类型,作用,定义以及使用方法做了一些说明,当然这些都是比较基础的内容,真正深入的理解还得多多使用,如果文章中有错误或者不足的地方,请大家在评论中指正出来。

更多内容可访问我的博客: