DCSIMG
<-- +1 script --> Create customize url with MVC - .Net && Beyond

.Net && Beyond

Taking about .NET and much more

Create customize url with MVC

Hi,

I wanted to create an application that will allow users to pick there own urls just like twitter (http://twitter.com/anativ). It seems that there is a very simple way for doing it using MVC.

I saw many solution to this problem that catch the 404 and handle it. I personally didn’t like that solution it was too complicated and had many small issues that you need to solve.

Here is the sample controller that handle the custom urls:

public class UsersController : Controller
{
    private static HashSet<string> _pages = new HashSet<string>();
    
    public ActionResult Add(string id)
    {
        _pages.Add(id);
        string msg = "Page added " + id;
        if (string.IsNullOrWhiteSpace(id))
        {
            msg = "The site name can't be empty...";
        }
        ViewBag.Message = msg;
        return View();
    }
 
    public ActionResult Get(string id)
    {
        if (_pages.Contains(id))
        {
            ViewBag.Message = "Welcome " + id;
            return View();
        }
        return RedirectToRoute("Error","NotFound");
    }
}

The idea is to support customize urls without losing the default routing mechanism (controller/action/id).

First try:

routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
 
 
routes.MapRoute(
    "UserPage", // Route name
    "{id}", // URL with parameters
    new { controller = "Users", action = "Get"  } // Parameter defaults
);

So how do you do that? lets see the problem… if we create a “UserPage” route like the example above we will never catch it because the “Default” MVC route will catch all the patterns before it. If we will change the order that the “UserPage” route will be before the default route the “UserPage” route will catch everything before the “Default” route.

Lets see how we can solve the problem… In order to do that we need to understand what is the job of the “Default” route. The MVC “Default” route catches 3 types of request:

  1. Empty request: the empty request –> handled by /Home/Index
  2. Controller + method –> handled by {controller}/{index}
  3. Controller with empty request –> handled by {controller}/Index

So lets split the controller into rules that will answer all types of the requests above:

for rule number 1 we will create a rule:

routes.MapRoute(
    "Home", // Route name
    "", // URL with parameters
    new { controller = "Home", action = "Index" } // Parameter defaults
);

for rule number 2 we will create a rule:

routes.MapRoute(
           "Controller_Action", // Route name
           "{controller}/{action}/{id}", // URL with parameters
           new { id = UrlParameter.Optional } // Parameter defaults
);

This rule is almost the same as the “Default” MVC route the only deference is the “Parameter defaults” section this rule must get a controller name and an action name.

Now to solve number 3 we need to do a small trick. I created a custom rule for each controller in my project – you can do it manually or you can do it with reflection. I added the method below to my global.asax file it runs on every controller that I have in my project and creates a rule for it (controller/Index).

private static IEnumerable<Route> GetDefaultRoutes()
{
    //My controllers assembly (can be get also by name)
    Assembly assembly = typeof (HomeController).Assembly;
    // get all the controllers that are public and not abstract
    var types = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof (Controller)) && t.IsPublic && !t.IsAbstract);
    // run for each controller type
    foreach (var type in types)
    {
        //Get the controller name - each controller should end with the word Controller
        string controller = type.Name.Substring(0, type.Name.IndexOf("Controller"));
        // create the default
        RouteValueDictionary routeDictionary = new RouteValueDictionary
                                                   {
                                                       {"controller", controller}, // the controller name
                                                       {"action", "index"} // the default method
                                                   };                
        yield return new Route(controller,routeDictionary, new MvcRouteHandler());
    }
}

Now we will need to do is add the call for this method in the RegisterRoutes method just above the last rule

foreach (var route in GetDefaultRoutes())
{
    routes.Add(route);
}

That’s it. Now the global.asax looks like this:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
    routes.MapRoute(
        "Home", // Route name
        "", // URL with parameters
        new { controller = "Home", action = "Index" } // Parameter defaults
    );
 
    routes.MapRoute(
               "Controller_Action", // Route name
               "{controller}/{action}/{id}", // URL with parameters
               new { id = UrlParameter.Optional } // Parameter defaults
    );
 
    foreach (var route in GetDefaultRoutes())
    {
        routes.Add(route);
    }
 
    routes.MapRoute(
        "UserPage", // Route name
        "{id}", // URL with parameters
        new { controller = "Users", action = "Get"  } // Parameter defaults
    );
}

Now I can create an url per user and save the default routing logic.

If you don’t want to apply the default rule on all of your controllers you can add an attribute that will “ignore” the controller or change the default action method from “Index” to something else.

Keep Writing, Compiling, and Debugging

Alon Nativ

Comments

Mayrun Digmi said:

Nice trick, thanks!

# February 21, 2011 3:47 PM

Alon Nativ said:

Thanks,

Alon

# February 25, 2011 12:55 AM

bala said:

Please give me sample application for this.

# July 25, 2011 4:33 PM

Turaz said:

Thank you.

Most of the tutorials/articles explain only the default routing process.

# August 13, 2011 10:21 PM

SussyGunish said:

Nice article to my mind. Keep posting this way!

Sussy Gunish

# November 5, 2011 10:13 PM

ramakrishnan said:

Thanks Alon Nativ.

I found your article very useful.

gr8 work.

This work is pending for me for past 1 week!

Credits goes to you.

Thanks

# December 23, 2011 10:48 PM

Alon Nativ said:

Thanks,

Glad I could help

Alon

# December 24, 2011 1:02 AM

jatin said:

this is my view code  @Html.ActionLink("another-slug1", "Get", new { controller = "Users", id = "First" })

i have create controller mention by in this code also made change in global.asax.cs ,but when i clicked on another-slug1 it display url like localhost/.../First but i want to display only http://localhost/test1/First ,please provide you solutions...

# July 15, 2012 1:13 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: