4

I have a super simple controller with 2 methods:

public IActionResult Users(long id)
{
    return Json(new { name = "Example User" });
}

public IActionResult Users()
{
    return Json(new { list = new List<User>() });
}

One to select all users and the other to return all users. In web api 2 I could user the following route and everything worked fine:

config.Routes.MapHttpRoute(
                name: "Users",
                routeTemplate: "v1/Users",
                defaults: new { action = "Users", controller = "Users" },
                constraints: null,
                handler: new TokenValidationHandler() { InnerHandler = new HttpControllerDispatcher(config) }
            );

I have the following routes setup in startup.cs:

app.UseMvc(routes =>
            {
                routes.MapRoute(name: "User_Default", template: "v1/{controller=Users}/{action=Users}/{id?}");
            });

However this gives me a AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied

What am I doing wrong?

2
  • I'm going to guess that you have an attribute Route on your controller, which is there in many templates. Can you provide your whole controller? Commented Jun 21, 2015 at 13:09
  • @MattDeKrey I don't as I didn't think that they would be needed if I simply set my routes up like webapi 2 routes Commented Jun 21, 2015 at 20:40

1 Answer 1

6

In your original webapi code, you were using Routes.MapHttpRoute which adds webapi specific routes. This is different from an MVC route which won´t take into account the parameters in the action, for instance you would have the same problem in MVC 5 if you were using Routes.MapRoute.

The same thing is happening in your MVC 6 code, since you are adding a standard MVC route using routes.MapRoute. In both cases the framework is finding 2 controller actions matching the same route with no additional constraints. It needs some help in order to select one of those 2 actions.

The easiest way to disambiguate the api actions would be using attribute routing instead of defining a route, as in this example:

[Route("v1/[controller]")]
public class UsersController : Controller
{
    [HttpGet("{id:int}")]
    public IActionResult Users(long id)
    {
        return Json(new { name = "Example User" });
    }

    public IActionResult Users()
    {
        return Json(new { list = new[] { "a", "b" } });
    }
}

There are other options that would let you change the behaviour of the MVC routing in MVC 6. You could create your own IActionConstraint attribute to enforce having or not a given parameter. That way one of those actions requires an id parameter in the route while the other requires not to have an id parameter (Warning, untested code):

public class UsersController : Controller
{
    [RouteParameterConstraint("id", ShouldAppear=true)]
    public IActionResult Users(long id)
    {
        return Json(new { name = "Example User" });
    }

    [RouteParameterConstraint("id", ShouldNotAppear=true)]
    public IActionResult Users()
    {
        return Json(new { list = new[] { "a", "b" } });
    }
}

public class RouteParameterConstraintAttribute : Attribute, IActionConstraint
{
    private routeParameterName;

    public RouteParameterConstraintAttribute(string routeParameterName)
    {
        this.routerParamterName = routerParameterName;
    }

    public int Order => 0;
    public bool ShouldAppear {get; set;}
    public bool ShouldNotAppear {get; set;}

    public bool Accept(ActionConstraintContext context)
    {
        if(ShouldAppear) return context.RouteContext.RouteData.Values["country"] != null;
        if(ShouldNotAppear) return context.RouteContext.RouteData.Values["country"] == null;

        return true;
    }
}

A better option to deal with webapi 2 style controllers would be adding conventions into the MVC pipeline. This is exactly what the Microsoft.AspNet.Mvc.WebApiCompatShim is doing to help migrating webapi 2 controllers. You can see the conventions added here. Check this guide for a quick overview of this package.

Sign up to request clarification or add additional context in comments.

4 Comments

I've marked this as the correct answer but I'd also love to know why specifying the route in the startup.cs file doesn't work
@Gazeth, I have added more details to my answer
Updated. Also got rid of the RouteConstraintAttribute in favor or IActionConstraint

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.