MVC Custom and Friendly Routes | Create a Route with only one parameter in the URL

Asked

Viewed 739 times

5

To create a path where only the single parameter would be displayed?

For example, my route is currently like this:

routes.MapRoute(
   name: "RouteEvent",
   url: "{ProdutoNome}",
   defaults: new { 
       controller = "Produto",
       action = "Detalhe", 
       ProdutoNome= UrlParameter.Optional 
});

and in this scenario I want URL visible in the browser is just like this:

URL: localhost:43760/NomeDoMeuProduto

however, as it stands, my return is as 404 when I try to call this URL.

  • It is because you have already registered a route that gives match in this URL, the default MVC route will capture this and try to find a controller whose name is NomeDoMeuProduto

  • I don’t think so. Because in my route file only this route is being recorded.

  • There is not even the standard record?

  • In this case I retreated to test. But yes, there will be.

  • Post the route archive?

2 answers

2


The order in which routes are declared makes a difference. There is the possibility of a URL hit with two routes, but the router will compare with the routes in the order they were declared. The first hit wins.

The right thing to do is to specific routes at the beginning, because if they are not suitable, then the route will be analyzed Default. We could start like this:

routes.MapRoute(
    name: "RouteEvent",
    url: "{ProdutoNome}",
    defaults: new
    {
        controller = "Produto",
        action = "Detalhe",
        ProdutoNome = UrlParameter.Optional
    }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

First let’s understand that EVERYTHING that comes after the domain and port (ex: after localhost:43760/), ie all the path to the URL comes in parameter form to the RouteValueDictionary, including the name of the Controller, Action (and Area if you had any), as well as the parameters of the action itself.

Even "would work" but not as we would like. The problem is that all Urls with only one parameter (or none) would fall in the first route:

localhost:43760 (no parameters)

localhost:43760/Nomedomeuproduto (with a parameter Nomedomeuproduto)

localhost:43760/Home (with a parameter Home)

localhost:43760/Account (with a parameter Account)

...because the first route consists of a single parameter ProdutoNome, which is also optional.

These Urls would not fall into it, however:

localhost:43760/Home/Index (two parameters, Home and Index)

localhost:43760/Account/Login (two parameters, Account and Login)

For the route Routeevent expects only one parameter (ProdutoNome which may even be omitted), but not two parameters (e.g.: Home + Index or Account + Login).

By removing the ProdutoNome = UrlParameter.Optional, at least the localhost:43760 no longer falls on this route (because the ProdutoNome is mandatory). Still, all other Urls with a single parameter (e.g.: Controller only, omitting Action) would fall on that route.


Solution

To solve this, we have to create a Constraint on the specific route, to check the Urls with a single parameter, and find out if this parameter is actually a Product or a Controller. Example:

public class ProdutoConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        string nomeProduto = values[parameterName].ToString();
        using (var ctx = new MeuDbContext())
            return ctx.Produtos.Any(p => p.Nome == nomeProduto);
    }
}

...or any other way for you to ensure that the value in question is a product.

You could also do the reverse if you wanted to avoid a SELECT in the database. I could guarantee that the parameter received does not match the name of any Controller (maybe it’s even better this way):

public class ProdutoConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
       return !Assembly.GetAssembly(typeof(MvcApplication))
            .GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type))
            .Any(c => c.Name.Replace("Controller", "") == values[parameterName].ToString());
    }
}

So your route would be that way:

routes.MapRoute(
    name: "RouteEvent",
    url: "{ProdutoNome}",
    defaults: new
    {
        controller = "Produto",
        action = "Detalhe"
    },
    constraints: new { ProdutoNome = new ProdutoConstraint() }
);

Note: Maybe you have a Controller whose name is also the name of a product, obviously your Controller will be ignored, because it will understand that you want to see the product, once it exists. But it would be the opposite if your routine was trying to ensure that the parameter was not an existing Controller, in this case the Controller would be displayed instead of the product.

Recalling that the RouteEvent must be declared in RouteConfig.cs above the route Default.

  • You did good, Alisson. VLW You nailed what I needed.

  • @Brunoheringer edited the answer, put an alternative solution that instead of going to the bank to see if the product exists, does the opposite, that is, checks if the parameter received corresponds to the name of some Controller. So, if a user accesses a product that has been deleted or even that does not exist, it will still fall into your Details action and you can show a page stating that the product "x" has not been located.

  • Top... I was already doing the same to get top

  • I updated your answer by putting the code that worked correctly in Match. If you evaluate that it is better accepted the edition. Either way, reinforcement here... return !Assembly.GetAssembly(typeof(MvcApplication))
 .GetTypes().Where(type => typeof(Controller).IsAssignableFrom(type))
 .Any(c => c.Name.Replace("Controller", "") == values[parameterName].ToString());

0

Assuming you don’t have another Maproute, you made a controller called ProdutoController with an action called Detalhe?, if you have not done your web server will not find this route and will give 404.

You need something like that:

public class ProdutoController : Controller
{
    public ActionResult Detalhe(string produtoNome = "")
    {
        Console.WriteLine(produtoNome);
    }
}
  • Gabriel, I have a Product Controller and a Detail Action. The question is the route itself: The localhost:43760/Nameparoduct call

Browser other questions tagged

You are not signed in. Login or sign up in order to post.