views:

306

answers:

4

Im trying to make a very simple application that lets my client create their own pages. The hard part is to let them create thir own URL.

Client need to fill in:

  • Page name (ex. About us).
  • Page description (ex. We are a big company).
  • Page URL relative (ex. /About)

When client enter this information and save, it should be saved in the database.

I can easily create a controller named "Page" which takes a value "ID" and when a user enters "www.someurl.com/Page/1" the newly created page pops up.

But what I really want is for the user to write "www.someurl.com/About" and then the page pops up.

How can this be done with some kind of dynamic rewrite/route code using ASP MVC.

+5  A: 

As the number of questions about routing on this site indicates, handling multiple, potentially overlapping routes correctly is tricky even for programmers. Putting this kind of broad-reaching functionality in the hands of end-users is terrifying.

To answer your question, it's fairly simple to read the "custom" routes from the database at application startup and map them as usual. You don't typically do this during application execution, as the route list will be shared amongst multiple threads. If you dig into the routing source code, you'll probably find a way to protect a "rebuild" of the routing table if you determine that you must do it during execution.

However, I'd strongly suggest that you go with your "Site" controller idea instead. Consider the following: Routing is used both for parsing incoming URIs and for generating URIs. If there is even one case in your application where you use ActionLink instead of RouteLink, then consider that a route added by a user could potentially break your entire site. Meaning that the user would be unable to fix their mistake, even if they realized that they had caused it.

Craig Stuntz
+1  A: 

Best idea I have in mind would be to use the "last case scenario" idea.

Basically you have this scenario.

1- normal routing (eg: controller/action/id route).
2- any special route/area you may set (eg: admin)
3- write a catchall route. This route will search in db for a page named by the route. If this route does not exists, it will throw a 404 page not found error.

Erick
+3  A: 

My idea would be to create a field in your 'Page' table called 'Slug.' When your user creates a new page they would have to fill out the 'Slug' field and enter what they would like to see in the url (ex: page wanted - "About", slug - "about" | page wanted - "See Our Sponsors", slug - "see-our-sponsors" | etc). You can automate this process with a little bit of javascript if you want.

Create a route like so:

routes.MapRoute(
    null,
    {slug},
    new { controller = "Page", action = "ChoosePage" });

Create an action method like so:

[AcceptVerbs(HttpVerbs.Get)]
public ViewResult ChoosePage(string slug)
{
    //Logic to display page
}

Basically it would work like your idea of putting the page id in the url but it would be much cleaner and it would use 'slug' to do the database lookup instead of the page id.

DM
Be sure to place the route as the last route in your routes page. This way, if a user creates a route that conflicts with your hard coded routes, your hard coded routes are executed, making sure your application doesn't break.In addition, create a list of "banned" routes that are already in use by your application.
Baddie
I added it below the "Default" route aswell, but then it did not work. It worked when I placed it directly above the "Default" route, is that ok?
Martin
What if I want the user to create their own levels (Ex. /News/my-news-item). This solution only support one level, what is the best way to extend this to a "unkown level" solution?
Martin
Yeah, I think Baddie forgot to mention to delete the standard default route or place it ahead of that. The problem you were having is the default route looks like {controller}/{action}/{id}. It was taking your "/about" url and trying to find a controller named "about" and a null action method causing a hot mess. I would just remove the default route. As for the levels in your dynamic url: Is the "my-news-item" something like a blog post or is it a static page like your original example?
DM
I get it :D I removed the default route and added new routes for my static pages. Everything else is picked up by the slug route. By the way, I also added an asterix * in front of the slug `{*slug}` so that it picks up everything. It works perfect, thank you all!
Martin
A: 

This is how the final code looks like, thanks to all of you who helped!

public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            "Home",
            "",
            new { controller = "Home", action = "Index" }
        );

        routes.MapRoute(
            "Account",
            "Account/{action}",
            new { controller = "Account", Action = "Index" }
        );

        routes.MapRoute(
            "Page",
            "{*slug}",
            new { controller = "Page", action = "ChoosePage" }
        );

    }
Martin