在深入研究如何编写过滤器之前,首先看看包含在ASP.NET MVC中的过滤器。
ASP.NET MVC包括了如下3种即开即用的动作过滤器:
Authorize:该过滤器用于限制对控制器或控制器动作的访问。
HandleError:该过滤器用来指定一个处理异常的动作,这个异常是从动作方法的内部抛出的。
OutputCache:该过滤器用来为动作方法提供输出的缓存。
接下来将依次深入讨论这3个过滤器。
1 Authorize
AuthorizeAttribute是包含在ASP.NET MVC中默认的授权过滤器。可以使用它来限制对动作方法的访问。将该特性运用到控制器上可以迅速将其运用到每个动作方法中。
在运用该过滤器时需要牢记如下内容:
在运用该特性时,可以指定一个逗号来划分角色(Role)或用户(User)的列表。如果指定了一个角色的列表,那么为了动作方法的执行,用户必须是其中一个角色中的成员。同样的,如果指定了一个用户的列表,那么当前用户的名称必须在该列表中。
为什么不使用已有的、构建在ASP.NET中的URL授权
保护使用了Web Forms的应用程序的常见方式是使用URL授权。例如,如果具有一个管理部分,而且希望将其限制为那些位于管理员(Admins)角色中的用户,那么可能会把所有管理页面都放在管理文件夹中并拒绝除位于管理员角色中以外的任何人访问子文件夹。
对于MVC而言,这种方法不会很好地运转,原因有两个:
请求不再映射到物理目录中。
进入到同一个控制器的路由可能不止一个。
对于MVC而言,理论上讲,是可以使用AdminController封装应用程序的管理功能,然后在web.config根文件中设置URL授权,进而阻止访问任何以/Admin开头的请求。然而,这并不是万无一失的,也有可能存在另外一个路由,它可能在不经意间映射到AdminController中。
例如,后面将会讨论的,如果决定切换默认路由中的{controller}和{action}的顺序,那么此时,/Index/Admin是默认的管理页面的URL,而且URL授权也不再阻止它。
安全性较好的一个方法是总是尽可能紧紧地将安全检查放到需要保护的对象上。虽然可能会在更高层次的堆栈上具有其他检查,但是最终希望保护的是实际的资源。在这种情况下,不希望依靠路由选择和URL授权来保护控制器;真正想要保护的是控制器自身。AuthorizeAttribute正好用于此用途。
如果没有指定任何角色或用户,那么为了调用动作方法,必须只验证当前的用户。这是阻止非验证用户访问特殊控制器动作的一个简单的方法。
如果用户试图访问运用了该特性的动作方法且在授权检查中失败,那么过滤器将引发服务器返回一个401 Unauthorized的HTTP状态代码。
对于启用了表单验证且在web.config中指定了注册URL的情形,ASP.NET将处理该响应代码并将用户重新引向注册页面。这是ASP.NET已有的行为,对于ASP.NET MVC也不是新鲜事物。
产品小组的话:
一开始,我们将PrincipalPermissionAttribute看作是保护控制器动作的一个可行的解决方案,但是碰到了很多问题。首先,在将其运用到类时,PrincipalPermissionAttribute将在安全检查失败而试图实例化Controller类时导致一个异常。因为我们希望当安全检查失败时可能会有其他过滤器运行(例如,日志记录过滤器),所以这并不是我们想要的行为。
其次,我们希望能控制产生的状态代码。而PrincipalPermissionAttribute只是抛出一个SecurityException。
下面看一个使用的简单示例。在下面的代码中,管理控制器只限制于Admins和SuperAdmins角色的成员。注意,角色是通过逗号隔开的。过滤器将忽略逗号之间的空白以允许提高在运用该特性时的可读性。
- [Authorize(Roles="Admins, SuperAdmins")]
- public class AdminController
- {
- //Only admins should see this.
- public ActionResult Index()
- {
- return View();
- }
- //Only admins should see this.
- public ActionResult DeleteAllUsers()
- {
- //Thankfully, this is secured by the Authorize attribute.
- }
- }
在认真思考上述示例之后,本书作者认识到我们并不希望任何管理员或系统管理员(superadmin)能够调用DeleteAllUsers的动作。事实上,我们只信任由Phil完成该动作,所以这里运用了一个更为具体的授权,即只允许用户Phil来调用该动作。当运用了多个授权过滤器时,用户为了调用动作必须满足所有的授权过滤器。因此,在这种情况下,Phil必须同时是管理员(Admins)角色的成员也是系统管理员(SuperAdmins)角色的成员。
注意:
另一件需要注意的事情是,一般来说:甚至在类似情况下,使用角色而不是具体的用户名将更有意义。更好的方法可能是创建一个名为CanDeleteAllUsers的角色并将Phil添加到该角色中,然后运用一个指定了该角色的授权过滤器。
- [Authorize(Roles="Admins, SuperAdmins")]
- public class AdminController
- {
- //Only admins should see this.
- public ActionResult Index()
- {
- return View();
- }
- //Only Phil should do this.
- [Authorize(Users="Phil")]
- public ActionResult DeleteAllUsers()
- {
- //…
- }
- }
在最后的示例中,切换到一个为个人用户提供用户管理的控制器中;不需要指定用户和角色。在这种情况下,控制器可能向所有通过验证的人打开大门。
- [Authorize]
- public class UsersController
- {
- public ActionResult ManageProfile()
- {
- //…
- return View();
- }
- }
2.OutputCache
OutputCacheAttribute用来缓存动作方法的输出。该特性是到ASP.NET输出缓存功能内的连接,而且提供了在使用@OutputCache页面指令时得到的大部分相同的API和行为。本章后面将讨论两者之间微小的区别。
因为输出缓存是ASP.NET非常著名的功能,所以本节没有深入讨论缓存行为自身的细节,而是将注意力集中于MVC实现上。此时,可能会有一个问题,"为什么不在视图中只使用@OutputCache指令呢?"
对于MVC而言,首先在选中视图之前,执行控制器动作。因此,将输出缓存放到视图上,这实际上将缓存视图的输出,而动作方法自身将仍然在每个请求上执行,这样就否定了缓存输出的大部分优点。
通过将该特性运用到动作方法中,过滤器随后可以确定缓存是否有效并跳过动作方法的调用,直接呈现缓存的内容。
API
表8-1列出了OutputCacheAttribute类的属性。这些设置用来控制OutputCache过滤器如何执行其缓存动作。
表8-1 OutputCacheAttribute类的属性
属 性 |
说 明 |
CacheProfile |
即将使用的缓存设置的名称。它允许将缓 存的配置放到web.config文件中而不是 特性中。随后,该特性可以通过该属性 来引用配置设置 |
Duration |
指定了将输出存储到缓存中的秒数 |
Location |
指定了可能缓存内容的地方。枚举 OutputCacheLocation包含了允许的位置: Any、Client、Downstream、Server、 None、ServerAndClient等 |
NoStore |
将HTTP题头“Cache-Control: Private, no-store”设置为阻止浏览器缓存响应。 它等价于调用Response.Cache.SetNoStore |
(续表)
属 性 |
说 明 |
SqlDependency |
特殊格式化的字符串值,包含了一组 输出缓存所依赖的数据库和表的名称 对。当这些表中的数据发生了更改, 则缓存失效 |
VaryByContentEncoding |
在ASP.NET 3.5中引入,这是以逗号 分隔的内容编码的列表,用于变更缓存 |
VaryByCustom |
确定是否基于对Global.asax.cs文件中 的GetVaryByCustomString的调用缓存 新版本的输出。这为开发人员提供了 在缓存时完全的控制 |
VaryByHeader |
基于http题头改变缓存。例如,可能使 用它基于Accept-Language题头缓存 不同版本的输出 |
VaryByParam |
用于指定哪一个QueryString参数导 致了一个新版本的输出被缓存 |
与@OutputCache指令之间的区别
由于应用程序开发的ASP.NET MVC模型与Web Forms模型之间的区别,所以还有一个选项不会翻译成输出缓存的过滤器,即VaryByControl属性。因此,该属性在OutputCacheAttribute中是没有的。
使用示例
下面将讨论输出缓存过滤器常见的使用模式。在很多情况下,可能希望仅缓存动作的输出一段很短的时间。在下面的代码示例中,缓存了默认的About动作的输出,持续时间为60秒。修改方法的实现以显示时间:
- [OutputCache(Duration=60, VaryByParam="none")]
- public ActionResult About()
- {
- ViewData["Title"] = "This was cached at " + DateTime.Now;
- return View();
- }
在考虑前面的示例时,我们希望不必每次修改持续时间时就重新编译代码。相反,可能希望在配置文件中设置持续时间。
幸运的是,在web.config中已经存在一个输出缓存的设置部分。下面的样本阐述了如何将缓存设置添加到web.config中:
- <system.web>
- <caching>
- <outputCacheSettings>
- <outputCacheProfiles>
- <add name="MyProfile" duration="60" varyByParam="none" />
- </outputCacheProfiles>
- </outputCacheSettings>
- </caching>
- </system.web>
注意,添加了一个名为MyProfile的缓存配置文件,现在可以修改输出缓存过滤器来通过CacheProfile属性读取该配置文件的设置:
- [OutputCache(CacheProfile="MyProfile")]
- public ActionResult About()
- {
- ViewData["Title"] = "This was cached at " + DateTime.Now;
- return View();
- }
3 异常过滤器
HandleErrorAttribute是包含在ASP.NET MVC中的默认异常过滤器。如果动作方法抛出一个未处理的异常,且该异常与指定的异常类型匹配或源自它,那么就可以使用该过滤器指定需要处理的异常类型以及需要显示的视图(如果需要的话,还包括主视图)。
默认情况下,如果没有指定异常类型,那么过滤器将处理所有的异常。如果没有指定视图,那么过滤器将默认处理名为Error的视图。默认的ASP.NET MVC项目在Shared文件夹中包含了一个名为Error.aspx的视图。
请看下面的示例:
- [HandleError(ExceptionType = typeof
(ArgumentException), View="ArgError")] - public ActionResult GetProduct(string name)
- {
- if(name == null)
- {
- throw new ArgumentNullException("name");
- }
- return View();
- }
因为ArgumentNullException继承自ArgumentException,所以将null传递到该动作方法中将导致显示ArgError视图。
在一些情况下,可能希望将多个异常过滤器运用到同一个动作方法中。那么,为了能将最具体的异常类型放在最前面而将较不明确的放在后面,就需要指定这些情况的顺序,这是很重要的。例如,在下面的代码片断中:
- //This is WRONG!
- [HandleError(Order=1, ExceptionType=typeof(Exception))
- [HandleError(Order=2, ExceptionType=typeof(ArgumentException),
- View="ArgError")]
- public ActionResult GetProduct(string name)
- {
- …
- }
第一个过滤器要比其后的过滤器更为通用,它将处理所有的异常,而始终不会给第二个过滤器任何机会来处理异常。为了修复此问题,只需将过滤器从最具体到最不具体进行排序。
- //This is BETTER!
- [HandleError(Order=1, ExceptionType=typeof(ArgumentException),
- View="ArgError")
- [HandleError(Order=2, ExceptionType=typeof(Exception)]
- public ActionResult GetProduct(string name)
- {
- …
- }
当该异常过滤器处理一个异常时,它创建了一个HandleErrorInfo类的实例并设置在呈现Error视图时ViewDataDictionary实例的Model属性。
表8-2展示了HandleErrorInfo类的属性:
表8-2 HandleErrorInfo类的属性
属 性 |
说 明 |
Action |
抛出异常的动作的名称 |
Controller |
在其中抛出异常的控制器的名称 |
Exception |
抛出的异常 |
注意,该过滤器没有捕捉到在没有启用自定义错误时在调试构建中的异常。过滤器只是检查HttpContext.IsCustomErrorEnabled以确定是否处理该异常。这样做的理由是允许通过信息更丰富的Yellow Screen of Death来显示开发阶段与异常有关的信息。
转自:http://book.51cto.com/art/201006/205387.htm