Search code examples
asp.net-mvcasp.net-mvc-5routesasp.net-mvc-5.2attributerouting

ASP .NET MVC 5.2 Inherited Route: Multiple controller types were found that match the URL


I'm trying to use a base controller with the [InheritedRoute] attribute. The base class and controllers are created like this:

[InheritedRoute("app/{controller}/{action=index}/{id?}")]
public abstract class MyBaseController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

public class DefaultController : MyBaseController { }

public class KyleController : MyBaseController { }

[RoutePrefix("app/support")]
[Route("{action=Index}/{id?}")]
public class SupportController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

The InheritedRouteAttribute class and stuff:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class InheritedRouteAttribute : Attribute, IDirectRouteFactory
{
    public string Name { get; set; }
    public int Order { get; set; }
    public string Template { get; }

    public InheritedRouteAttribute(string template)
    {
        this.Template = template;
    }

    public RouteEntry CreateRoute(DirectRouteFactoryContext context)
    {
        var controllerDescriptor = context.Actions.First().ControllerDescriptor;
        var controllerName = controllerDescriptor.ControllerName;
        string template;
        if (string.Equals(controllerName, "Default", StringComparison.OrdinalIgnoreCase))
        {
            template = this.Template.Replace("{controller}/", string.Empty);
        }
        else
        {
            template = this.Template.Replace("{controller}", controllerName);
        }

        IDirectRouteBuilder builder = context.CreateBuilder(template);
        builder.Name = this.Name ?? controllerName + "_Route";
        builder.Order = this.Order;

        return builder.Build();
    }
}

public class InheritedDirectRouteProvider : DefaultDirectRouteProvider
{
    protected override IReadOnlyList<IDirectRouteFactory> GetControllerRouteFactories(ControllerDescriptor controllerDescriptor)
    {
        return controllerDescriptor.GetCustomAttributes(typeof(IDirectRouteFactory), true).Cast<IDirectRouteFactory>().ToArray();
    }
}

In my RouteConfig.cs, I'm not using conventional routes:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapMvcAttributeRoutes(new InheritedDirectRouteProvider());
}

In essence, I want to make it so that if the user is in a DefaultController, the word "Default" is stripped out of the URL; otherwise, use the standard convention, prepended with "app". If I strip out Default (by taking out {controller}/), then I get the Multiple controller types error whenever I go to another URL. However, if I leave it, everything is fine, except the URL isn't as I wish.

I'm looking at RouteDebugger and seeing what is registered as my routes, and I see (as I expect):

  • app/Kyle/{action}/{id}
  • app/support/{action}/{id}
  • app/{action}/{id}

What am I missing? Why does it think I have a duplicate route, specifically on SupportController and DefaultController?


Solution

  • Let's look at these two route templates:

    • app/support/{action=Index}/{id?}
    • app/{action=index}/{id?}

    If you visit app/support both of them will match:

    • app/support/{action=Index}/{id?} - action is 'Index' by default, id is missing (optional).
    • app/{action=index}/{id?} - action is 'support' (!), id is missing (optional).

    The both routes match also if you visit app/support/something:

    • app/support/{action=Index}/{id?} - action is 'something', id is missing (optional).
    • app/{action=index}/{id?} - action is 'support', id is 'something'.

    So any url under 'app/support' with 2 or 3 segments will match both routes.

    Now when you understand what happens, you probably could figure out the fix by yourself. The easiest fix probably is to make those routes match different number of segments. The possible option is to remove default action for both routes and get rid of optional id parameter:

    • app/support/{action}
    • app/{action}

    Choose solution that satisfies the requirements you have. But make sure that 'support' segment from one route does not match the action part from another route as in your current routing.