• Asp.Net MVC4 之Url路由


      先来看下面两个个url,对比一下:

    1. http://xxx.yyy.com/Admin/UserManager.aspx
    2. http://xxx.yyy.com/Admin/DeleteUser/1001 

       对于第1个Url,假设它与服务器上的文件有直接的关系,那么服务器在接受客户端请求并将对应的文件传送给客户端。我们大概可以猜到它是对用户管理的一个页面,它的物理文件UserManager.aspx在网站根目录下面的Admin文件夹中。而第2个url,在不知道Mvc路由以及Url重写时,很难猜到这个Url背后具体有些什么,前提条件是基于.Net框架开发的Web项目。 那么在这里,我们引入Asp.Net Mvc Url路由这个概念,也正是本文所要阐述的主题,Url路由模块是负责映射从浏览器请求到特定的控制器动作。

      基于上面提到的Url路由以及其作用,我们就大概能猜到第2个Url背后有些啥了。 自然而然的Admin就是控制器了,DeleteUser是控制器里面的动作及Action了,1001就是Action的参数。到这里我们对Url路由有一个简单的认识,那么接着看下面一组url,假设Home是控制器,Index是控制器里面的Action,至于1001或者2345这类数据我们暂且约定为参数:

    1. http://xxx.yyy.com
    2. http://xxx.yyy.com/Home/1001
    3. http://xxx.yyy.com/Index/1001
    4. http://xxx.yyy.com/Home/Index/1001/2345
    5. http://xxx.yyy.com/System/Home/Index/1001

    按照约定,从上面的几组Url中可以看出,有的缺控制器,有的缺Action,有的带有好几个参数,有的又莫名的多出了控制器、Action、参数之外的东西,那么他们能正确的访问吗?

      注册路由

        在vs2012里面新建一个asp.net mvc4 web 应用程序项目,可以在项目的根目录下App_Start里面看到RouteConfig文件,在这个文件里面就可以添加url路由了。在文件里面可以看到MapRoute 这个方法,它是RouteCollection的扩展方法,并且含有多个重载,下面看看MapRoute方法的参数,这里选参数最多的那个:

    public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);

    从上面的代码片段大概可以看出该方法含有路由名称、路由的Url、默认值、约束、首先查找路由所在的命名空间,该方法是返回对映射路由的引用。不过在RouteConfig文件中我们可以看到添加路由的代码:

    routes.MapRoute(
      name: "Default",
      url: "{controller}/{action}/{id}",
      defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );

    稍微分析一下这段代码可以知道,在路由集合里面添加了一个名为"Default"的路由,并且有一个默认的路由指向,指向名称为Home的控制器,并且Action为Index,可以明显的看到这里 "id = UrlParameter.Optional" 的写法,它的意思就是说Action的参数可以不需要用户来指定,可以缺省。

      多个参数如何传递

      之前在一个QQ群里面见到一兄弟在问,类似这样的Url“http://xxx.yyy.com/Home/Index/1001/2345/tauruswu”在路由里面怎么配置?其实这个也很简单,我们只需要将路由配置、以及Action稍作调整。

    routes.MapRoute(
      name: "Default",
       url: "{controller}/{action}/{id}/{*catchall}",
       defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );

    这个是调整之后的路由配置,只需要在Url后面再加上"{*catchall}"即可,那么Action调整之后如下

    public ActionResult Index(string id,string catchall)
    {
      ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";
    
      return View();
    }

    在Action里面定义了一个参数"catchall"用来接收Url中除了Id之外其他所有的参数值,那么上面那个Url中接收的参数就是“2345/tauruswu”,这样看起来好不好了,不过我觉得怪怪的,有没有更好的解决方法了?Url有必要写成那么长吗?这个问题在后续文章中会涉及到。

      你也许会犯的错误

      不知各位兄弟在刚刚接触MVC的时候,有没有碰到过这样的问题,如下图

    那么这个错误是如何引起的了?代码是这么写的

    namespace MvcDebug.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index(string id,string catchall)
            {
                ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";
    
                return View();
            }
        }
    }
    
    namespace MvcDebug.Controllers.Tauruswu
    {
        public class HomeController : Controller
        {
            public ActionResult Index(string id, string catchall)
            {
                ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";
    
                return View();
            }
        }
    }

    我们再看英文提示大概就是说匹配出了多个控制器为"Home"的类型,在它的提示中也告诉了我们解决方法,说在MapRoute方法中使用"namespaces"这个参数,我们先将这个放在一边,将抛出这个错误的源码给找出来,具体的源码在DefaultControllerFactory这个类中,看类名就能猜出它的作用是什么了。

    private Type GetControllerTypeWithinNamespaces(RouteBase route, string controllerName, HashSet<string> namespaces)
    {
      // Once the master list of controllers has been created we can quickly index into it
      ControllerTypeCache.EnsureInitialized(BuildManager);
    
      ICollection<Type> matchingTypes = ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
      switch (matchingTypes.Count)   {     case 0:   // no matching types   return null;    case 1:   // single matching type   return matchingTypes.First();   default:   // multiple matching types   throw CreateAmbiguousControllerException(route, controllerName, matchingTypes);   } }

    上面粗体标出的代码,因为我们在路由中没有配置“namespaces”这个参数,这段代码的意思就是通过控制器名称获取所匹配的控制器类型集合,当获取到集合数据之后就开始Case了,很明显,这里集合的数目是2,自然就抛错了。

      问题出来了,该如何解决?在错误提示中说要用到“namespaces”这个参数,我们能不能告诉MVC解析引擎,在解析控制器名称时,能不能对某些命名空间进行优先处理,事实上是可以的,只需要在配置路由的地方稍微调整一下

    routes.MapRoute(
      name: "Default",
      url: "{controller}/{action}/{id}/{*catchall}",
      defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
      namespaces: new[] { "MvcDebug.Controllers.Tauruswu" }
    );

    上面标粗的代码的意思是说优先解析“MvcDebug.Controllers.Tauruswu”这个命名空间里面的控制器。

      路由约束

      对于路由约束,我个人觉得这个可能在实际开发中用的不是很多,既然MapRoute方法提供了constraints这个参数及约束,那么在某些特定的场合肯定能发挥它的作用。在MVC中提供了三种路由约束方案,分别是: 1)正则表达式 ,2)http方法 ,3)自定义约束 。下面我们分别介绍下这三种约束的使用方法。

      1)正则表达式 ,在路由配置中,我们做了这样的规则,只匹配Controller名称以H开头的

    routes.MapRoute(
      name: "Default",
      url: "{controller}/{action}/{id}/{*catchall}",
      defaults: new { controller = "Demo", action = "Index", id = UrlParameter.Optional },
      constraints: new { controller = "^H.*" },
      namespaces: new[] { "MvcDebug.Controllers.Tauruswu" }
    );

      2) http方法 ,我们将路由配置稍作修改

    routes.MapRoute(
      name: "Default",
      url: "{controller}/{action}/{id}/{*catchall}",
      defaults: new { controller = "Demo", action = "Index", id = UrlParameter.Optional },
      constraints: new { httpMethod = new HttpMethodConstraint("POST"), },
      namespaces: new[] { "MvcDebug.Controllers.Tauruswu" }
    );

    然后在对应的Action上面打个标记

    [HttpGet]
    public ActionResult Index()
    {
      ViewBag.Message = "修改此模板以快速启动你的 ASP.NET MVC 应用程序。";
    
      return View();
    }

    你们说这样行不行了?

       3) 自定义约束,如果说上面两种需求还是不能满足你,那么我们可以自定义约束。我们翻看HttpMethodConstraint这个类,可以看到它是继承IRouteConstraint这个接口,其定义是

    public interface IRouteConstraint
    {
      bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
    }

    里面只有一个布尔类型的方法,关于这个例子,是从网上借鉴过来的,如下

    public class CustomConstraint : IRouteConstraint
    {
      private string requiredAgent;
    
      public CustomConstraint(string agentArgs)
      {
        this.requiredAgent = agentArgs;
      }
    
      public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
      {
        return httpContext.Request.UserAgent != null && httpContext.Request.UserAgent.Contains(requiredAgent);
      }
    
    }

    这段代码的意思就是检查客户端请求的UserAgent属性值,看它是否含有一个被传递给构造函数的值。那么我们将路由作如下修改

    routes.MapRoute(
      name: "Default",
      url: "{controller}/{action}/{id}/{*catchall}",
      defaults: new { controller = "Demo", action = "Index", id = UrlParameter.Optional },
      constraints: new { customConstraint = new CustomConstraint("IE"), },
      namespaces: new[] { "MvcDebug.Controllers.Tauruswu" }
    );

    很显然,这个只能在IE游览器下面游览。

      如何创建自定义路由处理程序

      在翻阅MapRoute方法源码的时候,看到了这么一段

    Route route = new Route(url, new MvcRouteHandler())
    {
      Defaults = CreateRouteValueDictionary(defaults),
      Constraints = CreateRouteValueDictionary(constraints),
      DataTokens = new RouteValueDictionary()
    };
    
    .....
    routes.Add(name, route);

    在Route实例化的时候,它是用到了“MvcRouteHandler“这个类,该类继承”IRouteHandler“接口,如果我们不用系统里面已经定义好的路由处理方案,我们要自己来实现一套?改怎么下手,此时只需要继承”IRouteHandler“这个接口并实现”GetHttpHandler“方法即可。

       最后在添加路由时,像这样操作

    routes.Add(new Route("DemoUrl",new DemoRouteHandler());

    当我们在游览器里面请求/DemoUrl这个地址时,就会用到我们自定义的处理程序,在实际开发当中,如果真的要用到自定义路由处理程序,那么我们就要实现很多原本框架所实现的空能,虽然这给我们带来了很大的扩展空间,但是又不可控。

      总结

      Url路由系统是通过请求地址进行解析从而得到以目标Controller/Action名称为核心的路由数据,Url路由系统是建立在Asp.net 之上,我们在调试System.Web.Routing的源码时候可以得知。在这里我们由浅入深的了解了路由系统,接下来我们会讲到控制器以及Action,也是最为核心的东西。

  • 相关阅读:
    Registering a Visual Studio .Net AddIn without an Installer
    广州的.Net Fans俱乐部的T恤和Logo的设计
    模板控件概念(转)
    随笔-在广州.Net俱乐部的一个下午
    快速备份Outlook中的资料
    web.config文件详解
    使用RAPI库操作移动设备——C#语言描述 (转贴)
    Ready to Rock 撼动未来:SQL Server 2005 ,Visual Studio 2005, BizTalk Server 2006 新品发布会 报名
    IBM Rational Solutions V2003
    VS.Net 2003 IDE 插件开发指南
  • 原文地址:https://www.cnblogs.com/wucj/p/3113878.html
Copyright © 2020-2023  润新知