I am having an issue with ambiguous routes when using attribute routing. The problems stem from (I believe) using variable parameters at the root of our routes. What's vexing me is that literal parameters do not seem to be taking precedence, and MVC5 can't determine which route to use.
I've run into this before with other routes and thought I'd managed a fix by defining a convention route. With that in mind, where can I find more information on best practices for attribute routing, and resolving ambiguities?
Here's the code I'm having trouble with, as well as the exception.
Server Error in '/' Application.
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
AccountController
RoundController
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.
The request has found the following matching controller types:
AccountController
RoundController
RouteConfig.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// I added this constraint resolver to resolve some other ambiguous routes that end
// with a literal, but MVC5 wasn't able to determine whether to use those routes
var constraintResolver = new System.Web.Mvc.Routing.DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("notWriteAction", typeof(IsNotWriteActionConstraint));
routes.MapMvcAttributeRoutes(constraintResolver);
// This is the convention route I added to resolve another ambiguous route.
routes.MapRoute(
name: "Account",
url: "Account/{action}/{GroupName}/{AccessToken}",
defaults: new { controller = "Account", action = "Login", GroupName = UrlParameter.Optional, AccessToken = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
RoundController.cs
public class RoundController : ControllerBase
{
[Route("{groupid}/{campaignid}/{accesstoken}")]
public async Task<ActionResult> TempRoundLink(string groupid, string campaignid, string accesstoken)
{
}
}
AccountController.cs
public class AccountController : ControllerBase
{
[AllowAnonymous]
[Route("Account/ResetPassword/{token}")]
public async Task<ActionResult> ResetPassword(string token)
{
}
}