Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test if a request's URL is in the Route table

I want to test if a URL is part of the routes defined in the Global.asax. This is what I have:

var TheRequest = HttpContext.Current.Request.Url.AbsolutePath.ToString();
var TheRoutes = System.Web.Routing.RouteTable.Routes;

foreach (var TheRoute in TheRoutes)
{
    if (TheRequest  == TheRoute.Url) //problem here
    {
        RequestIsInRoutes = true;
    }
}

The problem is that I can’t extract the URL from the route. What do I need to change?

like image 955
frenchie Avatar asked Apr 27 '16 15:04

frenchie


1 Answers

The problem is that I can't extract the URL from the route.

I disagree. The problem is that you expect to pull the URLs out of the route table and compare them externally. Furthermore, it is unclear what you hope to gain by doing so.

Routing compares the incoming request against business logic to determine if it matches. This is a route's purpose. Moving the matching logic outside of the route is not a valid test because you are not testing the business logic that is implemented by the route.

Not to mention, it is a bit presumptive to assume that a route can only match a URL and nothing else in the request such as form post values or cookies. While the built in routing functionality only matches URLs, there is nothing stopping you from making a constraint or custom route that matches other criteria.

So, in short you need to write unit tests for the business logic in your routes. Any logic that happens outside of your route configuration should be unit tested separately.

There is a great post by Brad Wilson (albeit a bit dated) that demonstrates how to unit test your routes. I have updated the code to work with MVC 5 - here is a working demo using the below code.

IncomingRouteTests.cs

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcRouteTesting;
using System.Web.Mvc;
using System.Web.Routing;

[TestClass]
public class IncomingRouteTests
{
    [TestMethod]
    public void RouteWithControllerNoActionNoId()
    {
        // Arrange
        var context = new StubHttpContextForRouting(requestUrl: "~/controller1");
        var routes = new RouteCollection();
        RouteConfig.RegisterRoutes(routes);

        // Act
        RouteData routeData = routes.GetRouteData(context);

        // Assert
        Assert.IsNotNull(routeData);
        Assert.AreEqual("controller1", routeData.Values["controller"]);
        Assert.AreEqual("Index", routeData.Values["action"]);
        Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]);
    }

    [TestMethod]
    public void RouteWithControllerWithActionNoId()
    {
        // Arrange
        var context = new StubHttpContextForRouting(requestUrl: "~/controller1/action2");
        var routes = new RouteCollection();
        RouteConfig.RegisterRoutes(routes);

        // Act
        RouteData routeData = routes.GetRouteData(context);

        // Assert
        Assert.IsNotNull(routeData);
        Assert.AreEqual("controller1", routeData.Values["controller"]);
        Assert.AreEqual("action2", routeData.Values["action"]);
        Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]);
    }

    [TestMethod]
    public void RouteWithControllerWithActionWithId()
    {
        // Arrange
        var context = new StubHttpContextForRouting(requestUrl: "~/controller1/action2/id3");
        var routes = new RouteCollection();
        RouteConfig.RegisterRoutes(routes);

        // Act
        RouteData routeData = routes.GetRouteData(context);

        // Assert
        Assert.IsNotNull(routeData);
        Assert.AreEqual("controller1", routeData.Values["controller"]);
        Assert.AreEqual("action2", routeData.Values["action"]);
        Assert.AreEqual("id3", routeData.Values["id"]);
    }

    [TestMethod]
    public void RouteWithTooManySegments()
    {
        // Arrange
        var context = new StubHttpContextForRouting(requestUrl: "~/a/b/c/d");
        var routes = new RouteCollection();
        RouteConfig.RegisterRoutes(routes);

        // Act
        RouteData routeData = routes.GetRouteData(context);

        // Assert
        Assert.IsNull(routeData);
    }

    [TestMethod]
    public void RouteForEmbeddedResource()
    {
        // Arrange
        var context = new StubHttpContextForRouting(requestUrl: "~/foo.axd/bar/baz/biff");
        var routes = new RouteCollection();
        RouteConfig.RegisterRoutes(routes);

        // Act
        RouteData routeData = routes.GetRouteData(context);

        // Assert
        Assert.IsNotNull(routeData);
        Assert.IsInstanceOfType(routeData.RouteHandler, typeof(StopRoutingHandler));
    }
}

OutgoingRouteTests.cs

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcRouteTesting;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

[TestClass]
public class OutgoingRouteTests
{
    [TestMethod]
    public void ActionWithAmbientControllerSpecificAction()
    {
        UrlHelper helper = GetUrlHelper();

        string url = helper.Action("action");

        Assert.AreEqual("/defaultcontroller/action", url);
    }

