1

I am running Commerce SXA (XP 9.0.2 + XC 9.0.3) with two sites next to eachother. One site is non-SXA and the other SXA based. I have a controller with the same name as exists in Commerce SXA: CartController.

In my non-SXA based site I get the following stacktrace for MVC controller resolution:

Exception: System.InvalidOperationException
Message: Multiple types were found that match the controller named 'Cart'. This can happen if the route that services this request ('{*pathInfo}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.

The request for 'Cart' has found the following matching controllers:
Sitecore.Commerce.XA.Feature.Cart.Controllers.CartController
My.Feature.Checkout.Controllers.CartController
Source: System.Web.Mvc
   at System.Web.Mvc.DefaultControllerFactory.GetControllerTypeWithinNamespaces(RouteBase route, String controllerName, HashSet`1 namespaces)
   at System.Web.Mvc.DefaultControllerFactory.GetControllerType(RequestContext requestContext, String controllerName)
   at System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName)
   at Sitecore.Mvc.Controllers.SitecoreControllerFactory.CreateController(RequestContext requestContext, String controllerName)

I solved this error by fully qualifying the controller name in the Rendering: My.Feature.Checkout.Controllers.CartController, My.Assembly.

While in my SXA site I get the following stacktrace:

Exception: System.InvalidOperationException
Message: Multiple types were found that match the controller named 'cart'. This can happen if the route that services this request ('api/cxa/{controller}/{action}') does not specify namespaces to search for a controller that matches the request. If this is the case, register this route by calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.

The request for 'cart' has found the following matching controllers:
Sitecore.Commerce.XA.Feature.Cart.Controllers.CartController
My.Feature.Checkout.Controllers.CartController
Source: System.Web.Mvc
   at System.Web.Mvc.DefaultControllerFactory.GetControllerTypeWithinNamespaces(RouteBase route, String controllerName, HashSet`1 namespaces)
   at System.Web.Mvc.DefaultControllerFactory.GetControllerType(RequestContext requestContext, String controllerName)
   at System.Web.Mvc.DefaultControllerFactory.System.Web.Mvc.IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, String controllerName)
   at System.Web.Mvc.MvcRouteHandler.GetSessionStateBehavior(RequestContext requestContext)
   at System.Web.Mvc.MvcRouteHandler.GetHttpHandler(RequestContext requestContext)
   at Sitecore.Mvc.Routing.RouteHandlerWrapper.GetHttpHandler(RequestContext requestContext)
   at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context)
   at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

The second shows a different stacktrace, and fully qualifying (which does SXA by default) does not help.

Why is there a different stacktrace for non-SXA and SXA?

Is there another solution than using MVC areas for this?

3
  • Are you registering your controller in the DI container? Beside this, I would change your controller name to something like BasketController or CheckoutController (base on functionality), and the problem should be gone. – Dawid Rutkowski Mar 12 '19 at 18:42
  • Renaming would indeed by the easiest, but I have (way) more controllers. Thereby I would like to understand the cause of the issue (the controller is registered in the DI container). – Joost Mar 13 '19 at 7:55
  • Then you are registering two controllers with the same name to the container. I wouldn't recommend that. Especially that they both are of the same type - Controller. – Dawid Rutkowski Mar 13 '19 at 8:26
2

I'm afraid you'll have to use area's.. Or rename your controllers. To make it less rework, you could plugin a processor that scans assemblies and creates areas on startup. Only thing left to do is to add the area name to your renderings. I've tested this with the code below:

public class HelixAreaInitializationProcessor
{
    public virtual void Process(PipelineArgs args)
    {
        AddHelixAreas("*.Feature.*", "*.Foundation.*", "*.Project.*");
    }

    private void AddHelixAreas(params string[] assemblyFilters)
    {
        var assemblies = GetAssemblies(assemblyFilters);
        foreach (var assembly in assemblies)
        {
            var area = new HelixAreaRegistration(assembly);
            area.Register();
        }
    }

    private static Assembly[] GetAssemblies(IEnumerable<string> assemblyFilters)
    {
        var assemblies = new List<Assembly>();
        foreach (var assemblyFilter in assemblyFilters)
        {
            assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().Where(assembly => IsWildcardMatch(assembly.GetName().Name, assemblyFilter)).ToArray());
        }
        return assemblies.ToArray();
    }

    private static bool IsWildcardMatch(string input, string wildcard)
    {
        return input == wildcard || Regex.IsMatch(input, "^" + Regex.Escape(wildcard).Replace("\\*", ".*").Replace("\\?", ".") + "$", RegexOptions.IgnoreCase);
    }
}

class HelixAreaRegistration : AreaRegistration
{
    private readonly string _name;
    private readonly string[] _namespaces;

    public HelixAreaRegistration(Assembly assembly)
    {
        _name = assembly.GetName().Name;
        _namespaces = assembly.ExportedTypes
            .Where(x => typeof(IController).IsAssignableFrom(x))
            .Select(x => x.Namespace)
            .Distinct()
            .ToArray();
    }

    public override string AreaName => _name;

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            AreaName,
            $"{AreaName}/{{controller}}/{{action}}/{{id}}",
            new { action = "Index", id = UrlParameter.Optional },
            _namespaces
        );
    }

    public void Register()
    {
        RegisterArea(new AreaRegistrationContext(AreaName, RouteTable.Routes));
    }
}
2
  • 1
    Great piece of code, I added a Distinct() to the namespace calculation. – Joost Mar 15 '19 at 12:23
  • Good addition, i've added it in the example – Schuer Mar 15 '19 at 12:28
0

Based on above code, I managed to solve it by adding proper (Commerce SXA only) namespaces to the MapRoute calls in the Sitecore.Commerce.XA.Foundation.Common.Pipelines.Initialize.RegisterControllerRoutes processor:

protected virtual void Register()
{
     var namespaces = GetNamespaces("Sitecore.Commerce.XA.Feature.*", "Sitecore.Commerce.XA.Foundation.*");

     RouteTable.Routes.MapRoute(
         name: "ApiRoutes", 
         url: "{virtualFolder}/api/cxa/{controller}/{action}", 
         namespaces: namespaces);

     RouteTable.Routes.MapRoute(
         name: "RootApiRoutes",
         url: "api/cxa/{controller}/{action}",
         namespaces: namespaces);
}
1
  • This has been registered by Sitecore as a feature request with reference number 318288. – Joost Mar 21 '19 at 8:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.