• 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,也是最为核心的东西。

  • 相关阅读:
    [BJOI2019] 光线
    C# 从零开始写 SharpDx 应用 笔刷
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    PowerShell 拿到显卡信息
    PowerShell 拿到显卡信息
    win10 uwp 如何使用DataTemplate
    win10 uwp 如何使用DataTemplate
  • 原文地址:https://www.cnblogs.com/wucj/p/3113878.html
Copyright © 2020-2023  润新知