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)
{
}
}
I intended to write up something much longer than this, but I haven't had the time. The solution I ended up going with was to strip out any calls to Microsoft's attribute routing library, and to instead use the "Attribute Routing" package found here. http://htmlpreview.github.io/?https://github.com/mccalltd/AttributeRouting/blob/gh-pages/index.html
It's been working very well, and the *Precedence properties resolve the exact issue I was having in my original question.