zoukankan      html  css  js  c++  java
  • 为解决ASP.NET MVC(CTP)中URL“页面请求”和“单纯逻辑处理请求”混淆问题,提供一条思路

       写在最前:本文主要是提供一种解决ASP.NET MVC(CTP)中URL“页面请求”和“单纯逻辑处理请求”混淆问题的思路,演示代码只作实现效果之用,不一定适合直接应用于“实战”,如有“粗燥”之处请多包涵。如果大家觉得可行,我们可以一起来完善她。

    之前我很多次提到ASP.NET MVC 中“指令性”的URL,以及它可以给我们带来的一些新的体验,这样的URL可以把V层的页面逻辑(或者请求)让C层去承担,并且由C层负责判断到底将哪个网页最后传输到客户端。

    这样的好处(或者说一部分的必要之处),是将VC在一定程度上分离开来,一切以Controller为中心,而不再是aspx。但是这样“指令性”的URL我感觉更像是一把双刃剑,我们说他好,也可以说它有很大缺陷

    第一,比如当我们在一个Controller中的Action中的逻辑处理完毕之后,通常最后会有两种选择:RenderView(转向处理页面)或者RedirectToAction(转向另一个Action),如果你不这样做,而是像Web Forms或者ASP中那样下面不写点什么,那么返回的将是一个空白网页,至少不是你原来请求的入口网页。也就是说,一个Action只能返回相对固定的结果网页,除非你给他加上指定的参数或者每次都通过一系列判断。那如果我有很多地方要重用同一个Action,并且URL是不确定的,怎么办?

    第二,由于MVC中请求的URL已经被Route过,并且通过一系列内部规则找到对应的Action才执行,当某个Action进行时,如果你想直接用Web Forms的方法得到请求网页的URLRequest.UrlReferrer),几乎是不可能的。这时候你用Request.URL找到的也不是你刚才在访问的网页URL,而是你主动请求的这个“指令性”URL。那么又出现一个问题,如果我想运行完一段Action后仍然回到这个网页,就像一个buttonpostback之后没有转向,那要如何做到呢?

    基于这两点,我们当然可以有“水来土掩”的办法

    第一,不重用Action,而是为许多的Action创建重用的方法,Action之后到底RenderView/RedirectToAction到什么地方,还是由Action各自负责。

    第二,每个这样的Action执行完之后,用最“死”的办法指定返回的页面(也就是说一个页面的逻辑处理只对应一个Action)。

    这样确实可以解决两个问题,但是他们都有各自的很大的不足

    第一,这种方法使Action片断无畏的增加,这一切只是为了返回不同结果页而已。并且维护需要更加“深入”,不直观。同时这样做只会增加程序员体力和磁盘空间(说白了也就是“庞大”低效代码)的付出,不折不扣的“内耗”。

    第二,同样的“内耗”。并且虽然没有涉及到较大的“重用”的需求,但是这些工作如果能像在Web Forms或者ASP中那样完成,岂不是更好。

     

    为了同时解决这两个不足,我进行了几套方案的测试,并且最后确定了一套目前为止我所能想到的比较好的办法:把“指令性”URL“分流”,就是说指向同样的Controller中的同一个Action,通过一个页面上简单的参数,让他自动处理是返回请求页面还是继续。也就是说,把“页面请求”(不管是否需要逻辑处理,最后返回一个结果页)和“单纯逻辑请求”(就像我们很多时候用需要Web Forms中button做的那样)在需要的时候,彻底分离开,同时保证原有方法继续有效。

    听上去似乎很容易实现,但是别忘了我有一些必要的前提:

    1、能同时解决(或者改善)以上两个问题。

    2、不破坏ASP.NET MVC本身的构架(也就是说不删除、修改任何代码,不Hack任何dll)。

    3、尽量简单、高效,并且可能的话,考虑到改善其他安全性方面不足的问题。因为大家都知道,Web FormsPostBack方法可以很好的解决一些安全性方面的问题(执行逻辑的触发上),而目前MVCAction等于是全部暴露给用户的,既然要在URLAction上动手脚,那么看看是否可以把这个问题顺带处理掉(因为这方面涉及的问题比较多,我的案例中只是提供一种可行的思路,并不着力实现它)

     

    当然,还要说明一下,这个未必是最好的办法,我这里只是提供一个可行的思路,这是我在开发一个测试ASP.NET MVC的项目过程中临时碰到和想到的,只是“逢山开路”了一下,可能还有更好的方法没有想到,或者一些细微处没有兼顾到,欢迎大家指出。如果大家有更好的,或者是因为本人对ASP.NET MVC认识不足而导致的“多此一举”也欢迎探讨!

     

    下面介绍一下我的思路的一种做法:

    我们要同时解决URLAction方面的问题,那么首先当然是要考虑到,如何获取到请求页的URL。既然Action不行,那么当然首先想到在Global.asax中指定(虽然这个办法有些“作孽”,不过因为只是测试,就先拿Global.asax开刀吧)。

    我们在Global.asax中添加一套自己的URL标准格式:



               RouteTable.Routes.Add(
    new Route

                
    {

                    Url 
    = "[controller].mvc/[ao].html/[action]/[id]/[aop]",

                    Defaults 
    = new

                    
    {

                        action 
    = "Index",

                        id 
    = (string)null,

                        ao 
    = "0",//(string)null

                        aop 
    = (HttpContext.Current.Request.UrlReferrer != null?

                            Server.UrlEncode(HttpContext.Current.Request.UrlReferrer.PathAndQuery) : (
    string)null

                    }
    ,

                    RouteHandler 
    = typeof(MvcRouteHandler)

                }
    );

     

    其中[ao]Action Only的缩写,这个参数如果传入时不为0也不为空,那么就说明用户需要执行完后返回当前页,而不是View到其他地方去。

    [aop]Action Only Page的缩写,这个字段用户不用传入,有服务器自动获得当前的URL(确切的说,对服务器来说是上一个),我们此处用HttpContext.Current.Request.UrlReferrer.PathAndQuery获取路径和参数。之所以不让用户在页面就传入URL,也不依靠外部数据,我是考虑到两点:一、简单易用;二、安全性,这样对于可以触发某些Action Only的网页,我们可以在第一时间有完全的控制(逻辑还没有执行的时候),我们可以把aop的赋值过程写入一个单独的过程,筛选触发这个Action的网页的URL(如Admin目录下指定URL), 当然我这里为了方便测试,没有展开。这样从根本上杜绝了外部的注入(因为直接在浏览器中打开是没有UrlReferrer的)和内部自定义链接注入(就像cnblogs中,用户自己用编辑器发一个链接作为“指令”)。

     

    接下来我们需要有一个方法来接收处理[ao][aop]指令。

    我写了一个ActionOnlyForControllers.cs放入Controller文件夹,因为Controller后面加了s,所以你不用担心V层的ActionOnlyFor之类文件夹的干扰:

    using System;
    using System.Web;
    using System.Web.Mvc;

    namespace ActionOnlyForControllers.Controllers
    {
        
    public class ActionOnlyForControllers
        
    {
            
    /// <summary>
            
    /// 核对URL参数,确认是否需要ActionOnly
            
    /// Check URL parameters([ao] and [aop]), make suer ACTIONONLY is hoped
            
    /// </summary>
            
    /// <param name="rd">RouteData</param>
            
    /// <returns></returns>

            public static bool CheckAO(RouteData rd)
            
    {
                
    //确保有[ao]/[aop]参数
                
    //check if [ao]/[aop] exist in the URL
                if (!rd.Values.ContainsKey("ao"|| !rd.Values.ContainsKey("aop"))
                
    {
                    
    return false;
                }


                
    //获取URL来源
                
    //get the URL to return after ACTIONONLY
                string returnURL = (!string.IsNullOrEmpty(rd.Values["ao"].ToString()) && rd.Values["ao"].ToString() != "0" && rd.Values["aop"!= null?
                                      System.Web.HttpContext.Current.Server.UrlDecode(rd.Values[
    "aop"].ToString()) : null;

                
    //执行转向
                
    //do Redirect
                if (!string.IsNullOrEmpty(returnURL))
                
    {
                    
    //System.Web.HttpContext.Current.Response.Write(rd.Values["ao"].ToString() + "<br />");
                    
    //System.Web.HttpContext.Current.Response.Write(rd.Values["aop"].ToString() + "<br />");
                    
    //System.Web.HttpContext.Current.Response.Write(returnURL+"<br />");
                    
    //System.Web.HttpContext.Current.Response.Write(rd.Route.Url + "<br />");

                    System.Web.HttpContext.Current.Response.Redirect(returnURL, 
    true);
                    
    return true;
                }

                
    else
                
    {
                    
    //不转向,继续执行RenderView、Action等操作    
                    
    //[aop] isn't correct , return as nothing happened with "FALSE"
                    return false;     
                }


            }

        }

    }


     

    过程上我都写了注释,大家如果发现有bug可以向我反映。

        这个判断我们直接在Controller中调用,方法如下:

           

    [ControllerAction]
            
    public void About()
            
    {
                
    // 以下Response.Write语句只做在V中确定这里曾经执行过逻辑的标记
                System.Web.HttpContext.Current.Response.Write("我在Controller中输出了一串字符");

                
    //关键是这句
                if (!ActionOnlyForControllers.CheckAO(RouteData))

                    RenderView(
    "About");
            }

    感谢一些朋友对这句话提出的一些看法:System.Web.HttpContext.Current.Response.Write("我在Controller中输出了一串字符");
        大家的说法我是同意的,但是这个语句并不是我在V里面输出数据的方法,这儿这么做只是为了能在页面执行后留下这段逻辑处理的“痕迹”。对应于我做的这个简单示例:ActionOnlyForControllers的一个简单示例 。 这里只是为了便于测试(因为只有这样不用牵扯到下游aspx文件),同样的效果大家可以把这句话理解为:
        ViewData["somewords"]="我在Controller中输出了一串字符";
        然后在aspx中调用 ViewData["somewords"]。
        谢谢!


        
    只需要多加一行

    if (!ActionOnlyForControllers.CheckAO(RouteData))

        可以实现“留在页面”或者“转向页面”的判断。

    在HTML中我们只需要用一个ao参数来控制:ao=0或为空,则不执行判断,保持MVC原有形式。ao为其他值时,则执行完Action仍然回到本页面,其效果类似简单的postback一下。我们可以这样做:

        <%= Html.ActionLink("执行方法后回到本页面(button效果)"new { ao = 1, action = "About", controller = "Home" })%><br />
        
    <%= Html.ActionLink("执行方法后继续执行RenderView(hyperlink效果)"new { ao = 0, action = "About", controller = "Home" })%></p>


        这样我们可以轻松的在“页面请求(+ 逻辑处理)”和“单纯逻辑处理”之间切换,让Action得到很好的重用,并且解决页面返回的问题。

    当然,这些似乎看上去有点不太合ASP.NET MVC的本意,也许是我被Web Forms“宠”得太依赖于“postback体验”,但总之只要能为我们提供方便,并且达到特定的目的,MVC这个名字本身已经不再重要^_^

     

    这里我提供了一个示例下载:ActionOnlyForControllers的一个简单示例
         友情提示:其中使用的MVCToolkit.dll是我曾经修改过的:MVC Toolkit 部分已发现bug的根治方案 Part(1) ,请不要在不知情的情况下当作官方的dll引用到其他项目中,以免出错。

    转载请注明出处和作者,谢谢!
    作者:JeffreySu / QQ:498977166
    博客:http://szw.cnblogs.com/

    Senparc官方教程《微信开发深度解析:微信公众号、小程序高效开发秘籍》,耗时2年精心打造的微信开发权威教程,点击这里,购买正版
    
微信开发深度解析:微信公众号、小程序高效开发秘籍

    Senparc 官方微信开发视频教程:《微信公众号+小程序快速开发》,点击这里点击观看
    Senparc 官方微信开发视频教程:《微信公众号+小程序快速开发》
  • 相关阅读:
    基本指令6---GRANT
    基本指令4---ALTER
    基本指令3--UPDATE/DELETE/DROP
    基本指令2---CREATE/INSERT INTO
    基本指令1
    jmeter-跨线程组传值
    【数学】斯特林子集数 | 斯特林轮换数 | 欧拉数
    【图论】拓扑排序
    【数学】多项式(原理)
    【数学】普通型生成函数
  • 原文地址:https://www.cnblogs.com/szw/p/1029517.html
Copyright © 2011-2022 走看看