Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL

StackOverflow https://stackoverflow.com/questions/21414448

Question

...guess I'm the first to ask about this one?

Say you have the following routes, each declared on a different controller:

[HttpGet, Route("sign-up/register", Order = 1)]
[HttpGet, Route("sign-up/{ticket}", Order = 2)]

... you could do this in MVC 5.0 with the same code except for the Order parameter. But after upgrading to MVC 5.1, you get the exception message in the question title:

Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.

So the new RouteAttribute.Order property is only controller-level? I know in AttributeRouting.NET you can do SitePrecedence too. Is the only way to have routes like the above when all actions are in the same controller?

Update

Sorry, I should have mentioned these routes are on MVC controllers, not WebAPI. I am not sure how this affects ApiControllers.

Was it helpful?

Solution 2

In case of Attribute routing, Web API tries to find all the controllers which match a request. If it sees that multiple controllers are able to handle this, then it throws an exception as it considers this to be possibly an user error. This route probing is different from regular routing where the first match wins.

As a workaround, if you have these two actions within the same controller, then Web API honors the route precedence and you should see your scenario working.

OTHER TIPS

If you know that ticket will be an int you can specify that type in the route to help resolve the route:

[HttpGet, Route("sign-up/register")] [HttpGet, Route("sign-up/{ticket:int}")]

This approach worked for me, per user1145404's comment that includes a link to Multiple Controller Types with same Route prefix ASP.NET Web Api

There are two ways to fix this:

A regex constraint, like here: MVC Route Attribute error on two different routes

Or a custom route constraint, like here: https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/

You can create custom route constraints by implementing the IRouteConstraint interface. For example, the following constraint restricts a parameter to set of valid values:

public class ValuesConstraint : IRouteConstraint
{
    private readonly string[] validOptions;
    public ValuesConstraint(string options)
    {
        validOptions = options.Split('|');
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        object value;
        if (values.TryGetValue(parameterName, out value) && value != null)
        {
            return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
        }
        return false;
    }
}

The following code shows how to register the constraint:

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

        var constraintsResolver = new DefaultInlineConstraintResolver();

        constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));

        routes.MapMvcAttributeRoutes(constraintsResolver);
    }
}

Now you can apply the constraint in your routes:

public class TemperatureController : Controller
{
    // eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin
    [Route("temp/{scale:values(celsius|fahrenheit)}")]
    public ActionResult Show(string scale)
    {
        return Content("scale is " + scale);
    }
}

In my opinion, this isn't great design. There are no judgments about what URL you intended and no specificity rules when matching unless you explicitly set them yourself. But at least you can get your URLs looking the way you want. Hopefully your constraint list isn't too long. If it is, or you don't want to hard-code the route string parameter and its constraints, you could build it programmatically outside the action method and feed it to the Route attribute as a variable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top