views:

255

answers:

4

Say I have an ASP.Net MVC site with products and categories, and I want these urls...

Products:

  • Fruitshop.com/super-tasty-apple
  • Fruitshop.com/squishy-orange
  • Fruitshop.com/worm-infested-apple
  • Fruitshop.com/megaorange

Categories:

  • Fruitshop.com/apples
  • Fruitshop.com/oranges

For each product/category in the database, I save the slug (or whatever you call it), like "super-delicous-apple" or "apples".

There is no pattern or regular expression(that I can see, anyway), that allow me to determine just by looking at the url, if it is a product or a category. I need to look in the database to know.

What would be a good way to approach this?

  1. At application startup, register the route of every single product and category
  2. Create some code that handles the requests, and look up the slug in the database
  3. ??

Nevermind caching, refreshing routing-table etc for now. :)

Feel free to edit my question title, if you feel there is a more appropriate title for this question - this was the best I could come up with.

A: 
  1. Not quite possible unless you have (say less than 100) pages. Registering routing might not be a costly operation, but consider you have to check through those things every time you have request, I is a waste of cpu time.
  2. Yes, if do so, make a HttpModule to handle and rewrite the Url will be the easiest. Say, (eg the request is fruitshop.com/oranges), you check oranges in db to see if it is categories or a product (I think a stored procedure may help), if neither of them, return 404. If so, rewrite the url to something controller friendly:

/product/id/5 or /category/id/3

The id could also be returned with the stored procedure to save one-round-trip

xandy
+2  A: 

I like your approach #2 more so than #1. I think you could get away with creating a single route that goes to a SlugHandler action in a SlugController (or whatever else you want to call these). You would need only one route for this.

Your SlugHandler action would then accept a slug string, then call upon a method to determine whether the slug is a category or a product. (You did say these were unique, yes? i.e., not possible to have same slug for BOTH a product and a category.) From there, you could call upon the appropriate Product or Category action method, passing along the slug string.

Some code fragments follow:

internal enum SlugType
{
    Category,
    Product
}

private SlugType DetermineSlugTypeFromSlugName(string slug)
{
    // some database magic happens here to return appropriate SlugType
}

public ActionResult SlugHandler(string slug)
{
    SlugType slugType = DetermineSlugTypeFromSlugName(slug);
    switch (slugType)
    {
        case SlugType.Category:
            return Category(slug);
        case SlugType.Product:
            return Product(slug);
        default:
            return SomeErrorAction();
    }
}

Best of luck!
-Mike

Funka
Furthermore, when time permits, you could revisit this code and rewrite DetermineSlugTypeFromSlugName to implement some caching so that you wouldn't need to keep hitting database...
Funka
A: 

I tried to solve the same problem a month or so ago. The solution I settled on was to use the UrlRewriter HttpModule to rewrite urls to the MVC url. An example of this mapping would be

<rewrite url="/super-tasty-apple" to="/apple/details/3" />

I save these urls in the database. When the application starts I read all the urls from the database and write out the mappings to my web.config. I don't particularly like doing this but, it gets around the problem of having to query the database for each request.

If your application is frequently adding/editing urls then this approach is probably not for you.

If you decide to try this approach then the order of the HttpModules becomes important. The UrlRewriter module must be added before the UrlRoutingModule.

Hope this helps.

Joel Cunningham
+1  A: 

Here's how you might want to do option 1:

On application start, you could go off to the database and return all categories.

From here, you could build a route using regex...

//This has a route constraint in the form of the regex returned from GetMyCategories
routes.MapRoute("Categories", 
  "{category}",
  new { controller = "Product", action = "Category" },
  new { category = GetMyCategories() }
);

routes.MapRoute("Product", 
  "{product}",
  new { controller = "Product", action = "Product" }
);


string GetMyCategories()
{
  StringBuilder categoriesRegex = new StringBuilder("(");

  var categories = GetAllCategoriesFromDb();
  categories.ForEach(x=>categoriesRegex.AppendFormat("{0}|", x));
  categoriesRegex.Append(")");

  return categoriesRegex.ToString();
}

A major problem with this solution is that, if you add a new category to the database, you will need to restart your application to refresh the regex on to the route constraint.

Regarding option 2, I think that Funka's answer is good enough.

Dan Atkinson