快速入门系列--MVC--05行为

    Action执行包含内容比较多,主要有同步/异步Action的概念和执行过程,Authorationfilter, ActionFiltor, ResultFilter, ExceptionFilter等四个主要过滤器类型的执行过程。首先介绍异步的Action,之前学习Controller的时候已经知道默认情况下Controller的执行是异步的,在不继承异步Controller的情况,我们代码中的方法一般是同步的Action,我们可以通过使用Task<ActionResult>类型的返回值和在Action方法使用Task.Factory.StartNew()等方法来调用异步的Action,主要用于I/O绑定等操作。ASP.NET是通过线程池的机制来处理并发的HTTP的请求的,这种方式的优点是:工作线程的重用,减少线程的创建和释放;限制工作线程数量,避免高并发时服务器的崩溃。这里省略MVC4版本前的老式异步Action调用,Task返回值的Action如下所示:

 1 public Task<ActionResult> Article(string name)
 2 {
 3 return Task.Factory.StartNew(() =>
 4 {
 5 string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\articles\{0}.html", name));
 6 using (StreamReader reader = new StreamReader(path))
 7 {
 8 AsyncManager.Parameters["content"] = reader.ReadToEnd();
 9 }
10 }).ContinueWith<ActionResult>(task =>
11 {
12 string content = (string)AsyncManager.Parameters["content"];
13 return Content(content);
14 });
15 }

在上代码中,可以看到一个AsyncManager类,它起到了在异步操作和回调操作间传递参数的作用。这是一个关于异步操作很重要的类型,其属性OutstandingOperatons是一个异步操作计数器,类似信号量的概念,用Increment设置初始值,当一个或多个异步操作完成时递减,为0时表示有所操作已完成,出发Completed事件,调用Finish方法。需要注意的细节是设置初始值的方法需要放在异步操作的外部,异步操作的超时时间可以通过AsyncTimeoutAttribute特性的Duration属性来设置。

接下来,介绍Action的执行过程,在Controller中,包括Model绑定和验证在内的整个Action的执行是通过一个名为ActionInvoker的组件来完成的,也包含同步异步两个版本,实现类为ControllerActionInvoker和AsyncControllerActionInvoker。这个简单介绍一下Controller在选择ActionInvoker时的步骤:通过DependencyResolver以IAsyncAcionInvoker查;以IActionInvoker查;创建异步类型作为默认。不同的ControllerActionInvoker会创建其对应的ControllerDescriptor实现类,包含对应类型的ActionDescriptor。还有一点需要注意的是,Dependency默认使用会将反射创建的对象缓存到CurrentCache属性中,而不会使用当前新设置的映射重新获取。若想在程序中修改,需要手动的清空CurrentCache所对应类型中的_cache字段,部分代码如下所示:

1 private void ClearCachedActionInvokers()
2 {
3 var property = typeof(DependencyResolver).GetProperty("CurrentCache", BindingFlags.NonPublic|BindingFlags.Static);
4 var cacheActionInvoker = property.GetValue(null, null);
5 FieldInfo field = cacheActionInvoker.GetType().GetField("_cache", BindingFlags.NonPublic|BindingFlags.Instance);
6 var dictionary = field.GetValue(cacheActionInvoker) as ConcurrentDictionary<Type, object>;
7 dictionary.Clear();
8 }

    在介绍筛选器的执行之前,再回顾一下相关过程,目标Action方法的最终执行是由被激活Controller的ActionInvoker决定,ActionInvoker通过调用对应的ActionDesciptor来执行被它描述的Action。筛选器使用面向切面概念(AOP)的实现,它会在在Action方法执行的前后自动执行,主要包含非业务逻辑的实现,例如授权,异常处理等。Filter作为基类包含FilterScope和Order属性,Scope包括First、Global、Controller、Action和Last,Order越小优先级越高,默认值为-1。同样Filter也有相应的Provider类,框架中原生的有FilterAttributeFilterProvider,ControllerInstanceFilterProvider和GlobalFilterCollection,简介如下表所示:

类型

简介

FilterAttribute, FilterAttributeFilterProvider

方法GetFilter得到的每个Filter,对应的FilterAttribute特性作为其Instance属性,Scope属性取决于FilterAttribute特性是应用在Controller类型上还是Action方法上。

Controller, ControllerInstanceFilterProvider

