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

5

How to create a route 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 the URL visible in the browser to look like this:

  

URL: localhost:43760/NomeDoMeuProduto

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

    
asked by anonymous 18.08.2017 / 22:09

2 answers

2

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

The correct thing is to put specific routes at the beginning, because if they are not appropriate, the route Default will be analyzed. 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 ALL what comes after the domain and the port (eg after localhost: 43760 /), that is, the entire URL path comes as a parameter to the RouteValueDictionary , including the name of the Controller, Action (and the Area if any), as well as the actual parameters of the action.

It would "work", but not as we would like. The problem is that all URLs with only one parameter (or none) would fall on the first route:

  

localhost: 43760 (without parameters)

     

localhost: 43760 / MyProductName (with a parameter MyProductName )

     

localhost: 43760 / Home (with a Home parameter)

     

localhost: 43760 / Account (with an Account parameter)

... because the first route is made up of a single parameter ProdutoNome , which is optional.

These URLs would not fall into it, though:

  

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

     

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

The RouteEvent route expects only one parameter ( ProdutoNome that can be omitted), but not two parameters (eg Home + Index or Account + Login).

Removing ProdutoNome = UrlParameter.Optional , at least localhost:43760 no longer falls on that route (since ProdutoNome is required). However, all other URLs with a single parameter (eg only the Controller, 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 in fact 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 you can ensure that the value in question is a product.

You could also do the reverse if you wanted to avoid a SELECT in the bank. You could ensure that the received parameter does not match the name of any Controller (maybe even better):

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 look like this:

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

Note: If 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 reverse if your routine was trying to ensure that the parameter was not an existing Controller, in which case the Controller would be displayed instead of the product.

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

    
19.08.2017 / 03:14
0

Assuming you do not have another MapRoute, you have made a controller called ProdutoController with an action called Detalhe , if you have not done your web server it will not find this route and will give 404. >

You need something like this:

public class ProdutoController : Controller
{
    public ActionResult Detalhe(string produtoNome = "")
    {
        Console.WriteLine(produtoNome);
    }
}
    
18.08.2017 / 22:35