1

I am working on an web ASP.NET MVC, I want all the pages on my web to have a unique URL form that looks like this: WebName/{Title} . This is requirement of my customer.

For example: store.com/chicken-pizza and store.com/how-to-cook-beefsteak, but not: store.com/foods/chicken-pizza and store.com/recipe/how-to-cook-beefsteak

I tried to using RouteConfig:

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

        routes.MapMvcAttributeRoutes();

        /*   store.com/foods => This works well */
        routes.MapRoute(
            name: "List-Foods",
            url: "foods",
            defaults: new { controller = "Food", action = "ListFoods", id = UrlParameter.Optional }
        );

        /*   store.com/chicken-pizza, store.com/cheese-sandwich,... => This works well */
        routes.MapRoute(
            name: "Detail-Food",
            url: "{title}",
            defaults: new { controller = "Food", action = "FoodDetail", id = UrlParameter.Optional }

        );

        /*   store.com/recipes => This works well */
        routes.MapRoute(
            name: "List-Recipes",
            url: "recipes",
            defaults: new { controller = "Recipe", action = "ListRecipes", id = UrlParameter.Optional }
        );

        /*   store.com/how-to-make-beefsteak, 
             store.com/instructions-for-making-cookies,..
             => Conflict occurred... this route can't be touch because it 
             has the same url form as Details-Food (url:{title}) */

        routes.MapRoute(
            name: "Detail-Recipe",
            url: "{title}",
            defaults: new { controller = "Recipe", action = "RecipeDetail", id = UrlParameter.Optional }
        );

        ...............

    }

I realized that routes.MapRoute(s) cannot have the same URL form (url:"{title}"). That is not a problem for url:"foods" (to get a list of foods) and url:"recipes" (to get a list of recipes) because I have specified the words(foods and recipes) in routes.Maproute. Also, I can get detailed information of any feed by {title} with Detail-Food route easily. But, the problem occurred at Detail-Recipe route, because it has the same url form (url:{title}) as Detail-Food route, I can't touch Recipe/RecipeDetail to get data.

  • Judging by the method name, is this .Net Framework (rather than .Net Core)? Do you need to use Route Registrations or can you use routing attributes on your actions? – gunr2171 Dec 23 '19 at 15:38
  • Posible duplicate https://stackoverflow.com/questions/15836401/routing-with-and-without-controller-name-in-asp-net-mvc-4 – Oleg Skidan Dec 23 '19 at 15:40
  • Without any pattern which can differentiate between Details-Foods and Details-Recipe you cannot achieve this. – Rajesh G Dec 23 '19 at 16:14
  • You'll have to handle all of this in the same controller. I would reconsider your architecture, having everything at the root level like this makes navigation and the code-behind difficult keep clean –  Dec 23 '19 at 16:19

3 Answers3

0

It looks like you're trying to map the same method signature to different controllers. You'd need to have another distinct piece of the Url to have them route to different controllers. With your current configuration it will not know which controller to route to (I'm not actually sure of the behavior, but you will most likely get an error message here).

I would think if you want to keep with the naming convention you have, maybe use /how-to-cook-{title} for the url for Recipes pages. (this could get dicey, in the event you have some weirdness in the title parameter).

gunr2171
  • 16,104
  • 25
  • 61
  • 88
Wexx
  • 13
  • 3
  • It seems one of the explicit requirements is _not_ have a url like `/how-to-cook/{title}`. There should be only one `/` after the ".com". (even if I fundamentally disagree with this strategy) – gunr2171 Dec 23 '19 at 15:42
  • Yeah, it could work, I don't particularly like it though. I've edited my answer to account for that requirement. – Wexx Dec 23 '19 at 15:52
0

Instead of this you can use [Route] attribute.

[HttpGet]
[Route("chicken-pizza")]
public ActionResult chickenPizza(){
 // Code Here
}

Read more: Web API Route and Route Prefix: Part 2 by Jasminder Singh.

gunr2171
  • 16,104
  • 25
  • 61
  • 88
Rajan Mishra
  • 1,178
  • 2
  • 14
  • 30
  • This solution looks like you'd need a separate action for each food item. That seems unreasonable. – itsme86 Dec 23 '19 at 15:54
  • @itsme86 TBH, the requirements sound a bit unreasonable - you're not making any route discrimination between foods and recipes. This sounds like the next best thing. – gunr2171 Dec 23 '19 at 15:55
  • This is great if you want to hand-write each and every page as a new controller action. It would be rather cumbersome to have to go and change the code-behind for every single page you want to add. Most likely it would be better to parametrize the route `[Route("{id}")]` and use `[FromRoute(Name = "id")] string id` to build the page dynamically –  Dec 23 '19 at 15:58
  • I tried this way a few days ago but it has seemed not working. Therefore, I have used RouteConfig. I don't use API for this web. – P.T. Nghĩa Dec 23 '19 at 17:21
0

You're not gonna be able to pull this off using the default route handler you're gonna have to roll out your own custom one to catch the FoodDetail and RecipeDetail urls.

FoodRecipeRouteHandler.cs

public class FoodRecipeRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        string title = requestContext.RouteData.Values["title"] as string;

        var food = database.GetFoodByTitle(title);

        if (food != null)
        {
            requestContext.RouteData.Values["controller"] = "Food";
            requestContext.RouteData.Values["action"] = "FoodDetail";
        }
        else
        {
            var recipe = database.GetRecipeByTitle(title);

            if (recipe != null)
            {
                requestContext.RouteData.Values["controller"] = "Recipe";
                requestContext.RouteData.Values["action"] = "RecipeDetail";
            }
        }

        return base.GetHttpHandler(requestContext);
    }
}

RouteConfig

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapRoute(
        name: "FoodRecipeDetail",
        url: "{title}").RouteHandler = new FoodRecipeRouteHandler();
}

What's happening here is we're catching the route with the format {title} then looking up in a database to try and find a matching food or recipe item and assigning the appropriate action and controller values to the RequestContext. This is just a basic implementation example to set you on the right track.

dom
  • 6,702
  • 3
  • 30
  • 35
  • Thank you, Dom! I tried to solve my problem with your solution which used "Custom Route" and something went wrong, the problem had been happening! But your solution has suggested to me another way to completely solve the problem. I used a common Controller, it had a ActionMethod which contained both DetailFood and DetailRecipe code. – P.T. Nghĩa Dec 24 '19 at 06:44