URL Routing in ASP.NET MVC – Part 2

This post continues our discussion on how ASP.NET MVC’s URL Routing System works. I assume you have already read Part 1 of the series cause I ‘m gonna work on the project we have created. In our MVC solution project we had two controllers named “ProductController” and “SimpleController” plus a View named “DisplayActionController” in the Views/Shared folder.

public class ProductController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Controller = "Product";
            ViewBag.Action = "Index";
            return View("DisplayActionController");
        }
        public ActionResult ListProducts()
        {
            ViewBag.Controller = "Product";
            ViewBag.Action = "ListProducts";
            return View("DisplayActionController");
        }
    }

public class SimpleController : Controller
    {

        public ActionResult Index()
        {
            ViewBag.Controller = "Simple";
            ViewBag.Action = "Index";
            return View("DisplayActionController");
        }
    }

Delete the code we have added in the previous post inside the “RegisterRoutes” function in the RouteConfig.cs file, where we declared our own routes. We are going to see new features now. The first feature we will examine is how to create custom segments. Till now we have seen the MVC build in segments “controller” & “action”, which apparently correspond to the respective controller and action that will be used to service the requested URL. To start with, we ‘ll create a route with a custom segment variable, named Id. Paste the following code to the RegisterRoutes function.

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
                new
                {
                    controller = "Simple",
                    action = "Index",
                    id = "CustomSegmentId"
                });    
        }

Notice the {id} segment we added and it’s default value “CustomSegmentId” as well. You can access any of the segment variables in the action methods, through the RouteData.Values property. To demonstrate this, first add a new action method in the SimpleController:

public ActionResult MySegmentVariable()
        {
            ViewBag.Controller = "Simple";
            ViewBag.Action = "MySegmentVariable";
            ViewBag.CustomVariable = RouteData.Values["id"];
            return View();
        }

Right click inside MySegmentVariable action and select “Add view..”. Leave the default values and create a MySegmentVariable View inside the Views/Simple folder. Change it’s contents as follow.

@{ 
    Layout = null; 
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    <div>Controller processed the URL: @ViewBag.Controller</div>
    <div>Controller action invoked: @ViewBag.Action</div>
    <div>The custom segment variable is: @ViewBag.CustomVariable</div>
</body>
</html>

Build and run your application. Navigate to the following URL (always use your own port number).

http://localhost:49529/Simple/MySegmentVariable

urlroutingpart2_1
Notice the “CustomSegmentId” default value passed for the Id custom segment. Request another URL, passing this time a value for the Id segment. You should get the imputed value (“Test” in the following case).

http://localhost:49529/Simple/MySegmentVariable/Test

You can access your custom segment variable with a more legitimate way than using the RouteData.Values property. Simply change your action method to accept a parameter with name same as the segment variable.

public ActionResult MySegmentVariable(string id)
        {
            ViewBag.Controller = "Simple";
            ViewBag.Action = "MySegmentVariable";
            ViewBag.CustomVariable = id;
            return View();
        }

The application will run in the same way as before. The MVC Framework, will search in the invoked action a parameter with name as the custom variable, hence “Id”. If it finds a match, it passes the value from the URL to the method. Be aware that if we had defined the Id parameter as int in our action method, the framework would try to cast the URL value to int. You can test it, by changing your id parameter to int and requesting a URL, where you pass a string “test” for the id segment. You will get an error cause “text” couldn’t be casted to int.

You can make your custom segment variable optional, by removing it’s default value and replacing with a “UrlParameter.Optional” definition. Change the route we have defined like this.

routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
                new
                {
                    controller = "Simple",
                    action = "Index",
                    id = UrlParameter.Optional
                });

If you request the “MySegmentVariable” action now, without providing a value for the id custom segment, a null value will be passed.
urlroutingpart2_2
We saw before how to declare a default value for a custom segment variable. Some developers prefer not to declare a default value in the “RegisterRoutes” function as we did, but instead inside the action’s parameter declaration.

public ActionResult MySegmentVariable(string id ="MyDefaultIdValue")
        {
            ViewBag.Controller = "Simple";
            ViewBag.Action = "MySegmentVariable";
            ViewBag.CustomVariable = id;
            return View();
        }

urlroutingpart2_3
Have you consider what would happened if you requested a URL such as

http://site.com/Simple/Index

but instead of having one SimpleController class, have two with different namespaces? Test this, by creating a new SimpleController controller inside a new folder named “OtherControllers”. Change the new SimpleController’s content to the following, build and run your application.

namespace UrlRouting.OtherControllers
{
    public class SimpleController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Controller = "Simple in OtherControllers";
            ViewBag.Action = "Index";
            return View("DisplayActionController");
        }
    }
}

urlroutingpart2_4
To address this problem, you need to tell MVC Framework your preferred namespaces to look when it searches for controller classes. You declare your preferred namespaces with an array of strings like this.

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
                new
                {
                    controller = "Simple",
                    action = "Index",
                    id = UrlParameter.Optional
                },
                new[] {"UrlRouting.OtherControllers"});    
        }

Here we told MVC to search first in the UrlRouting.OtherControllers namespace, which means that if a “Simple” controller class was requested from the URL, then the UrlRouting.OtherControllers.SimpleController will be chosen.
urlroutingpart2_5
Mind that, if you declare both the namespaces in the string array for preferred namespaces, you will get the same conflict error. This means that if you want to use both the namespaces you need to provide two different routes, defining in each of them your preferred namespace.

Another important feature you want to learn is how to constrain a route, using regular expressions. For example, consider that you want a specific route, to make use only controllers start with the letter “S”. You define such a route like this.

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
                new { controller = "Simple", action = "Index", id = UrlParameter.Optional },
                new { controller = "^S.*" },
                new[] { "UrlRouting.OtherControllers" });
        }

We declared a constraint using an anonymous type again, between the default values and the preferred namespaces. I added a new JunkController inside the OtherControllers folder to test if the constraint works.

namespace UrlRouting.OtherControllers
{
    public class JunkController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Controller = "Junk in OtherControllers";
            ViewBag.Action = "Index";
            return View("DisplayActionController");
        }
    }
}

Here is the result. The constraint works just fine.
urlroutingpart2_6
You can add more more constraints such as limiting the action methods can be invoked inside a controller.

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
                new { controller = "Simple", action = "Index", id = UrlParameter.Optional },
                new { controller = "^S.*", action ="Index|NewIndex" },
                new[] { "UrlRouting.OtherControllers" });
        }

Here we told the MVC that this route accepts only controllers with name starting with “S” and only two actions can be invoked to that kind of controllers: Either an Index or a NewIndex action. Before testing this feature I added two actions in the OtherControllers.SimpleController.

        public ActionResult NewIndex()
        {
            ViewBag.Controller = "Simple in OtherControllers";
            ViewBag.Action = "NewIndex";
            return View("DisplayActionController");
        }

        public ActionResult ConstrainedIndex()
        {
            ViewBag.Controller = "Simple in OtherControllers";
            ViewBag.Action = "ConstrainedIndex";
            return View("DisplayActionController");
        }

Build and run your application. If you request the following two URLs, you will get a valid response since neither of the constraints are violated.

http://localhost:49529/Simple/Index
http://localhost:49529/Simple/NewIndex

But if you try to invoke the “ConstraintIndex” action..
urlroutingpart2_7
This is it. In the next post of these series we will look how to generate outgoing URLs from routes in your views.



Categories: ASP.NET

Tags: , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: