Tuesday, January 17, 2012

EPiServer Composer and Commerce, getting global functions to work

In a default installation of EPiServer with Composer and Commerce the global functions and clipboard of Composer does not work. If you debug Composer you will find the following stack trace in the log file,


2012-01-17 11:57:09,893 ERROR [38] Dropit.Extension.Common.Logger.Error - Dropit.Extension.UI.Edit.CreateContentFunction: Value cannot be null.
Parameter name: page
System.ArgumentNullException: Value cannot be null.
Parameter name: page
at EPiServer.Business.Commerce.BreadcrumbsFactory.IsProductListingPage(PageData page)
at EPiServer.Business.Commerce.HttpModules.CatalogNodeModule.RemoveCache(PageData pageData)
at EPiServer.PageEventHandler.Invoke(Object sender, PageEventArgs e)
at EPiServer.Core.PageStoreBase.RaisePageEvent(Object key, PageEventArgs eventArgs)
at EPiServer.DataFactory.Copy(PageReference pageLink, PageReference destinationLink, AccessLevel requiredSourceAccess, AccessLevel requiredDestinationAccess, Boolean publishOnDestination, Boolean allowThreading)
at Dropit.Extension.Controllers.PageDataManager.CopyContentFunction(ContentFunctionData sourceContentFunctionData, PageReference targetPageLink, PageReference targetShadowPageRef)
at Dropit.Extension.Core.ContentFunctionData.Copy(PageReference targetPageLink)
at Dropit.Extension.UI.Edit.CreateContentFunction.Page_Load(Object sender, EventArgs e)

I don’t know why but when Composer copies the page and the create page event is called the page link in the event argument is null and when the argument null the function IsProductListingPage will throw and exception and the creation of the global function will not happen. It’s the same error when you try to use a content function that is placed on the clipboard.


The idea of how to solve this problem was to just inherit CatalogNodeModule and add a null check before the call but as RemoveCache is a private function that did not work. The next approach was to just reimplement the module but then we noticed that the call within the RemoveCache function is internal so that didn’t work either.

After some more reflection of the code I found that the only calls to the BreadcrumbsFactory and its caching (that would be affected by the RemoveCache call) were the Breadcrumb control in the Commerce demo templates, and as we don’t use them in the current project we decided to just remove the RemoveCache call all together and just create a new module with the code that was left. This is the final result and the module we run with,

public class CatalogNodeModule : IHttpModule
{
public void Init(HttpApplication context)
{
InitializationModule.FirstBeginRequest += new EventHandler(this.InitializationModule_FirstBeginRequest);
context.BeginRequest += new EventHandler(this.BeginRequest);
}

private void BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
if (context.Request.Url.AbsolutePath.ToUpperInvariant().Contains("IMAGE.ASHX") && context.Request.QueryString.AllKeys.Contains("epslanguage"))
{
string[] values = context.Request.QueryString.GetValues("epslanguage");
CatalogContext.MetaDataContext.UseCurrentUICulture = false;
CatalogContext.MetaDataContext.Language = values[0];
}
}

private void InitializationModule_FirstBeginRequest(object sender, EventArgs e)
{
RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
RouteTable.Routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");
RouteTable.Routes.MapRoute("ProductInCartOrWishList", "ProductInCartOrWishList/Edit/{id}", new { controller = "ProductInCartOrWishList", action = "Edit", id = "" });
RouteTable.Routes.MapRoute("CustomerProperties", "CustomerProperties/Edit/{id}", new { controller = "CustomerProperties", action = "Edit", id = "" });
RouteTable.Routes.MapRoute("OrdersFrequency", "OrdersFrequency/Edit/{id}", new { controller = "OrdersFrequency", action = "Edit", id = "" });
RouteTable.Routes.MapRoute("RecentSpent", "RecentSpent/Edit/{id}", new { controller = "RecentSpent", action = "Edit", id = "" });
RouteTable.Routes.MapRoute("RecentOrders", "RecentOrders/Edit/{id}", new { controller = "RecentOrders", action = "Edit", id = "" });
}

public void Dispose()
{
}
}

After removing the commerce http module and adding our own both the global functions and clipboard started working in Commerce.