• 005. Asp.Net Routing与MVC 之三: 路由在MVC的使用


    上次讲到请求如何激活Controller和Action,这次讲下MVC中路由的使用。
    本次两个关注点:

    • 遗留:ModelBinder.BindModel的过程
    • MVC中路由的使用
    • MVC 5中的Action新特性

    一、ModelBinder.BindModel的过程

    MVCHander –>  ProcessRequest()
    xxxxxControllerControllerFactory
    IController.Excute();
    ControllerBase.Excute().ExcuteCore()
    Controller.ExecuteCore()  { . GetActionName  ;  IActionInvoker.InvokeAction() }

    IActionInvoker.InvokeAction() {

    get methodInfo  //sys

    处理参数  // BindModel

    methodInfo.invoke();//sys

    }

    在MVC源码中,ControllerActionInvoker.InvokeAction()
    我们看看BindModel做了些什么。

       1:  
       2: public class DefaultModelBinder : IModelBinder
       3: {
       4:     /// <summary>
       5:     /// ControllerContext (控制器上下文,包含Controller 类型实例,和 当前请求上下文RequestContext)
       6:     /// MVC Model Bind, 从  ControllerContext 中,为当前参数(确定的类型 和 名称)赋值。
       7:     /// </summary>
       8:     /// <param name="controllerContext"></param>
       9:     /// <param name="modelName"></param>
      10:     /// <param name="modelType"></param>
      11:     /// <returns></returns>
      12:     public object BindModel(ControllerContext controllerContext, string modelName, Type modelType)
      13:     {
      14:         // 值类型 或 对象值类型(string),即简单点说:值类型
      15:         if (modelType.IsValueType || typeof(string) == modelType)
      16:         {
      17:             object instance;
      18:             //获取值类型的 值实例,并返回
      19:             if (GetValueTypeInstance(controllerContext, modelName, modelType, out instance))
      20:             {
      21:                 return instance;
      22:             };
      23:             return Activator.CreateInstance(modelType);
      24:         }
      25:  
      26:         //不是值类型,即 对象类型
      27:         //使用类型默认构造函数,创建一个实例
      28:         object modelInstance = Activator.CreateInstance(modelType);
      29:         //下边就是一个循环的过程,查找对象的属性,值类型的就赋值,循环属性直至完成
      30:         foreach (PropertyInfo property in modelType.GetProperties())
      31:         {
      32:             //忽略了 action参数 的对象类型中“对象类型”的属性
      33:             if (!property.CanWrite || (!property.PropertyType.IsValueType && property.PropertyType != typeof(string)))
      34:             {
      35:                 continue;
      36:             }
      37:             object propertyValue;
      38:             if (GetValueTypeInstance(controllerContext, property.Name, property.PropertyType, out propertyValue))
      39:             {
      40:                 property.SetValue(modelInstance, propertyValue, null);
      41:             }
      42:         }
      43:         return modelInstance;
      44:     }
      45:  
      46:     /// <summary>
      47:     /// 为值类型 或 对象值类型(string),(即简单点说:值类型)获取值。
      48:     ///  结构体(数值,bool,自定义结构体struct),枚举enum,可空类型 
      49:     /// </summary>
      50:     /// <param name="controllerContext"></param>
      51:     /// <param name="modelName"></param>
      52:     /// <param name="modelType"></param>
      53:     /// <param name="value"></param>
      54:     /// <returns></returns>
      55:     private bool GetValueTypeInstance(ControllerContext controllerContext, string modelName, Type modelType, out object value)
      56:     {
      57:         //HttpPost 提交,使用窗体变量集合  
      58:         var form = HttpContext.Current.Request.Form;
      59:         string key;
      60:         //form集合不为空
      61:         if (null != form)
      62:         {
      63:             //窗体变量集合 AllKeys 里查找 “modelName” 变量。
      64:             key = form.AllKeys.FirstOrDefault(k => string.Compare(k, modelName, true) == 0);
      65:             if (key != null)
      66:             {
      67:                 //?结构体
      68:                 //value =  Convert.ChangeType(form[key]., modelType);
      69:                 var curKeyVal = form[key];
      70:                 value = Convert.ChangeType(curKeyVal, modelType);
      71:                 return true;
      72:             }
      73:         }
      74:         //HttpGet 提交,使用路由值中的 QueryString
      75:         key = controllerContext.RequestContext.RouteData.Values
      76:             //更快!
      77:             //.Where(item => string.Equals(item.Key, modelName,StringComparison.InvariantCultureIgnoreCase))
      78:             .Where(item => string.Compare(item.Key, modelName, true) == 0)
      79:             //get提交中,只取当前key的第一个匹配项的值
      80:             .Select(item => item.Key).FirstOrDefault();
      81:         if (null != key)
      82:         {
      83:             value = Convert.ChangeType(controllerContext.RequestContext.RouteData.Values[key], modelType);
      84:             return true;
      85:         }
      86:  
      87:         //http://msdn.microsoft.com/zh-cn/library/system.web.routing.routedata.datatokens.aspx
      88:         //RouteData.DataTokens 属性
      89:         //与RouteData.Values类似的键值对方式,允许按key获取值。
      90:         //没试出与 RouteData.Values的区别???
      91:         key = controllerContext.RequestContext.RouteData.DataTokens
      92:             .Where(item => string.Compare(item.Key, modelName, true) == 0)
      93:             .Select(item => item.Key).FirstOrDefault();
      94:         if (null != key)
      95:         {
      96:             value = Convert.ChangeType(controllerContext.RequestContext.RouteData.DataTokens[key], modelType);
      97:             return true;
      98:         }
      99:         value = null;
     100:         return false;
     101:     }
     102: }

    概括的说,就是 

    1、ControllerActionInvoker.InvokeAction() 里,获取当前 MethodInfo 的参数数组(在完整的MVC项目中,在InvokeAction时,还有各种过滤器的操作,ActionExecutedContext Action执行上下文的构建等)

    2、循环 MethodInfo 的 参数数组,调用 IModelBinder.BindModel()为参数赋值

    3、在IModelBinder.BindModel(),判断参数的类型,是值类型的,直接从 RouteData里获取路由值,转换成对应的类型输出并返回。

    4、是引用类型的,调用该类型的默认构造函数,实例化一个该类型,并循环其公开属性,重复值类型的赋值方式,赋值输出并返回。

    二、MVC中路由的使用

       MVC中,用户访问的地址并不映射到服务器中对应的文件,而是映射到对应Control里对应的ActionMethod,由ActionMethod来决定返回用户什么样的信息。而把用户访问的地址对应到对应的Action(当然也可以是对应的文件)的工作有路由系统完成,这其中许多复杂的处理由.net自动完成,而开发者需要告诉.net用户的访问地址和对应Action的具体映射关系。
    MVC中路由系统可以完成两件任务:
    (1). 处理从用户接收到得URL映射到对应的Action;
    (2). 将某个Action根据路由系统的映射关系,动态生成URL,(当网站程序结构改变时,该URL同样会自动改变);

    1、注册:

    在MVC项目的 RouteConfig.RegisterRoutes()里注册,一个典型的 RouteConfig内容如下:

       1: public class RouteConfig
       2: {
       3:     public static void RegisterRoutes(RouteCollection routes)
       4:     {
       5:         routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
       6:  
       7:         Route myRoute1 = new Route("mytest/{controller}/{action}/{id}", new MvcRouteHandler());        
       8:         routes.Add("MyRoute1", myRoute1);
       9:  
      10:         //玄机就在这了,这个MapRoute位于System.Web.Mvc.RouteCollectionExtensions
      11:         //看RouteCollectionExtensions里面做了什么
      12:         routes.MapRoute(
      13:             name: "Default",//命名
      14:             url: "{controller11}/{action2123}/{id}",  //url 这里使用了路由规则表达式,在《Asp.Net Routing与MVC 之三: 路由在MVC的使用》里说
      15:             //id = UrlParameter.Optional  可选的路由参数
      16:             defaults: new { controller11 = "Home", action2123 = "Index", id = UrlParameter.Optional }
      17:         );
      18:     //不存在的url
      19:     routes.MapRoute(
      20:             name: "DefaultNonExist",//命名
      21:             url: "{controller11}/{action2123}/{id}",  //url 这里使用了路由规则表达式,在《Asp.Net Routing与MVC 之三: 路由在MVC的使用》里说
      22:             //id = UrlParameter.Optional  可选的路由参数
      23:             defaults: new { controller11 = "Home", action2123 = "IndexNonExist", id = UrlParameter.Optional }
      24:         );
      25:  
      26:  
      27:  
      28:         //要启用MVC5的新特性,这里需要启用“映射特性路由”
      29:         routes.MapMvcAttributeRoutes();
      30:     }
      31: }

    2、处理接受到的URL

    第二个参数中, "ABC{category}/{controller}/Page{page}/DEF",路由系统会根据此处的参数,把用户请求的URL和这里的参数进行匹配,有两种映射匹配方式:

         (1).动态内容,放在{}里的,即为要匹配的参数名,比如controller,action中的内容均会被匹配并赋值给controller,action参数。然后依据controller,action参数的值,查找controller类型,并到此controller里需找对应的ActionMethod。

         (2).静态内容,放在{}以外的内容,会将此处每一个字符同URL进行比较,比如mytest/…这些

    如果URL和该参数的两种比较方式比均均匹配成功,则为完全匹配。接下来相应的Controller和Action进行执行处理。
    否则,未匹配成 功的URL项:比如action2123,会按默认的参数去匹配Action,如果默认参数匹配不成功,则抛出异常

    如果相同的URL,注册了多个重复的MapRoute时,仅有第一个会生效、。

    3、将对应的Action转换为URL
       路由系统的第二个功能就是实现把Action转换为对应的URL。

       1,生成链接:<a>
       @Html.ActionLink("About this application", "Index", "Home",new {id = "myAnchorID", @class = "myCSSClass"})第四个参数可以为生成的链接提供属性; 当提供的参数和路由系统中的参数不一致时,会生成QueryString:?..=..
       2,生成URL字符串:仅仅产生URL的字符串,即href后的内容
        @Html.Action(),用法同ActionLink一致。

    三、MVC5中的新的路由特性

    1、什么是Attribute路由?怎么样启用Attribute路由?

      微软在 ASP.NET MVC5 中引入了一种新型路由:Attribute路由,顾名思义,Attribute路由是通过Attribute来定义路由。当然,MVC5也支持以前定义路由的方式,你可以在一个项目中混合使用这两种方式来定义路由。  

    在MVC5中,我们可以把路由定义和 Action 放在一起,新的方式:

       1: //MVC 5 的新路由特性,我们不用去 RouteConfig中劳神了
       2: [Route("{testId:int}/{testName}")]
       3: public ActionResult About(int testId,string testName)
       4: {
       5:     ViewBag.Message =string.Format( "Your application description page.test info :{0},{1}",testId,testName);
       6:  
       7:     return View();
       8: }

    2、URL可选参数和默认值

      我们可以使用问号“?”来标记一个可选参数,也可以对参数设定默认值:

       1: //可选的特性路由参数 及默认值
       2: [Route("{testId?}/{testName=JasonLiu}")]
       3: public ActionResult About1(int? testId,string testName)
       4: {
       5:     ViewBag.Message = string.Format("Your application description page.test info :{0},{1}", testId,testName);
       6:  
       7:     return View();
       8: }

    3、路由前缀与默认路由

      有时候在同一个 Controller 中,所有 Action 匹配的 URL 都拥有相同的前缀,当某个Action 不想使用该前缀时,我们用 ~来忽略她。、

    并且可以指定当前路由的默认值。

       1: [RoutePrefix("myhome")] //此Controller的默认路由前缀
       2: [Route("{action=index}")] //此Controller对应路由的默认action值
       3: [Route("{testName=JasonLiu}")] //如上类推
       4: public class HomeController : Controller
       5: { 
       6:  
       7:     public ActionResult Index()
       8:     { 
       9:         return View();
      10:     }
      11:  
      12:     //MVC 5 的新路由特性,我们不用去 RouteConfig中劳神了
      13:     [Route("{testId:int}/{testName}")]
      14:     public ActionResult About(int testId,string testName)
      15:     {
      16:         ViewBag.Message =string.Format( "Your application description page.test info :{0},{1}",testId,testName);
      17:  
      18:         return View();
      19:     }
      20:  
      21:     //可选的特性路由参数 及默认值
      22:     [Route("{testId?}/{testName=JasonLiu}")]
      23:     public ActionResult About1(int? testId, string testName)
      24:     {
      25:         ViewBag.Message = string.Format("Your application description page.test info :{0},{1}", testId, testName);
      26:  
      27:         return View();
      28:     }
      29:  
      30:     [Route("~/contact")]
      31:     public ActionResult Contact()
      32:     {
      33:         ViewBag.Message = "Your contact page.";
      34:  
      35:         return View();
      36:     }
      37: }

    别的,路由约束之类,与之前普通路由定义遵守相同规则。

  • 相关阅读:
    JavaScript 闭包
    JavaScript for循环
    JavaScript switch语句
    JavaScript if...else 语句
    JavaScript流程控制语句脑图
    JavaScript比较和逻辑运算符
    JavaScript运算符
    记录一下获取浏览器可视区域的大小的js
    20181016记录一次前端布局
    20181015记录一个简单的TXT日志类
  • 原文地址:https://www.cnblogs.com/acejason/p/3890085.html
Copyright © 2020-2023  润新知