    [TestMethod]
    public void ActionWithSpecificControllerAndAction()
    {
        UrlHelper helper = GetUrlHelper();

        string url = helper.Action("action", "controller");

        Assert.AreEqual("/controller/action", url);
    }

    [TestMethod]
    public void ActionWithSpecificControllerActionAndId()
    {
        UrlHelper helper = GetUrlHelper();

        string url = helper.Action("action", "controller", new { id = 42 });

        Assert.AreEqual("/controller/action/42", url);
    }

    [TestMethod]
    public void RouteUrlWithAmbientValues()
    {
        UrlHelper helper = GetUrlHelper();

        string url = helper.RouteUrl(new { });

        Assert.AreEqual("/defaultcontroller/defaultaction", url);
    }

    [TestMethod]
    public void RouteUrlWithAmbientValuesInSubApplication()
    {
        UrlHelper helper = GetUrlHelper(appPath: "/subapp");

        string url = helper.RouteUrl(new { });

        Assert.AreEqual("/subapp/defaultcontroller/defaultaction", url);
    }

    [TestMethod]
    public void RouteUrlWithNewValuesOverridesAmbientValues()
    {
        UrlHelper helper = GetUrlHelper();

        string url = helper.RouteUrl(new
        {
            controller = "controller",
            action = "action"
        });

        Assert.AreEqual("/controller/action", url);
    }

    static UrlHelper GetUrlHelper(string appPath = "/", RouteCollection routes = null)
    {
        if (routes == null)
        {
            routes = new RouteCollection();
            RouteConfig.RegisterRoutes(routes);
        }

        HttpContextBase httpContext = new StubHttpContextForRouting(appPath);
        RouteData routeData = new RouteData();
        routeData.Values.Add("controller", "defaultcontroller");
        routeData.Values.Add("action", "defaultaction");
        RequestContext requestContext = new RequestContext(httpContext, routeData);
        UrlHelper helper = new UrlHelper(requestContext, routes);
        return helper;
    }
}

Stubs.cs

using System;
using System.Collections.Specialized;
using System.Web;

public class StubHttpContextForRouting : HttpContextBase
{
    StubHttpRequestForRouting _request;
    StubHttpResponseForRouting _response;

    public StubHttpContextForRouting(string appPath = "/", string requestUrl = "~/")
    {
        _request = new StubHttpRequestForRouting(appPath, requestUrl);
        _response = new StubHttpResponseForRouting();
    }

    public override HttpRequestBase Request
    {
        get { return _request; }
    }

    public override HttpResponseBase Response
    {
        get { return _response; }
    }

    public override object GetService(Type serviceType)
    {
        return null;
    }
}

public class StubHttpRequestForRouting : HttpRequestBase
{
    string _appPath;
    string _requestUrl;

    public StubHttpRequestForRouting(string appPath, string requestUrl)
    {
        _appPath = appPath;
        _requestUrl = requestUrl;
    }

    public override string ApplicationPath
    {
        get { return _appPath; }
    }

    public override string AppRelativeCurrentExecutionFilePath
    {
        get { return _requestUrl; }
    }

    public override string PathInfo
    {
        get { return ""; }
    }

    public override NameValueCollection ServerVariables
    {
        get { return new NameValueCollection(); }
    }
}

public class StubHttpResponseForRouting : HttpResponseBase
{
    public override string ApplyAppPathModifier(string virtualPath)
    {
        return virtualPath;
    }
}

With that out of the way, back to your original question.

How to determine if the URL is in the route table?

The question is a bit presumptive. As others have pointed out, the route table does not contain URLs, it contains business logic. A more correct way to phrase the question would be:

How to determine if an incoming URL matches any route in the route table?

Then you are on your way.

To do so, you need to execute the GetRouteData business logic in the route collection. This will execute the GetRouteData method on each route until the first one of them returns a RouteData object instead of null. If none of them return a RouteData object (that is, all of the routes return null), it indicates that none of the routes match the request.

In other words, a null result from GetRouteData indicates that none of the routes matched the request. A RouteData object indicates that one of the routes matched and it provides the necessary route data (controller, action, etc) to make MVC match an action method.

So, to simply check whether a URL matches a route, you just need to determine whether the result of the operation is null.

[TestMethod]
public void EnsureHomeAboutMatches()
{
    // Arrange
    var context = new StubHttpContextForRouting(requestUrl: "~/home/about");
    var routes = new RouteCollection();
    RouteConfig.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context);

    // Assert
    Assert.IsNotNull(routeData);
}

Note also that generating routes is a separate task from matching incoming routes. You can generate outgoing URLs from routes, but it uses a completely different set of business logic than matching incoming routes. This outgoing URL logic can (and should) be unit tested separately from the incoming URL logic as demonstrated above.

like image 168
NightOwl888 Avatar answered Oct 08 '22 00:10

NightOwl888