Controller实现了IActionFilter,IAuthorization等四接口,本身就是一个筛选器,通过ControllerInstanceFilterProvider类型来表示针对Controller对象这种特殊筛选器的Filter。它的GetFilter方法根据ControllerContext获得对应Controller,并作为Filter的Instance属性,其Scope为First,Order为Int32.MinValue,默认最先执行。

GlobalFilterCollection

全局的Filter通过GlobalFilter.Filters.Add方式来添加,默认Scope为Global。

    在筛选器的执行顺序上,遵循先Order排序,再Scope排序,若同一筛选器特性标注在不同Scope上且AllowMultiple为false时,会选中最后的一个执行。框架使用一个FilterInfo类型统一管理内置的筛选器,之后开始按照执行顺序详细介绍各个内置的筛选器。

AuthorizationFilter,实现IAuthorizationFilter的OnAuthoration方法用于实现授权操作,成功后继续Action后续工作(Model绑定,验证,Action的执行),失败后AuthorizationContext对象的Result属性回复一个"401,Unauthorized"相应或者重定向到错误页面。它所对应的几个实现IAuthorizationFilter接口的如下表所示:

类型

简述与例子

AuthrizeAttribute

多个Authorize特性间是"逻辑与"得关系,如下代码任何用户均无法访问。 [Authorize(Users = "Xixi", Roles="Admin")] [Authorize(Users = "XiongEr", Roles = "Admin")] public void CannotCall() { }

RequiredHttpsAttribute

要求请求为HTTPS形式(HttpRequest.IsSecureConnection),不满足时,如果是GET方式请求返回RedirectResult重定向请求,如果是其他方式抛出异常。

ValidateInputAttribute

在Controller, Action级别上针对整个请求决定输入参数是否进行验证。例如在QueryString中放入"<script></script>"

ValidateAntiForgeryTokenAttribute

防止CSRF(Cross-Site Request Forgery)跨站请求伪造网络攻击,如果说XSS(Cross Site Script)是利用了用户对网站的信任,那个CSRF就是利用了站点对认证用户的信任。在之后的内容中,将继续介绍CSRF的原理和框架的预防方法。

ChildActionOnlyAttribute

一般用于生成组成页面的某部分HTML,若非子Action则抛出异常。(通过DataTokens中是否包含ParentActionViewContext判断)

    接下来用蒋老师介绍的简单例子来解释CSRF的原理,假设我们奖励一个博客应用,作为博主的我们可以发表博文,而一般用户(包括匿名)可以评论。除此之外注册用户可以修改自己的绑定Email,我们将授权特性加在该Action上,看起来应该OK了,但仍然有漏洞可钻。

从上图可知,通过跳转攻击者获得用户安全令牌,通过了授权验证,说明CSRF是一种隐蔽且危害巨大的攻击,框架通过ValidateAntiForgeryTokenAttribute结合HtmlHelper的AntiForgeryToken方法有效解决了这个问题。在View中通过调用AntiForgeryToken方法,在页面中生一个值为防伪令牌字符串的hidden类型的<input>元素,并且设置一个具有HttpOnly的Cookie。防伪令牌值通过Salt,Creation,Username等内容计算得出。Cookie的名称通过应用路径base64编码值加上_RequestVerificationToken组合而成。对于加入防伪令牌的View在第一次访问或者Cookie不存在时,创建Cookie并设置HttpOnly标签,这样浏览器就无法通过脚本获得Cookie,保证了Cookie的安全。再次请求时,解密和反序列化Hidden与Cookie中相关值,比较属性即可。

ActionFilter之前介绍过的实现类包括AsyncTimeoutAttribute等,允许我们对Action执行前后添加一些额外操作,通过Result属性响应其请求。筛选器中OnActionExecuting与OnActionExecuted的执行顺序相反。正向执行时,一旦某一个ActionFilter将AcionExecuteingContext的Result设置为ActionResult对象,后续ActionFilter和目标Action将不会执行。而在逆向执行ActionFilter链时在ActionExecutedContext中设置Result不受影响,如下图所示:

    ActionFilter链的异常处理过程通过对应的上下文类的Exception对象传递,ExceptionHanlded属性表明异常是否已被处理。

ExceptionFilter既可以处理ActionFilter最终抛出的异常,还可以处理ResultFilter抛出的异常。其中实现类HanldeErrorAttribute用于针对具体的异常类型来呈现对应的错误页面。同时由于ExceptionFilter链的反向执行特性,需要设置Order属性使得具体的HanldeErrorAttribute优先执行。

    需要注意的一点是,HandleErrorAttribute只有在允许自定义错误时才有效, <customErrors mode="On"/>

蒋老师在书中提到,异常处理是程序员最熟悉也最难掌握的一块概念了,我确实也有这样的感受,比如说一个异常类型到底"谁来管,该怎么管,管不住怎么办",很像法制建设,需要一定的规定,但软件开发中还未有相关的通用规则。由于异常处理往往是场景驱动的,就需要一个灵活可配置的处理框架进行管理,例如微软企业库Entlib的Exception Handling Application Block(EHAB)。该库提供一种基于"策略"的异常处理方式。之后还提供了一个自动化处理异常的思路,即通过配置,自动生成try/catch过程。

简单来说就是:

异常处理策略=异常类型+异常处理器+异常后续处理方式,例子如下所示:

 1 <configSections>
 2 <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"/>
 3 </configSections>
 4 <exceptionHandling>
 5 <exceptionPolicies>
 6 <add name="data access policy">
 7 <exceptionTypes>
 8 <add type="System.Data.SqlClient.SqlException, System.Data" postHandlingAction="ThrowNewException" name="SqlException">
 9 <exceptionHandlers>
10 <add name="Logging.Handler"
11 type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler"/>
12 <add name="Replace Handler"
13 type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler"
14 exceptionMessage="Encounter data access error."
15 replaceExceptionType="Xixi.DbException"/>
16 </exceptionHandlers>
17 </add>
18 </exceptionTypes>
19 </add>
20 </exceptionPolicies>
21 </exceptionHandling>

相应的调用方式为:

1 try { test001(); }
2 catch (Exception ex)
3 {
4 if (ExceptionPolicy.HandleException(ex, "data access policy"))
5 {
6 throw;
7 }
8 }

ResultFilter用于控制ActionResult的执行,属于在Action方法执行过后对ActionResult执行过程的控制,也就是对视图渲染的控制了,内容与ActionFilter相似,就不介绍了。

注:本文主要供自己学习,不妥之处望见谅。

参考资料:

[1]蒋金楠. ASP.NET MVC4框架揭秘[M]. 上海:电子工业出版社, 2012. 320-389

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏hotqin888的专栏

DOC文件中法规对标系统完成

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/det...

8210
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第三十天 Activiti工作流【悟空教程】

工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的...

1.5K30
来自专栏葡萄城控件技术团队

ASP.NET MVC 5 - 给数据模型添加校验器

在本节中将会给Movie模型添加验证逻辑。并且确保这些验证规则在用户创建或编辑电影时被执行。 拒绝重复 DRY ASP.NET MVC 的核心设计信条之一是DR...

22570
来自专栏进击的程序猿

The Clean Architecture in PHP 读书笔记(四)The Clean Architecture in PHP 读书笔记(四)

上篇最重要的是介绍了去耦的工具之一设计原则SOLID,本篇将继续介绍去耦工具:依赖注入。

7910
来自专栏圣杰的专栏

ASP.NET Core 中断请求了解一下(翻译)

假设有一个耗时的Action,在浏览器发出请求返回响应之前,如果刷新了页面,对于浏览器(客户端)来说前一个请求就会被终止。而对于服务端来说,又是怎样呢?前一个请...

15330
来自专栏前端黑板报

HTTP2基础教程-读书笔记(四)

? 记录一下HTTP/2的底层原理,帮助理解协议实现细节。 连接 每个端点都需要发送一个连接作为最终确认使用的协议,并建立http/2连接的初始设置。客户端和...

35960
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第4章 global全局变量

global 全局变量 global用来定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。通常,可以用来为规则文件提供数据或服务。特别是用来操作规则执...

32760
来自专栏Java帮帮-微信公众号-技术文章全总结

Activiti学习详解【面试+工作】

一:Activiti第一天 1:工作流的概念 ? 说明: 1) 假设:这两张图就是XX兄弟的请假流程图 2) 图的组成部分: A. 人物:范XX 冯X刚 王X军...

76950
来自专栏分布式系统和大数据处理

HttpHandler介绍

在 Http请求处理流程 一文中,我们了解了Http请求的处理过程以及其它一些运作原理。我们知道Http管道中有两个可用接口,一个是IHttpHandler,一...

14320
来自专栏移动开发的那些事儿

BlockCanary源码解析

如上代码中的loop()方法是Looper中的,我们的目的是监测主线程的卡顿问题,因为UI更新界面都是在主线程中进行的,所以在主线程中做耗时操作可能会造成界面卡...

17120

扫码关注云+社区

领取腾讯云代金券