Sitecore 8: Fix MVC area execution and registration

I am upgrading my Sitecore Mvc Framework to Sitecore 8 and it broke due to two changes in Sitecore 8.

  1. Tag Injection Apps introduced in Sitecore 7.5
  2. Social Client area introduced in Sitecore 8

Both applications are designed as MVC Areas in Sitecore 8 and they individually try to register themselves as MVC areas in different pipeline.

Issue#1
If you are using Sitecore with multiple MVC Areas then this post might help you to resolve this issue. Most of the solution (example below) like my solution insert ‘area’ key and it’s value in route data token to let MVC infrastructure identify the correct area and create the controller instance to execute the action method but after execution during cleanup we set the ‘area’ value as null for consistency.

e.g.
Sitecore MvcContrib
Sitecore MVC in a multisite environment: area’s

Sitecore 7.5 introduced Tag Injection Apps designed in MVC and it has specific implementation of ControllerFactory (TagInjectionControllerFactory) and IDependencyResolver (TagInjectionDependencyResolver), this custom TagInjectionControllerFactory implementation checks if controller request is for AppCenter area before passing it to MVC’s implementation. While comparing the area value it throws object null exception if area value is null.

Sitecore 7.5 Method in question Class – TagInjectionControllerFactory

 public bool CanHandle(RequestContext requestContext)
 {
 if (!requestContext.RouteData.DataTokens.ContainsKey("area"))
 {
 return false;
 }
 return string.Equals(requestContext.RouteData.DataTokens["area"].ToString(), this.areaName, StringComparison.InvariantCultureIgnoreCase);
 }

In order to avoid this issue it’s better to set the route data token value to empty string or better remove it.

 protected override void ExecuteController(Controller controller)
        {
            RequestContext requestContext = PageContext.Current.RequestContext;
            var value = requestContext.RouteData.Values["controller"];
            var value2 = requestContext.RouteData.Values["action"];
            var value3 = requestContext.RouteData.DataTokens["area"];
            var value4 = requestContext.RouteData.DataTokens["namespace"];

            try
            {
                requestContext.RouteData.Values["controller"] = ActualControllerName;
                requestContext.RouteData.Values["action"] = ActionName;
                requestContext.RouteData.DataTokens["area"] = Area;

                var namespaces = new[] { string.Empty };
                if (!string.IsNullOrWhiteSpace(Namespace))
                    namespaces = Namespace.Split(new[] { ',' }, System.StringSplitOptions.RemoveEmptyEntries);

                requestContext.RouteData.DataTokens["namespace"] = namespaces;
                ((IController)controller).Execute(PageContext.Current.RequestContext);
            }
            finally
            {
                requestContext.RouteData.Values["controller"] = value;
                requestContext.RouteData.Values["action"] = value2;
                
                // Perform cleanup to avoid any issues
                if (value3 != null)
                    requestContext.RouteData.DataTokens["area"] = value3;
                else
                    requestContext.RouteData.DataTokens.Remove("area");

                if (value4 != null)
                    requestContext.RouteData.DataTokens["namespace"] = value4;
                else
                    requestContext.RouteData.DataTokens.Remove("namespace");
            }
        }

Issue#2

This issue is more to my solution specific where I am using WebActivatorEx.PostApplicationStartMethod to register all the dependencies including MVC Area Registration avoid hard binding with Application Start event. With Sitecore 8, Sitecore has introduced a new area for Social client which has its own pipeline which hooks into Sitecore’s Initialize pipeline and register the ‘Social’ area. As my code was executing after initialize pipeline has executed it was throwing below exception.

Server Error in '/' Application.

A route named 'SocialConnectorSpecific' is already in the route collection. Route names must be unique.
Parameter name: name 
  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.ArgumentException: A route named 'SocialConnectorSpecific' is already in the route collection. Route names must be unique.
Parameter name: name

Source Error: 

Line 24:         public static void Start()
Line 25:         {
Line 26:             AreaRegistration.RegisterAllAreas();
Line 27:             IoC.InitializeWith(new DependencyContainerFactory());
Line 28:             TypeRegistration.RegisterTypes();

Stack Trace: 
[ArgumentException: A route named 'SocialConnectorSpecific' is already in the route collection. Route names must be unique.
Parameter name: name]
   System.Web.Routing.RouteCollection.Add(String name, RouteBase item) +2445958
   System.Web.Mvc.RouteCollectionExtensions.MapRoute(RouteCollection routes, String name, String url, Object defaults, Object constraints, String[] namespaces) +394
   System.Web.Mvc.AreaRegistrationContext.MapRoute(String name, String url, Object defaults, Object constraints, String[] namespaces) +147
   System.Web.Mvc.AreaRegistrationContext.MapRoute(String name, String url, Object defaults, Object constraints) +53
   Sitecore.Social.Client.Mvc.Areas.Social.SocialAreaRegistration.RegisterArea(AreaRegistrationContext context) +154
   System.Web.Mvc.AreaRegistration.CreateContextAndRegister(RouteCollection routes, Object state) +196
   System.Web.Mvc.AreaRegistration.RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, Object state) +238
   System.Web.Mvc.AreaRegistration.RegisterAllAreas(Object state) +75
   System.Web.Mvc.AreaRegistration.RegisterAllAreas() +24

As, I am trying to avoid any code in Global.asax or Web Application for initialization and writing outside in a bootstrap assembly, I created a pipeline which will hook into Sitecore’s initialize method and call AreaRegistration and all other application initialization code.

<initialize>
        <processor type="Framework.Bootstrap.Start.Bootstrapper, Framework.Bootstrap" patch:before="processor[@type='Sitecore.Pipelines.Loader.ShowVersion, Sitecore.Kernel']" />
      </initialize>

Pipeline –

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(Framework.Bootstrap.Start.Bootstrapper), "Initialize")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(Framework.Bootstrap.Start.Bootstrapper), "Shutdown")]

namespace Framework.Bootstrap.Start
{
    public class Bootstrapper
    {
        public static void Initialize()
        {
            // Register our modules
            Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(ApplicationErrorModule));
            //DynamicModuleUtility.RegisterModule(typeof(ClaimsTransformationHttpModule));
        }

        public static void Start()
        {
            AreaRegistration.RegisterAllAreas();
            IoC.InitializeWith(new DependencyContainerFactory());
            TypeRegistration.RegisterTypes();
            DependencyResolver.SetResolver(new CustomDependencyResolver());
            ValueProviderFactories.Factories.Add(new TempDataModelProviderFactory());
        }

        public static void Shutdown()
        {
        }

        public virtual void Process(object args)
        {
            Start();
        }
    }
}

About cprakash

A developer, passionate about .Net, Architecture and Security.
This entry was posted in Sitecore MVC and tagged . Bookmark the permalink.

3 Responses to Sitecore 8: Fix MVC area execution and registration

  1. KD says:

    Nice post Chandra.

    How does this apply in Sitecore 8.1 where MVC area is supported out of the box?

    • cprakash says:

      Thanks, this doesn’t apply to Sitecore 8.1 as Sitecore already scans all assemblies and load/register areas in InitializeRoutes processor (see Sitecore.Mvc.Config). Along with this implementation Sitecore provides another option to specify areas which you want to exclude from standard area registration process.

  2. KD says:

    Thanks Chandra!

Leave a comment