Asp.Net MVC 系列--进阶篇之Filter

使用Filter

 

Filter是很好的实现crosscutting concern 的方式,常见的crosscutting concern包括log,验证,缓存,异常处理(笔者推荐postsharp),等等。

 

什么是crosscutting concern?

为了更好的专注业务的实现,降低耦合,提高内聚。因此把这些concern从业务中抽离出来解决,可以更好的维护,更改和扩展。它们也构成了架构中cross-cutting的部分,可以在与业务隔离的情况下,统一编码,测试,修改。

 

言归正传--Filter

 

例如AdminController :

public class AdminController : Controller {
// ...instance variables and constructor
public ViewResult Index() {
if(!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}

}
public ViewResult Create() {
if(!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
}
public ViewResult Edit(int  productId) {
if(!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
}
}


 

我们的目的很明确,我需要限制这个controller的访问,只有登录的才能访问,那么我们每个action都要加上Request.IsAuthenticated.如果需要限制role,指定用户才能访问,我们就要写一个验证函数,并修改全部的action。

应用了Filter的实现

[Authorize]
public class AdminController : Controller {
public ViewResult Index() {
}
public ViewResult Create() {
}
public ViewResult  Edit(int  productId) {
}
}


 

好处:可以看到filter提高了代码的可读性,减少了重复。

FilterAttribute的逻辑注入在http pipeline里面,在controller之前,反射出所有controller,查找customize的attribute,如果有加Authorize,调用相应的login验证函数(后续章节会介绍),验证不通过则返回401 。

 

关于Attribute

Attribute是.net 中很好应用装饰模式的例子,通过继承自System.Attribute,可以实现自己的attribute,包括class,method,property,field。目的是动态的注入property,member,和ability到相应的类中,编译之后,生成的IL已经注入了attribute要提供的能力。通常的AOP框架中有attribute和IL wave 一起使用的代码和使用方法。不熟悉的读者可以查阅相关MSDN,本章所有内容都建立在attribute有一个基本的认识上的。

 

MVC中四种基本的filter

Authorize Filter

验证。进入controller或action之前

Action Filter

进入action前后会被调用

Result Filter

Execute result的前后会被调用

Exception Filter

Filter,action,或者execute result时发生异常时被触发

 

给controller和action加 Filter:

MVC中的authorizefilter除了可以加在class上(前面演示过了),还可以针对action做filter:

public class AdminController : Controller {
 [Authorize]
public ViewResult Index() {
}
 [Authorize]
public ViewResult Create() {
}
}


也可以apply多个filter:

[Authorize(Roles="trader")]
public class ExampleController : Controller {
[ShowMessage]// applies to just this action
[OutputCache(Duration=60)]// applies to just this action
public ActionResult  Index() {
// ...action method body
}
}


Customize filter

代码:

 public class BlockAttribute : AuthorizeAttribute { 
public BlockAuthAttribute() { 
} 
protected override bool AuthorizeCore(HttpContextBase httpContext) { 
return false;
} 
}


 

作为演示,实现了一个block attribute,加上这个attribute会block所有的request。现实场景中,拿到了httpContext可以做很多事情,因为里面的对象包含的信息非常丰富。

 

使用:

[Block]
public string Index() {
return "Always block ";
}


 

访问页面会发现:


由于我创建的是basic的项目,没有加login view在Account,因此找不到login。但是filter已经生效了。

当使用AuthorizeAttribute,还有Users和Roles filter也很常用,使用方法:


[Authorize(Users= "iori", Roles = "admin")]
public string Index() {
return "This is the Index action on the Home controller";
}



这条filter就限制了访问这个action,需要user为iori,并且role为admin,关于MVC Authorize使用细节,如何完成验证,完成AccountController,会有专门一章介绍,在此只演示MVC有这样一个filter可以使用。

 

Exception Filter

 

Customize Exception Filter

需要实现接口:

public interface IExceptionFilter {
void OnException(ExceptionContextfilterContext);
}


FilterContext对象包含:

Controller

发生异常时的Controller Object

HttpContext

当前HttpContext对象

IsChildAction

是否为子action

RequestContext

HttpContext对象

RouteData

返回当前request的route数据

ActionDescriptor

Action 方法的描述

Result

Action method的result,可以修改result

Exception

异常对象

ExceptionHandled

如果其他filter已经handle,则为true

 

发生exception时候,拿到以上信息,除了记log,还可以做的事情:

 

页面跳转,跳转到错误页面

直接给客户端返回response

重写result返回给action

 

实现:

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter { 
public void OnException(ExceptionContext filterContext) { 
if (!filterContext.ExceptionHandled && 
filterContext.Exception is ArgumentOutOfRangeException) { 
filterContext.Result 
= new RedirectResult("~/Content/RangeErrorPage.html"); 
filterContext.ExceptionHandled = true; 
} 
} 
}


代码做的事情很简单,判断异常是否被handle,如果没有并且是argumentOutOfRange类型的exception,跳转到一个友好的错误页面。

错误页面代码:

<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>Range Error</title> 
</head> 
<body> 
<h2>Sorry</h2> 
<span>One of the arguments was out of the expected range.</span> 
</body> 
</html>


使用这个Exception filter:

[RangeException]
        public ActionResult Index()
        {
            throw new ArgumentOutOfRangeException();
        }


验证:


 

使用自带的Exception Filter – HandleErrorAttribute

ExceptionType

Exception type will be handled by this filter

View

View Name

Master

Layout Name

 

Web config 配置 default redirect


<customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>



Apply filter


[HandleError(ExceptionType= typeof(ArgumentOutOfRangeException), View = "RangeError")]
public ActionResult Index()
        {
            throw new ArgumentOutOfRangeException();
        }


RangeErrorView中会收到HandleErrorInfo 对象:

Action Name

发生异常的action name

Controller Name

发生异常的controller name

Exception

Exception对象

 

View 代码:

@model HandleErrorInfo
@{
ViewBag.Title= "Sorry, there was a problem!";
}
<!DOCTYPEhtml>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RangeError</title>
</head>
<body>
<h2>Sorry</h2>
<span>The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue)
was out ofthe expected range.</span>
<div>
@Html.ActionLink("Change value and try again", "Index")
</div>
<div style="display: none">
@Model.Exception.StackTrace
</div>
</body>
</html>


 

Action filter

接口:

public interface IActionFilter {
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}


OnActionExecuting

在action执行前被调用

OnActionExecuted

在action执行完毕被调用(返回值之前)

 

CustomizeAction Attribute 实现

public class ActionTestAttribute : FilterAttribute, IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
           filterContext.HttpContext.Response.Write("<br/>Written fromAction Filter.Should Before Execute action body");
        }
        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
           filterContext.HttpContext.Response.Write("<br/>Written fromAction filter,Should After Execute action body.");
        }
    }


 

