Making hackable URLs for a product catalog

When designing your URL scheme you might consider trying to make hackable URLs. By this I mean to say that your URLs should be able to support user manipulation. A URL such as Product/Details/2 is fine for displaying a product, but it doesn't show any information for Google to slurp up when ranking your site; it also doesn't provide any additional navigational abilities to your viewing customer, it just shows a product. Now take the following URL: Catalog/Computers/Laptops/Toshiba/Satellite-650. With this sort of URL, you are providing your customers with the ability to remove the specific laptop Satellite-650 and display all of the laptops that Toshiba makes. You could further remove the Toshiba from the URL and see a list of all the Laptops that the site offers. And you could remove Laptops and see all the computers, and so on.

One thing to keep in mind when creating these types of URLs is that you do have to do additional work. Each of these URLs become additional routes with corresponding actions in a controller, which ultimately map to a view that displays the appropriate data. If you provide hackable routes for one area of your site, your customers are inevitably going to try to hack other areas of your site's URLs. Be consistent in the implementation of hackable URLs on your site.

Also, you may have noticed in the preceding URL examples that we had some data separated by slashes, while other data was separated by dashes. The general idea is that data separated by slashes can be removed to view additional results. Data separated by dashes can't be modified for additional results.

Getting ready

In order to demonstrate how hackable URLs work, we first need to create an application, which is capable of showing data for each state of the URL. In this case, we will create a product catalog that allows you to navigate from a category to a subcategory, to manufacturers, to products, to a product—and back again.

I will describe the tasks for setting up the routing in detail, but I will point to the other sections of code only as needed. To see the full example code, feel free to take a look at the source code included with the book.

How to do it...

  1. We still start by first creating a new MVC project.
  2. Now open Global.asax.cs and take a look at the RegisterRoutes method. We will start by creating a new route. This first route will handle displaying all of the categories in our product catalog. As that is a simple task, we will end up with a simple route—this route will be called Categories. We will start all of these product catalog routes with the word Catalog to keep this set of routes separate from any other routes we might create for this application. Also, all of these routes will map to the Home Controller regardless of what is stated in the URL. The last thing for us to be concerned with is the action we want to use for this route. We will specify the ShowAllCategories action (which we will need to create). As you can see, this is a parameterless route.

    Global.asax.cs:

    routes.MapRoute("Categories", "Catalog", new {controller = "Home", action = "ShowAllCategories"});
    
  3. Add a new action to our Home Controller called ShowAllCategories. This action will make a call into the ProductCatalogFactory to get the appropriate instance of a view-specific model object called AllCategoriesModel. The ProductCatalogFactory makes a call into a non-specific Repository class that generates instances of various classes as we need them using NBuilder. The action then returns the properly hydrated instance of the AllCategoriesModel to the view.

    Global.asax.cs:

    public ActionResult AllCategories()
    {
    AllCategoriesModel model = ProductCatalogFactory.AllCategoriesModelFactory();
    return View(model);
    }
    

    Models/ViewModels/AllCategoriesModel.cs:

    public class AllCategoriesModel
    {
    public List<Category> Categories { get; set; }
    }
    

    Models/Services/ProductCatalogFactory.cs:

    public static AllCategoriesModel AllCategoriesModelFactory()
    {
    AllCategoriesModel result = new AllCategoriesModel();
    result.Categories = new Repository().GetCategories();
    return result;
    }
    

    Models/Repositories/Repository.cs:

    public List<Category> GetCategories()
    {
    return Builder<Category>
    .CreateListOfSize(10)
    .Build()
    .ToList();
    }
    

    Models/Domain/Category.cs:

    public class Category
    {
    public Guid CategoryId { get; set; }
    public string Name { get; set; }
    }
    

    Views/Home/AllCategories.aspx:

    ...
    <h2>Catalog</h2>
    <fieldset>
    <legend>Categories</legend>
    <% foreach(Category category in Model.Categories) { %>
    <div class="display-label">
    <%:Html.Label(category.Name) %>
    </div>
    <div class="display-field">
    <%:Html.ActionLink(category.Name,"AllSubCategoriesForCategory", new {categoryName = category.Name}) %>
    </div>
    <% } %>
    </fieldset>
    ...
    
  4. With our Catalog view showing all the categories in our fake system, we can now move on to the next route. If a person was to select a category from our catalog, then we would expect it to show perhaps subcategories or related products. In this case, we are building a pretty specific system that requires a subcategory to be selected next. For that reason, we will now build a route to capture the selected category, so that we can display its subcategories.

    Global.asax:

    routes.MapRoute("Category", "Catalog/{categoryName}", new {controller = "Home", action = "AllSubCategoriesForCategory"});
    
  5. Next, we can build a route to additionally capture the selected subcategory. With the subcategory known, we can then grab all the manufacturers for display.

    Global.asax:

    routes.MapRoute("SubCategory", "Catalog/{categoryName}/{subcategoryName}", new { controller = "Home", action = "AllManufacturersForSubCategory" });
    
  6. You may be seeing the bigger picture here—in that we can continue to drill down as deep as we like by simply adding a new route to our route dictionary. The next route will allow us to show a list of products for a selected manufacturer.

    Global.asax:

    routes.MapRoute("Manufacturer", "Catalog/{categoryName}/{subcategoryName}/{manufacturerName}", new { controller = "Home", action = "AllProductsForManufacturer" });
    
  7. With the products displaying for the selected manufacturer, we are finally ready to show the product details. We will need to add one more route to capture the selected product.

    Global.asax:

    routes.MapRoute("Product", "Catalog/{categoryName}/" + "{subcategoryName}/{manufacturerName}/" + "{productName}", new { controller = "Home", action = "ProductDetails" });
    
  8. From here you are now ready to run the program and drill through the product catalog. Keep in mind that we are using NBuilder to generate some data for us, so that the data may look a bit odd but it is indeed working as you would expect it to.

How it works...

When a request comes in, it is matched to routes in the route dictionary. The collection of routes are interrogated from the top down, or from the first item added to the list. The thing to keep in mind here is that the order is very important. If you have a very general route specification at the top of the list that matches every request, then none of your other routes will be acted on. You usually want to have the more specific routes at the top of the list and the less specific routes towards the bottom.

There's more...

You could have done this in a slightly different manner. Had you been willing to put all of your display logic into one view, you could have also consolidated all of your routes into one specification. This would ultimately give you less code to manage but it would also yield less overall flexibility. In this case, we can use different views for each route, which may be good or bad depending on what you are building.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset