0

I am again here with my foolish MVC routing questions. I am creating an E commerce project and stuck at one point in routing. So far attribute routing working fine with following URL.
abc.com/Electronics/Audio/Portable Audio/iPods in form of

Category/Subcategory/Subsubcategory/Types/productname{stuck here}

But now I need to show the product name and here I am stuck. Product can be at any level. Like

abc.com/Electronics/Apple iPod Nano
abc.com/Electronics/Audio/Apple iPod Nano
abc.com/Electronics/Audio/Portable Audio/Apple iPod Nano
abc.com/Electronics/Audio/Portable Audio/iPods/Apple iPod Nano

The problem is if I define category controller like following:

 [Route("{category}/{productname?}")]
        public ActionResult Category(string category,string productname)
          {
                //code
           }

and subcategory controller like following

 [Route("{category}/{subcategory}/{productname?}")]
        public ActionResult Subcategory(string category,string subcategory,string productname)
          {
                //code
           }

And so on...
I get an error of ambiguous routes because after clicking the link for subcategory(If there is no product name),the attribute routing routes to the category controller again as criteria of two parameters is fulfilled.

So how can I differentiate between these two controllers. Or any better idea is always welcome as I am nearly a newbie in routing and MVC.

Also please tell if there is any idea through which we can strictly define controllers to be mapped regardless of parameters matched or not,but keeping the URL form as described above.

Thank You.

1 Answers1

0

Since all of your route segments are optional, you have routes that overlap.

// This will match any URL with 1 or 2 segments
[Route("{category}/{productname?}")]

// This will match any URL with 2 or 3 segments
[Route("{category}/{subcategory}/{productname?}")]

Route order matters no matter how you declare your routes. However, since they both will match a URL with 2 segments, it doesn't matter which order you place them in because the first match always wins and one of these routes will always cancel out the other when you have a 2 segment URL.

There are 6 ways out of this situation:

  1. Make sure all route have a different number of placeholder segments. For example, {category}/{productname} and {category}/{subcategory}/{productname} will not conflict.
  2. Declare at least one of the segments as a literal value instead of a placeholder. For example, Audio/{?productname} instead of {category}/{?productname}.
  3. Use a RegEx route constraint.
  4. Use a custom route constraint.
  5. Create a custom RouteBase subclass. See this example.
  6. Use a catch-all route such as {*slug} and then handle your URL logic inside of the controller or one of its services. I consider this option to be a hack because you are basically throwing routing out the window and managing URL logic inside of your controller instead of in the routing framework where it belongs.

If you know ahead of time the maximum number of subcategories you will have, you could just make a separate action method to handle each segment length.

[Route("{category}")]
public ActionResult Category(string category)
{

}

[Route("{category}/{subcategory1orProductName}")]
public ActionResult CategorySubcategory1(string category, string subcategory1orProductName)
{
    // Figure out here whether the last parameter is a subcategory or product name.
}

[Route("{category}/{subcategory1}/{subcategory2orProductName}")]
public ActionResult CategorySubcategory1(string category, string subcategory1, string subcategory2orProductName)
{
    // Figure out here whether the last parameter is a subcategory or product name.
}

But once again we are putting routing logic into the controller where it doesn't belong.

The built-in routing tools are powerful and can handle a wide variety of scenarios. However, one thing that they are particularly not good at are building category/subcategory/contentItem routes to an unknown number of levels and you need to do some extra work to get there.

In that case, your best options are

  1. Create a set of custom route constraints (one for each category segment, subcategory segment, and product segment) to act as filters to determine which route to match. This could get extremely complex to manage.
  2. Create a custom RouteBase override for product and another one for category. Put the URL segments into the database table. Use a self-joined table to build the category/subcategory part and match this to your category controller. Use the same self-joined table to build category/subcategory/product and match that in your product route. Make sure these URLs are cached so every request doesn't hit the database and so your route can both match the incoming URL and rebuild the URL when you want to use it for ActionLinks.
Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Thank You @Night Owl. I 've gone through your example but could not understand that how to use that class to get the url I want. In attribute routing I am defining the idea of URL just above controller. So where to define the my route idea in your example for the routes defined in question. – Manvendra Singh Aug 29 '15 at 10:03