目的很显然,在ActionExecuting和ActionExecuted分别打印一行字。

 

应用这个Filter

 
         [ActionTest]
        public ActionResult Index()
        {
		Response.Write("<div>ActionBody</div>");
            return Content("<br/>Action Result.");
        }



验证结果:


可以看到执行顺序:

·        ActionFilter Executing

·        Action body

·        ActionFilter Executed

·        Action body return result

 

 

参数常用对象

 

ActionExecutingContext对象:

Action Descriptor

Action的描述

Result

返回值

HttpContext

当前的HttpContext对象

 

ActionExecutedContext稍有不同,我们能拿到更多的object

ActionDescriptor

Action描述

Result

Action Result

Canceled

如果action已经被(其他filter)cancel会被标记为true

Exception

异常对象

ExceptionHandled

异常是否被handle了

HttpContext

当前HttpContext对象

 

 

Result Filter

接口

 

public interface IResultFilter {
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}


代码实现:

  public class ResultTestAttribute :FilterAttribute, IResultFilter
    {
        private Stopwatch timer;
        public void OnResultExecuting(ResultExecutingContext filterContext)
        {
           filterContext.HttpContext.Response.Write("<br/>----------StartExecuting Result.--------<br/>");
            timer = Stopwatch.StartNew();
        }
        public void OnResultExecuted(ResultExecutedContext filterContext)
        {
           timer.Stop();
           filterContext.HttpContext.Response.Write(
           string.Format("<div>Result elapsed time:{0}</div>",
            timer.Elapsed.TotalSeconds));
        }
}


 

开始执行Result时,开启一个stop watch,在executed关闭,为了计时执行result用了多少时间,同时在executing和executed分别打印一行字。

应用这个filter:

  [ResultTest]
       [ActionTest]
       public ActionResult Index()
        {
	Response.Write("<div>ActionBody</div>");
           return Content("<br/>Return Action Result.");
        }


和刚才演示的actionfilter一起应用,正好看一下执行的顺序。

 

验证结果:


可见执行顺序:

2.      ActionExecuting

3.      Actionbody

4.      ActionExecuted

5.      ResultExecuting

6.      Actionbody return result

7.      Resultexecuted

 

使用自带的ActionFilterAttribute

ActionFilter实际提供了四个virtual的函数,都是刚刚提到的:

public virtual void OnActionExecuting(ActionExecutingContext filterContext) {
}
public virtual void OnActionExecuted(ActionExecutedContext filterContext) {
}
public virtual void OnResultExecuting(ResultExecutingContext filterContext) {
}
public virtual void OnResultExecuted(ResultExecutedContext filterContext) {
}


