I am upgrading my Sitecore Mvc Framework to Sitecore 8 and it broke due to two changes in Sitecore 8.
- Tag Injection Apps introduced in Sitecore 7.5
- 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(); } } }
Nice post Chandra.
How does this apply in Sitecore 8.1 where MVC area is supported out of the box?
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.
Thanks Chandra!