路由简介
做过ASP.NET MVC开发的人都知道,一个页面的响应是通过前台url请求后端的controller在执行对应的action,然后返回视图。比如要访问"www.website.com/home/index",默认请求的是HomeController
下的Index()
方法。
public class HomeController : Controller
{
public ActionResult Index()
{
//logic
return View();
}
}
所以验证路由是否与url匹配的基本思路就是:
- 输入:~/home/index
- 输出: controller=home,action=index
准备工作
在编写单元测试之前,先简单介绍几个重要的类型。
HttpContextBase
在路由解析得时候需要HttpContextBase
对象,HttpContextBase
中包含了两个重要的类型HttpRequestBase
和HttpResponseBase
,其中HttpRequestBase
里面包含了HTTP请求信息。在这次的单元测试中,只需要它的两个只读(Read Only)成员。
public abstract class HttpRequestBase
{
// ... 省略其他成员
// URL中的相对路径,如"www.website.com/home/index"会被转化为"~/home/index"
public virtual string AppRelativeCurrentExecutionFilePath { get; }
// HTTP请求方法
public virtual string HttpMethod { get; }
}
HttpResponseBase
中包含了HTTP响应信息,因为我们只需要拿到controller和action中的值即可,不需要任何响应信息,所以在此不作讨论。
RouteCollection
它是ASP.NET路由的路由集合,所有注册过的路由对象都会放在该集合中。打开项目中App_Start文件夹下的RouteConfig.cs
文件,会看到RegisterRoutes
这个方法的routes
参数就是RouteCollection
类型。我们一般使用该类型的扩展方法MapRoute
来注册路由。
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
RouteCollection
中有一个GetRouteData
方法来获取路由对象。路由通过这个方法来获取与请求中的url相匹配的RouteData
对象。如果没有匹配到,这个方法就会返回null
,这个方法需要一个HttpContextBase
类型的参数,在编写测试时,我们需要创建一个HttpContextBase
对象来模拟请求。
public class RouteCollection
{
// ...省略其他成员
public RouteData GetRouteData(HttpContextBase httpContext);
}
RouteData
顾名思义,RouteData
存储了当前路由的所有信息。
public class RouteData
{
// ...省略其他成员
public RouteValueDictionary Values { get; }
}
我们看到Values
属性它是RouteValueDictionary
类型的,这个类型其实就是字典类型,它实现了IDictionary
接口。controller、action的 值就以Key-Value的形式保存在Values
中
编写单元测试
创建项目
打开宇宙第一IDE——Visual Studi,新建一个ASP.NET MVC项目
添加MVC核心文件和引用,以及勾选单元测试。
删掉单元测试中的默认文件"UnitTest1.cs",重新添加一个类文件名字“RouteMatchTest”。
内容如下:
using Microsoft.VisualStudio.TestTools.UnitTesting; namespace RouteTestDemo.Tests { [TestClass] public class RouteMatchTest { } }
通过Nuget管理器为单元测试项目引入Moq。
在
RouteMatchTest
类中,添加HttpContextSimulation
方法模拟请求。public HttpContextBase HttpContextSimulation(string requestUrl,string httpMethod) { // 创建HttpContextBase对象 Mock<HttpContextBase> mockHttpContext = new Mock<HttpContextBase>(); // 创建HttpRequestBase对象 Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>(); mockRequest.Setup(m => m.AppRelativeCurrentExecutionFilePath).Returns(requestUrl); mockRequest.Setup(m => m.HttpMethod).Returns(httpMethod); // 创建HttpResponseBase对象 Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>(); // 将前面创建的两个对象,分别赋值给HttpContextBase对象的Request和Respone属性成员 mockHttpContext.Setup(m => m.Request).Returns(mockRequest.Object); mockHttpContext.Setup(m => m.Response).Returns(mockResponse.Object); // 返回HttpResponseBase对象 return mockHttpContext.Object; }
然后我们来编写验证url是否和路由匹配的方法。
public void Can_Url_Match_Route(string url,string controller,string action) { // Arrange // 创建匿名方法:比较两个字符串的值是否相等 Func<string, string, bool> comparer = (v1, v2) => { return StringComparer.CurrentCultureIgnoreCase.Compare(v1, v2) == 0; }; // 注册路由表 RouteCollection routes = new RouteCollection(); RouteConfig.RegisterRoutes(routes); // Action // 获取匹配到的route对象 RouteData result = routes.GetRouteData(httpContext); // Assert // 将Values中的controller、action的值和期望值进行对比 Assert.IsTrue(comparer(result.Values["controller"].ToString(), controller)); Assert.IsTrue(comparer(result.Values["action"].ToString(), action)); }
添加以下方法,点击“测试资源管理器中”的运行,测试通过。
[TestMethod] public void Default_Route_Match_Test() { Can_Url_Match_Route("~/home/index", "home", "index"); }
修改一下上面的方法
修改上面的方法
[TestMethod] public void Default_Route_Match_Test() { Can_Url_Match_Route("~/home/index/index/index", "home", "index"); }
再次运行单元测试,很明显通过不了,所以我们就可以用这种方法来测试我们所编写的url能否匹配到正确的controller和action。