继承这个类,根据实际场景的需要,我们可以选择性的override其中的几个

 

代码实现

public class ActionResultTestAttribute :ActionFilterAttribute
    {
       public override void OnActionExecuting(ActionExecutingContextfilterContext)
        {
           filterContext.HttpContext.Response.Write("<br />[ActionFilterUsage] Action Executing...");      
        }
 
       public override void OnResultExecuted(ResultExecutedContextfilterContext)
        {
           filterContext.HttpContext.Response.Write("<br />[ActionFilterUsage] Result Executed");
        }
 
    }


 

以上代码演示了继承ActionFilterAttribute,重写OnActionExecuting和OnResultExecuted这两个函数,为了验证看的清楚,打印时加了特殊的标记。

使用:

[ActionResultTest]
       [ResultTest]
       [ActionTest]
       public ActionResult  Index()
        {
           Response.Write("<div>Action Body</div>");
           return Content("<br/>Return Action Result.");
        }


 

验证结果:


可以看到执行顺序:

·        Action filter 中的Action Executing

·        自定义的Action Executing

·        Action Body

·        自定义的Action Executed

·        自定义的Result Executing

·        Action Result

·        自定义的Result Executed

·        Action Filter中的Result Executed

给controller加Action,ResultExecuting, Executed

由于controller基类已经提供了virtual,因此直接重写就好了:

public class TestController : Controller
    {
       //
       // GET: /Test/
 
       public ActionResult Index()
        {
           Response.Write("<div>Action Body</div>");
           return Content("<br/>Return Action Result.");
        }
       protected override void OnActionExecuting(ActionExecutingContextfilterContext)
        {
           Response.Write("[From Controller ]: On action Executing.");
        }
       protected override void OnResultExecuted(ResultExecutedContextfilterContext)
        {
           Response.Write("[From Controller ] : On Result Executed.");
        }
    }


 

验证结果:



可以看到controller级别的filter生效了。虽然controller提供了virtual使得我们可以重写来完成controller级别的filter,但是仍然建议自定义或者使用默认的actionfilter。

 

使用全局filter

 

1.      打开App_Start 目录的filter config


 

让我们把刚才自定义的ActionTestFilter变成全局的:

 

public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(newHandleErrorAttribute());
            filters.Add(newActionTestAttribute());
        }
}


 

这样一来,任何一个Action的执行,都会调用刚才定义的ActionTestFilter。

                Action代码(记得拿掉ActionFilter以及刚才controller重写的):

  public ActionResult Index()
        {
           Response.Write("<div>Action Body</div>");
            return Content("<br/>Return Action Result.");
        }


 

验证:


可见,自定义filter已经成功被加在全局。

 

Filter 排序

这个功能只针对Multiple Filter (也就是定义Attribute的时候,要AllowMultiple)

 

例子:

 

[AttributeUsage(AttributeTargets.Method,AllowMultiple = true)]
    public class SomeMessageAttribute :FilterAttribute, IActionFilter
    {
        public string Message { get; set; }
        public void OnActionExecuting(ActionExecutingContext  filterContext)
        {
           filterContext.HttpContext.Response.Write(
           string.Format("<div>[Before Action]  [Message: <{0}>]<div>", Message));
        }
 
        public void OnActionExecuted(ActionExecutedContext  filterContext)
        {
           filterContext.HttpContext.Response.Write(
               string.Format("<div>[After Action] [Message:  <{0}>]<div>", Message));
        }
}


 

代码说明:在executing和executed分别打印出message。

应用这个filter(为了分开演示清楚,记得把刚才全局filter拿掉):

[SomeMessage(Message = "A",Order =1)]
       [SomeMessage(Message = "B",Order = 2)]
       [SomeMessage(Message = "C",Order = 3)]
       public ActionResult Index()
        {
           Response.Write("<div>Action Body</div>");
           return Content("<br/>Return Action Result.");
        }


我们加了三个filter,并安排了执行顺序。

查看结果:


可见按着我们期望的结果,现在调整一下顺序:

[SomeMessage(Message = "A",Order = 3)]
       [SomeMessage(Message = "B",Order = 1)]
       [SomeMessage(Message = "C",Order = 2)]


验证结果:

期望结果是B – C – A

查看结果:


 

其他一些常用filter

 

RequireHttps:限制只接受https协议

OutputCache : 和Web Form中的使用方法类似,传递一个duration,就可以享受页面缓存了

Post:限制请求类型必须为post

Get:限制请求类型必须为Get

 

 

 

Asp.Net MVC 系列--进阶篇之Filter,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。