十一:URL路由

路由系统有两个功能

  • 输入URL,推断想要请求哪个控制器和动作
  • 输出URL,是视图渲染的HTML中出现的URL,以便用户点击这些链接时,调用一个特定的动作(又变成了输入URL)

与URL相关的概念有以下内容

  • 映射URL到动作的方法:定义路由
  • 允许省略的URL片段:定义默认值
  • 匹配没有相应路由变量的URL片段:使用静态片段
  • 给动作方法传递URL片段:定义自定义片段变量
  • 允许URL片段省略默认值:定义可选片段
  • 定义可以匹配任意数量URL片段的路由:使用全匹配片段
  • 避免控制器名称混乱:在路由中指定命名空间优先级
  • 限制路由可匹配URL:运用路由约束
  • 启动属性(特效)路由:使用MapMvcAttributeRoutes方法
  • 一个控制器定义路由:在动作方法中使用Route属性(特性)
  • 约束属性(特性)路由:在路由模式中对片段变量运用约束
  • 对控制器的多有属性路由定义一个普通前缀:对控制器类使用RoutePrefix属性(特性)

URL模式

1:新建示例Web项目。模板选择空,文件夹和引用选择MVC,项目名UrlsAndRoutes,并添加测试项目

2:安装Moq包。执行NuGet命名如下

Install-package Moq -version 4.1.1309.1617 -projectname UrlsAndRoutes.Tests

3:新建控制器HomeController并修改代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Controller = "Home";
            ViewBag.Action = "Index";
            return View("ActionName");
        }
    }
}

4:新建一个控制器CustomerController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    public class CustomerController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Controller = "Customer";
            ViewBag.Action = "Index";
            return View("ActionName");
        }
        public ActionResult List()
        {
            ViewBag.Controller = "Customer";
            ViewBag.Action = "List";
            return View("ActionName");
        }
    }
}

5:新建一个控制器AdminController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    public class AdminController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Controller = "Admin";
            ViewBag.Action = "Index";
            return View("ActionName");
        }
    }
}

6:Views文件夹下新建Shared文件夹,并新增View名为ActionName.cshtml。代码如下

@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>ActionName</title>
</head>
<body>
    <div>控制器名:@ViewBag.Controller</div>
    <div>动作名:@ViewBag.Action</div>
</body>
</html>

7:运行程序,在地址后面加上/Home/Index得到完整地址如下

http://localhost:61509/Home/Index

其中第一个“/”后面的Home为URL地址的第一片段,第二个“/”后面的Index为第二片段

创建一个简单路由

8:查看项目中App_Start文件夹下的RouteConfig.cs文件

这个类的静态方法RegisterRoutes是有Global.asax.cs文件进行调用的。

修改RouteConfig.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            Route myRoute = new Route("{controller}/{action}",new MvcRouteHandler());
            routes.Add("MyRoute",myRoute);
        }
    }
}

9:测试路由

在测试项目中新增测试类

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Routing;

namespace UrlsAndRoutes.Tests
{
    [TestClass]
    public class RouteTests
    {
        [TestMethod]
        public void Test()
        {

        }
        private HttpContextBase CreateHttpContext(string targetUrl=null,string HttpMedthod="GET")
        {
            //模仿请求
            Mock<HttpRequestBase> mockReqest = new Mock<HttpRequestBase>();
            mockReqest.Setup(m=>m.AppRelativeCurrentExecutionFilePath).Returns(targetUrl);
            mockReqest.Setup(m=>m.HttpMethod).Returns(HttpMedthod);

            //模仿相应
            Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
            mockResponse.Setup(m=>m.ApplyAppPathModifier(It.IsAny<string>())).Returns<string>(s=>s);

            //创建使用上述请求和相应的模仿上下文
            Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
            mockContext.Setup(m=>m.Request).Returns(mockReqest.Object);
            mockContext.Setup(m => m.Response).Returns(mockResponse.Object);

            //返回模仿上下文
            return mockContext.Object;
        }

        private void TestRouteMatch(string url, string controller, string action, object routeProperties = null, string httpMethod = "GET")
        {
            RouteCollection routes = new RouteCollection();
            RouteConfig.RegisterRoutes(routes);

            RouteData result = routes.GetRouteData(CreateHttpContext(url,httpMethod));

            Assert.IsNotNull(result);
            Assert.IsTrue(TestIncomingRouteResult(result,controller,action,routeProperties));
        }

        private bool TestIncomingRouteResult(RouteData routeResult, string controller, string action, object propertySet = null)
        {
            Func<object, object, bool> valCompare = (v1, v2) =>
                {
                    return StringComparer.InvariantCultureIgnoreCase.Compare(v1,v2) == 0;
                };
            bool result = valCompare(routeResult.Values["controller"],controller)&&
                valCompare(routeResult.Values["action"],action);
            if(propertySet!=null)
            {
                PropertyInfo[] propInfo = propertySet.GetType().GetProperties();
                foreach(var pi in propInfo)
                {
                    if(!(routeResult.Values.ContainsKey(pi.Name))&&valCompare(routeResult.Values[pi.Name],pi.GetValue(propertySet,null)))
                    {
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }

        private void TestRouteFail(string url)
        {
            RouteCollection routes = new RouteCollection();
            RouteConfig.RegisterRoutes(routes);

            RouteData result = routes.GetRouteData(CreateHttpContext(url));

            Assert.IsTrue(result==null||result.Route==null);
        }

        [TestMethod]
        public void TestIncomingRoutes()
        {
            TestRouteMatch("~/Admin/Index","Admin","Index");
            TestRouteMatch("~/One/Two","One","Two");
            TestRouteFail("~/Admin/Index/Segment");
            TestRouteFail("~/Admin");
        }
    }
}
View Code

 定义默认值

10:修改RouteConfig.cs的内容如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                name: null,
                url: "{controller}/{action}",
                defaults: new { action="Index"}
                );
        }
    }
}

这时候请求http://localhost:49198/Customer。可以看到运行的结果是可以请求到动作是Index

使用静态URL

11:修改RouteConfig.cs的内容如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                name: null,
                url: "Test/{controller}/{action}",
                defaults: new {controller="Home", action="Index"}
                );
        }
    }
}

这时候运行的地址中间必须有固定常量Test才可以请求

http://localhost:49198/Test/Customer/List

http://localhost:49198/Test/Customer

http://localhost:49198/Test

路由顺序:如果定义的路由有多条,只有当前面的路由不生效时才会匹配后面的路由

自定义片段变量

12:修改RouteConfig.cs的内容如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace UrlsAndRoutes
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                name: null,
                url: "{controller}/{action}/{id}",
                defaults: new {controller="Home", action="Index",id="default"}
                );
        }
    }
}

13:修改HomeController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace UrlsAndRoutes.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/
        public ActionResult Index()
        {
            ViewBag.Controller = "Home";
            ViewBag.Action = "Index";
            return View("ActionName");
        }

        public ActionResult Test()
        {
            ViewBag.Controller = "Home";
            ViewBag.Action = "Test";
            ViewBag.MyId=RouteData.Values["id"];
            return View("ActionName");
        }
    }
}

14: 修改ActionName.cshtml

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>ActionName</title>
</head>
<body>
    <div> 
        控制器名:@ViewBag.Controller
    </div>
    <div>
        动作名:@ViewBag.Action
    </div>
    <div>
        ID:@ViewBag.MyId
    </div>
</body>
</html>

15:测试地址

http://localhost:49198/Home/Index显示的ID是空

http://localhost:49198/Home/Test显示的ID是default

http://localhost:49198/Home/Test/11显示的ID是11

使用自定义变量作为动作方法参数

16:修改HomeController中的方法Test()如下

        public ActionResult Test(string id)
        {
            ViewBag.Controller = "Home";
            ViewBag.Action = "Test";
            ViewBag.MyId=id;
            return View("ActionName");
        }

测试的结果和上面一致。

定义可选URL片段

17:修改RouteConfig.cs的RegisterRoutes方法

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                name: null,
                url: "{controller}/{action}/{id}",
                defaults: new {controller="Home", action="Index",id=UrlParameter.Optional}
                );
        }

可以正确匹配的URL如下

http://localhost:49198/

http://localhost:49198/Home/

http://localhost:49198/Home/Index

http://localhost:49198/Home/Test

http://localhost:49198/Home/Test/MyIdVar

不能匹配的URL是 http://localhost:49198/Home/Test/MyIdVar/MyIdVar

18:如果不喜欢把该参数放在路由中,同样可以修改HomeController中的方法Test()把参数修改为

Test(string id="DefaultID")

定义可变长路由

19:修改路由方法如下

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                name: null,
                url: "{controller}/{action}/{id}/{*catchAll}",
                defaults: new {controller="Home", action="Index",id=UrlParameter.Optional}
                );
        }

这时候再测试17步中不能匹配的路由地址http://localhost:49198/Home/Test/MyIdVar/MyIdVar。也可以正确匹配了。

结果是和http://localhost:49198/Home/Test/MyIdVar一致。

多命名空间控制器优先级

20:在项目下新建一个NewControllers的文件夹,并新建HomeController控制器代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace UrlsAndRoutes.NewControllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Controller = "New Controller - Home";
            ViewBag.Action = "Index";
            return View("ActionName");
        }
    }
}

再运行URL http://localhost:49198/ 会出现错误。因为路由规则中默认的匹配是Home/Index,但是现在有两个控制器都是Home

21:修改路由方法

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                name: null,
                url: "{controller}/{action}/{id}/{*catchAll}",
                defaults: new {controller="Home", action="Index",id=UrlParameter.Optional},
                namespaces: new[]{"UrlsAndRoutes.NewControllers"}
                );
        }

这时候再运行该项目就会发现默认匹配到NewControllers下的HomeController类的方法Index了。

22:如果禁用其他命名空间,只能使用某个命名空间下,需要修改路由方法如下

        public static void RegisterRoutes(RouteCollection routes)
        {
           Route myRoute=  routes.MapRoute(
                name: null,
                url: "{controller}/{action}/{id}/{*catchAll}",
                defaults: new {controller="Home", action="Index",id=UrlParameter.Optional},
                namespaces: new[]{"UrlsAndRoutes.NewControllers"}
                );
           myRoute.DataTokens["UseNamespaceFallBack"] = false;
        }

约束路由

URL模式在进行片段匹配时是保守的,如何进行片段匹配又是宽松的

23:正则表达式约束:修改路由方法如下

        public static void RegisterRoutes(RouteCollection routes)
        {
            Route myRoute = routes.MapRoute(
                 name: null,
                 url: "{controller}/{action}/{id}/{*catchAll}",
                 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                 namespaces: new[] { "UrlsAndRoutes.Controllers" },
                 constraints: new { controller="^H.*"}
                 );
        }

该规则约束Controller必须以H开头。

24:HTTP方法约束:修改路由方法如下

        public static void RegisterRoutes(RouteCollection routes)
        {
            Route myRoute = routes.MapRoute(
                 name: null,
                 url: "{controller}/{action}/{id}/{*catchAll}",
                 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                 namespaces: new[] { "UrlsAndRoutes.Controllers" },
                 constraints: new { controller="^H.*",HttpMethod=new HttpMethodConstraint("GET")}
                 );
        }

该约束指定请求的方式只能是GET

25:类型和值约束:修改路由方法如下

        public static void RegisterRoutes(RouteCollection routes)
        {
            Route myRoute = routes.MapRoute(
                 name: null,
                 url: "{controller}/{action}/{id}/{*catchAll}",
                 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                 namespaces: new[] { "UrlsAndRoutes.Controllers" },
                 constraints: new { controller="^H.*",HttpMethod=new HttpMethodConstraint("GET"),id=new RangeRouteConstraint(10,20)}
                 );
        }

地址http://localhost:49198/Home/Test/20是可以的,而http://localhost:49198/Home/Test/21就是一个非负URL

常用的约束类如下

  • AlphaRouteConstraint:匹配字母字符(A~Z,a~z)
  • BoolRouteConstraint:匹配bool
  • DateTimeRouteConstraint:匹配可以解析成DateTime类型的值
  • DecimalRouteConstraint:匹配可以解析成Decimal类型的值
  • RangeRouteConstrain(min,max):匹配一个值在min和max之间
  • 其他还有很多约束匹配

自定义约束

如果标准的约束不能满足要求,可以通过IRouteConstraint接口来自定义约束

26:新建一个类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;

namespace UrlsAndRoutes
{
    public class MyConstraint:IRouteConstraint
    {
        private string test;
        public MyConstraint(string Test)
        {
            test = Test;
        }
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            return test.Contains("Happy");
        }
    }
}

27:修改路由方法如下

        public static void RegisterRoutes(RouteCollection routes)
        {
            Route myRoute = routes.MapRoute(
                 name: null,
                 url: "{controller}/{action}/{id}/{*catchAll}",
                 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                 namespaces: new[] { "UrlsAndRoutes.Controllers" },
                 constraints: new { controller="^H.*",
                     HttpMethod=new HttpMethodConstraint("GET"),
                     id=new MyConstraint("NewYear")}
                 );
        }

发现该条路由匹配就是失效的,除非id=new MyConstraint中的参数含有要求的字符Happy

属性路由

MVC5新增了属性路由的技术

28:修改HomeController类,在Index()方法上新增属性路由[Route("Test")]

 

posted @ 2018-11-08 17:38  岚山夜话  阅读(237)  评论(0编辑  收藏  举报