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

3

19

...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.

Stagy answered 28/1, 2014 at 18:59 Comment(3)
Take a look at this that worked for me. #23095084Classless
This is an absolute disaster. It broke my entire site. Is there any real fix?Lucho
Could try regex: #26806895 Also going to try custom route constraint: blogs.msdn.microsoft.com/webdev/2013/10/17/…Galligaskins
L
12

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.

Larisa answered 28/1, 2014 at 19:36 Comment(4)
Does all of your answer apply to MVC controllers in addition to WebAPI?Stagy
Is there no other way to do this and keep the 2 actions in separate controllers?Dempster
@Dempster Yes, see Tony Borres' answer to this question. It worked perfectly for me.Demeter
Can you please link to documentation?Hines
M
22

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

Mccarty answered 9/9, 2015 at 12:17 Comment(2)
Thanks for the reference to a great SO question / answer.Vulture
This should be the correct answer. Specifying type is more explicit, clear, and allows you to resolve this issue across Controllers. Thanks this answer help me :)Guglielmo
L
12

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.

Larisa answered 28/1, 2014 at 19:36 Comment(4)
Does all of your answer apply to MVC controllers in addition to WebAPI?Stagy
Is there no other way to do this and keep the 2 actions in separate controllers?Dempster
@Dempster Yes, see Tony Borres' answer to this question. It worked perfectly for me.Demeter
Can you please link to documentation?Hines
G
3

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.

Galligaskins answered 20/7, 2017 at 5:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.