Create customize url with MVC

February 21, 2011

8 comments

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

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

8 comments

  1. Mayrun DigmiFebruary 21, 2011 ב 3:47 PM

    Nice trick, thanks!

    Reply
  2. Alon NativFebruary 25, 2011 ב 12:55 AM

    Thanks,

    Alon

    Reply
  3. balaJuly 25, 2011 ב 4:33 PM

    Please give me sample application for this.

    Reply
  4. TurazAugust 13, 2011 ב 10:21 PM

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

    Reply
  5. SussyGunishNovember 5, 2011 ב 10:13 PM

    Nice article to my mind. Keep posting this way!

    Sussy Gunish

    Reply
  6. ramakrishnanDecember 23, 2011 ב 10:48 PM

    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

    Reply
  7. Alon NativDecember 24, 2011 ב 1:02 AM

    Thanks,
    Glad I could help

    Alon

    Reply
  8. jatinJuly 15, 2012 ב 1:13 PM

    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 http://localhost/test1/Users/Get/First but i want to display only http://localhost/test1/First ,please provide you solutions…

    Reply