为了账号安全,请及时绑定邮箱和手机立即绑定

ActionFitlter的坑 防不胜防

标签:
PHP

我忘了是不是讲过,我命名为“截断式编程”的写法。其主要目的,就是把简单的、过滤条件、“非主干的”逻辑放在最前面。比如在ASP.NET MVC的Action中,处理POST时,我们通常都要进行服务端验证,于是我们就可以这样写:

[HttpPost]        public ActionResult Send(MessageSendModel model)
        {            #region 非截断式写法            //if (ModelState.IsValid)            //{            //    //假设发送了一个消息            //    Response.Write("消息已经发送");            //}            //return View(model);

            #endregion

            #region 截断式编程            //过滤条件
            if (!ModelState.IsValid)
            {                return View(model);
            }            //主干程序:假设发送了一个消息
            Response.Write("消息已经发送");            return RedirectToAction("Send");            #endregion
        }


由于采用了这种写法,我们很快就发现了一个问题:if (!ModelState.IsValid) { return View(model); }到处都是。

是不是有点“坏味道”的感觉?你是不是想怎么“弄”它一下?

 

ActionFilter

首先想到的,当然就是ActionFilter了:在Action执行之前,用一个Filter进行检查,不就OK了吗?

我觉得这个想法不错,但是,但是,请注意,一定要问一个为什么!为什么别人想不到呢?——这怎么可能?!

所以,在自己动手之前,养成习惯,google/bing一下,看看别人是怎么弄的。

果不其然,找到一篇博客::Automatic ModelState validation in ASP.NET MVC ,和我的思路一模一样!\(^o^)/

而且,他想得比我更周全!看得我那个兴奋啊……

所以,这里我们得到的第一个经验:动手之前先搜一搜,不要重复造轮子

再引申开一点,

英文 + google = 伟大的程序员。

至少在目前,以及可预见的将来,对于开发人员而言,英语非常重要,非常重要,重要性怎么强调都不为过。大家可以试一下,有没有中文的类似的博客资料等,我没去试。但根据我的经验,相比于英文资料,中文资料是非常匮乏的。

关于使用搜索引擎,很多同学觉得这是一种“可耻的行为”,但其实不然。你一定要明白:你的目的是解决问题,而不是炫技,非得把什么东西都记在脑子里,非得什么都自己写出来……算了,这话可能很多人不接受,篇幅有限,懒得说了。懂的人一点就通,不懂的人你怎么说都没用。

 

最简单的情形

一开始解决方案还是比较简单的。我就直接放代码了:

public class ValidateModelStateAttribute : ActionFilterAttribute
{       
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {        if (!viewData.ModelState.IsValid)
        {
            filterContext.Result = new ViewResult();
        }
    }
}

理解的难点大概就在于为什么:return View(); 和 filterContext.Result = new ViewResult(); 是等价的。

我觉得有这个问题的根源还是没有理解“面向对象”,这一直是.NET阵营程序员所缺乏的。

return View();认为是“转到那个View页面”的意思,而没有能够理解成:这就是一个方法,返回的是一个ActionResult对象。大家能明白我的意思吧?所有的Action都是一个方法,一个返回ActionResult对象的方法,然后ASP.NET MVC框架根据这个ActionResult对象,找到相应的View进行呈现(Render)。这中间多了一个环节,但这不是“脱了裤子放屁”,是非常有必要的,而且非常“精妙”的一个架构设计。

”,这又是一个非常庞杂的话题,此处先略过。

多说一句,一定要说这一句:感谢这位同学提出问题,让我知道作为初学者,那些地方是难点。话说,我做直播这么久,要收到点反馈可真难啊!

 

然而,这里有一个问题


假设页面中需要由后台传来的数据才能正常呈现时,如何在Filter里赋值,再传递给View?

”特征,POST不能“继承”GET时获取的数据,咋办呢?

 

TempData解围

很多种办法,Ben Foster的博客里是封装一个方法,GET和POST都调用。

我以前项目,是利用的MVC中的TempData,在两个请求见共享数据。这样,如果可选项数据是从数据库获取的,可以减少一次查询,有一点点性能上的提高。

”的,它知道你在写代码的时候可能会遇到什么问题,从而事先给你准备好解决方案。

TempData中的数据取出来(需要一个强制转换,因为数据是存放为Object类型的)。

代码就不贴了,因为这不是最终的解决方案。

 

但FilterAction里肿么办

在Controller里面你怎么玩都行,但我们现在要“封装”啊,我们要在Filter里解决这个问题啊!

傻眼了。关键的问题在于我们需要在OnActionExecuting(Action执行之前)进行验证,而此时Action的ViewModel并没有生成,我们可以从ControllerBase.ActionParameters 中取值,但取出来的是一个Object类型,你不知道要把它转换成什么类型的(当然你可以用反射做,但非常复杂,复杂到你都觉得没有必要),TempData也是一样的问题。

每次这个时候,我就会想起ASP.NET WebForm中的ViewState来,那些年认为它是“性能杀手”,对它口诛笔伐。MVC的诞生并流行,估计和这玩意就有非常大(多大呢?我乱说的,三成吧)的关系。

但现在MVC没ViewState了,要自己处理,呵呵,又有点怀恋以前WebForm开发的“便捷”了。

再展开来说,DateSet,Linq to SQL,Entity Framework……一系列的技术,一经推出,都是吵吵嚷嚷,不可开交。其实何必呢,各有各的用处,各有各的适用场景,脱离了具体的业务要求,能争出个什么高下来?

”。

 

PRG模式

RG模式。

RG是Post, Redirect, Get的缩写,意思是所有的POST请求,都Redirect到GET的Action,哪怕返回的实际上是同一个页面。示例代码如下:

 [HttpPost]       
  public ActionResult Send(MessageSendModel model)
        {            //主干程序:假设发送了一个消息
            Response.Write("消息已经发送");            //注意:不是return View()            //1、是Redirect            //2、重定向的这个Action是和自己同名的,都是Send
            return RedirectToAction("Send");

        }


记得我在直播里说过的:如果架构中出现了很多稀奇古怪难以克服的问题,一般来说,就是因为你没走在大道上。

通用的惯例模式,就是大道,别人已经走过的路啊。你循着别人走过的道走,碰到问题的时候,也一样会比较容易的找到解决问题的方案;你要独辟蹊径——通常情况下可能不是你想独辟蹊径,呵呵,多半是自己走岔了吧——那荒山野岭的,确实很难找到求助。

 

终极方案

思路就是:

  • 在Action的POST的Filter里,如果未通过验证,就把ModelState存放在TempData之中;

  • ModelState中。

  • 于是,通过Redirect,GET的Action得到了错误提示信息

最核心代码:

/// <summary>
        /// Exports the current ModelState to TempData (available on the next request).        /// </summary>       
        protected static void ExportModelStateToTempData(ControllerContext context)
        {
            context.Controller.TempData[Key] = context.Controller.ViewData.ModelState;
        }        /// <summary>
        /// Populates the current ModelState with the values in TempData        /// </summary>
        protected static void ImportModelStateFromTempData(ControllerContext context)
        {            var prevModelState = context.Controller.TempData[Key] as ModelStateDictionary;
            context.Controller.ViewData.ModelState.Merge(prevModelState);
        }

就这么简单,完美!回味无穷。

其实,在POST的Action里return View();并不是一个好套路。

POST所属的ChildAction,这肯定是不符合逻辑的。(表述起来好吃力!慢慢看,一边看一边想,想不明白的看视频吧……)

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消