Wednesday, June 29, 2016

Sitecore handling content URLs that change between environments

In some Sitecore implementations there can be various integration points to third party (or other internal web sites) which will have URLs (in content) which change based on environment (development, test or production).

An example of this would be the main content web site (in Sitecore) which has a number of links to an online application web site. In production the URL might be app.mydomain.com, and in test app-test.mydomain.com. The problem here is that as content is synced from production to test, the production links will be present in the test environment. This is not ideal as it can lead to test data in production which testers/content users may not notice - it also doesn't allow for fair end to end testing of the integrated systems.

Sitecore caching data when there is no context

When working with areas in Sitecore where the context is not resolving (such as custom index field or pipelines) the standard HttpContext.Current.Cache would not resolve because HttpContext.Current is null. In this case HttpRuntime.Cache will work instead.

I tend to use caching in custom search index fields where configuration is coming from a Sitecore item. This saves a lot calls to Sitecore and processing of the item to get the required data.

Wednesday, June 22, 2016

Glass Mapper error with web forms project

I added the Glass Mapper package to a Sitecore project which was web forms based and the following error was appearing when attempting to load the Sitecore admin area.
Could not resolve type name: Glass.Mapper.Sc.Pipelines.Response.GetModelFromView, Glass.Mapper.Sc.Mvc (method:
Sitecore.Configuration.Factory.CreateFromTypeName(XmlNode configNode, String[] parameters, Boolean assert)).
Looking into the details, Glass Mapper added a config file to my solution: Glass.Mapper.Sc.config. This file then referenced the MVC specific DLL: Glass.Mapper.Sc.Mvc in the pipelines. The fix was to remove the following section from the config file:

<mvc.getModel>
  <processor patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.GetModel.GetFromItem, Sitecore.Mvc']"  type="Glass.Mapper.Sc.Pipelines.Response.GetModel, Glass.Mapper.Sc.Mvc"/>
  <processor patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.GetModel.GetFromItem, Sitecore.Mvc']"  type="Glass.Mapper.Sc.Pipelines.Response.GetModelFromView, Glass.Mapper.Sc.Mvc"/>
</mvc.getModel>

It is a known issue.

Monday, June 20, 2016

Sitecore PXM InDesign is unable to load a project

When inside InDesign and attempting to load a Sitecore Print Experience Manager (PXM) project - via the Load the current item button, the project was not loading for me.


After looking into the printstudio.log older I noticed the following error:
ERROR Access to the path 'C:\inetpub\Jetstream\Website\Projects\sitecore\admin_a1wfr3ls\en\a9880262-14c3-4569-91d0-03aeaed946ed\' is denied.
ERROR BuildPublishXml Object reference not set to an instance of an object.
This was traced back to a setting inside Sitecore.PrintStudio.config - available in App_Config/Include/PXM. The PrintStudio.ProjectsFolder setting needs to be set to the PublishingCache folder rather than the default of projects. The correct setting should be something like: C:\inetpub\wwwroot\Jetstream\PXMPublishing\PublishingCache - which is the folder structure you created manually in the PXM installation.

This error may also trace back to permission/sharing of this folder depending on server/network architecture.

Sunday, June 19, 2016

Sitecore PXM DashBoardServer error - Message Queuing has not been installed

After following the Print Experience Manager installation guide the following error may appear in the DashBoardServerLog file.
ERROR HasJob:
Message Queuing has not been installed on this computer.
at System.Messaging.Interop.SafeNativeMethods.MQPathNameToFormatName(String pathName, StringBuilder formatName, Int32& count)
at System.Messaging.MessageQueue.ResolveFormatNameFromQueuePath(String queuePath, Boolean throwException)
at System.Messaging.MessageQueue.get_FormatName()
at System.Messaging.MessageQueue.GetMessageEnumerator2()
at System.Messaging.MessageQueue.GetAllMessages()
at Sitecore.PrintStudio.DashBoardServer.DashBoardWebService.HasJob(String serviceType)
This error occurs because Message Queuing (or MSMQ) has not been enabled on the server. Simpy go to the Control Panel and select turn Window features on or off. Then enable:

  • Microsoft Message Queue (MSMQ) Server
  • Microsoft Message Queue (MSMQ) Server Core
On windows server this will need to be done through the server manager, enable features.


Sitecore PXM InDesignProcessingService error with DashBoardWebService

When attempting to use PXM (Print Experience Manager) in Sitecore, the following error may appear in the InDesignProcessingService log file.
ERROR JobHandling
Could not connect to http://localhost:8070/DashBoardWebService. TCP error code 10061: No connection could be made because the target machine actively refused it 127.0.0.1:8070.
This is because the Sitecore Print Studio InDesign Processing service is not running (or accessible via network).

Sitecore PXM InDesign Server Service is not loading

In the installation guide for PXM, after starting the InDesign Server Service and assigning it the port of 8081, it should then load up in the browser using a URL such as http://localhost:8081/service?wsdl. However in my case, this was not occurring and the browser was unable to connect.

It turns out this was due to a licensing issue. To check this, the service can be loaded via command line:
  1. Change the command line directory to that of the Adobe InDesign Server path: C:\Program Files\Adobe\Adobe InDesign CC Server 2015 for example.
  2. Run the command: InDesignServer -port 8081
  3. If "Adobe InDesign server is not properly licensed and will now quit" displays, the license has not been installed correctly.
In my case this was due to a trial version, I simply downloaded Adobe_Provisioning_Toolkit_6_2_ALL and installed it to the desktop. Changed directgory to the install folder in the command line and ran the command: adobe_prtk --tool=StartTrial --leid=V7{}InDesignServer-11-Win-GM.

The service now loads correctly in the browser;


Thursday, June 9, 2016

Sitecore LinkManager.GetItemUrl duplicating the host name

I noticed an interesting feature of the LinkManager.GetItemUrl method (used in a custom module) in Sitecore where it was rerunning URLs with the host name twice. For example http://mysite.comhttp://mysite.com/link/to/page.

This was occurring because the module had UrlOptions with AlwaysIncludeServerUrl set to false and would assume that the link returned from LinkManager.GetItemUrl would never return the server URL.

However when the site definition (in Sitecore.config) has the targetHostName attribute set, the LinkManger will always return the server URL (from that setting). Which meant I had to update the module to handle this exception.

Thursday, June 2, 2016

Dynamic placeholders in sitecore

One problem with reusing Sitecore components that contain placeholders (such as a two column row) is that if you place more than one of the component on the page, the child elements will not render correctly - because there are two versions of the same placeholder on the page.

The solution to this issue is to use dynamic placeholder keys so that each version of a given component will have unique placeholders. This will then allow multiple components to work harmoniously together on the same page.

The solution below is for projects which are using web forms, there are plenty of solutions available online for those who are using MVC.

Defining the dynamic placeholder

The code below is a custom implementation of a Sitecore placeholder which will take the placeholder key and append the unique id of the rendering to ensure each key is unqiue.

using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using Sitecore.Common;
using Sitecore.Layouts;
using Sitecore.Web.UI;
using Sitecore.Web.UI.WebControls;

namespace MyProject.Helper
{
    public class DynamicKeyPlaceholder : WebControl, IExpandable
    {
        protected string _key = Placeholder.DefaultPlaceholderKey;
        protected string _dynamicKey = null;
        protected Placeholder _placeholder;

        public string Key
        {
            get
            {
                return _key;
            }
            set
            {
                _key = value.ToLower();
            }
        }

        protected string DynamicKey
        {
            get
            {
                if (_dynamicKey != null)
                {
                    return _dynamicKey;
                }
                _dynamicKey = _key;
                //find the last placeholder processed, will help us find our parent
                Stack<Placeholder> stack = Switcher<Placeholder, PlaceholderSwitcher>.GetStack(false);
                Placeholder current = stack.Peek();
                //find the rendering reference we are contained in
                var renderings = Sitecore.Context.Page.Renderings.Where(rendering => (rendering.Placeholder == current.ContextKey || rendering.Placeholder == current.Key) && rendering.AddedToPage);
                if (renderings.Count() > 0)
                {
                    //last one added to page defines our parent
                    var rendering = renderings.Last();
                    //use rendering reference unique ID to uniquely and permanently identify the placeholder
                    _dynamicKey = _key + rendering.UniqueId;
                }
                return _dynamicKey;
            }
        }

        protected override void CreateChildControls()
        {
            Sitecore.Diagnostics.Tracer.Debug("DynamicKeyPlaceholder: Adding dynamic placeholder with Key " + DynamicKey);
            _placeholder = new Placeholder();
            _placeholder.Key = this.DynamicKey;
            this.Controls.Add(_placeholder);
            _placeholder.Expand();
        }

        protected override void DoRender(HtmlTextWriter output)
        {
            base.RenderChildren(output);
        }

        #region IExpandable Members

        public void Expand()
        {
            this.EnsureChildControls();
        }

        #endregion
    }
}

Using a dynamic placeholder

Firstly to use the dynamic placeholder, a custom tag needs needs to be defined in the web.config under the controls node (<configuration><system.web><pages><controls>).
lt;add tagPrefix="dkp" namespace="WCC.Internet.UI.Helper" assembly="WCC.Internet.UI" />
Now a dynamic placeholder can be referenced on a sublayout/rendering:
<dkp:DynamicKeyPlaceholder ID="plSingle" runat="server" Key="singlecolumn" />
Don't forget to create placeholder settings for the placeholder key (singlecolumn in this case). Once rendered on the page it will appear unique with an appended GUID.

Allowing for placeholder settings

Now to allow placeholder settings to work with these dynamic placeholders.

public class GetDynamicKeyAllowedRenderings : GetAllowedRenderings
{
 //text that ends in a GUID
 private const string DYNAMIC_KEY_REGEX = @"(.+){[\d\w]{8}\-([\d\w]{4}\-){3}[\d\w]{12}}";

 public new void Process(GetPlaceholderRenderingsArgs args)
 {
  Assert.IsNotNull(args, "args");

  string placeholderKey = args.PlaceholderKey;
  Regex regex = new Regex(DYNAMIC_KEY_REGEX);
  Match match = regex.Match(placeholderKey);
  if (match.Success && match.Groups.Count > 0)
  {
   placeholderKey = match.Groups[1].Value;
  }
  else
  {
   return;
  }
  // Same as Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings but with fake placeholderKey
  Item placeholderItem = null;
  if (ID.IsNullOrEmpty(args.DeviceId))
  {
   placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
                args.LayoutDefinition);
  }
  else
  {
   using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
   {
    placeholderItem = Client.Page.GetPlaceholderItem(placeholderKey, args.ContentDatabase,
                 args.LayoutDefinition);
   }
  }
  List<Item> collection = null;
  if (placeholderItem != null)
  {
   bool flag;
   args.HasPlaceholderSettings = true;
   collection = this.GetRenderings(placeholderItem, out flag);
   if (flag)
   {
    args.CustomData["allowedControlsSpecified"] = true;
    args.Options.ShowTree = false;
   }
  }
  if (collection != null)
  {
   if (args.PlaceholderRenderings == null)
   {
    args.PlaceholderRenderings = new List<Item>();
   }
   args.PlaceholderRenderings.AddRange(collection);
  }
 }
}
And to have the chrome appear correctly in the page editor.
public class GetDynamicPlaceholderChromeData : GetChromeDataProcessor
{
 //text that ends in a GUID
 private const string DYNAMIC_KEY_REGEX = @"(.+){[\d\w]{8}\-([\d\w]{4}\-){3}[\d\w]{12}}";

 public override void Process(GetChromeDataArgs args)
 {
  Assert.ArgumentNotNull(args, "args");
  Assert.IsNotNull(args.ChromeData, "Chrome Data");
  if ("placeholder".Equals(args.ChromeType, StringComparison.OrdinalIgnoreCase))
  {
   string argument = args.CustomData["placeHolderKey"] as string;

   string placeholderKey = argument;
   Regex regex = new Regex(DYNAMIC_KEY_REGEX);
   Match match = regex.Match(placeholderKey);
   if (match.Success && match.Groups.Count > 0)
   {
    // Is a Dynamic Placeholder
    placeholderKey = match.Groups[1].Value;
   }
   else
   {
    return;
   }

   // Handles replacing the displayname of the placeholder area to the master reference
   Item item = null;
   if (args.Item != null)
   {
    string layout = ChromeContext.GetLayout(args.Item);
    item = Client.Page.GetPlaceholderItem(placeholderKey, args.Item.Database, layout);
    if (item != null)
    {
     args.ChromeData.DisplayName = item.DisplayName;
    }
    if ((item != null) && !string.IsNullOrEmpty(item.Appearance.ShortDescription))
    {
     args.ChromeData.ExpandedDisplayName = item.Appearance.ShortDescription;
    }
   }
  }
 }
}
Along with a patch file to register these two custom pipelines.
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <getPlaceholderRenderings>
        <processor
          type="MyProject.DynamicPlaceholders.GetDynamicKeyAllowedRenderings, MyProject"
          patch:before="processor[@type='Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings, Sitecore.Kernel']"/>
      </getPlaceholderRenderings>

      <getChromeData>
        <processor
          type="MyProject.DynamicPlaceholders.GetDynamicPlaceholderChromeData, MyProject"
          patch:after="processor[@type='Sitecore.Pipelines.GetChromeData.GetPlaceholderChromeData, Sitecore.Kernel']"/>
      </getChromeData>
    </pipelines>
  </sitecore>
</configuration>

You should now have dynamic placeholders working correctly. The main dynamic placeholder was taken from Nick Wesselman at Techphoria414. The placeholder settings/chrome was taken from user dunston on Stack Overflow.

Dynamic placeholders not showing in the Sitecore page editor

When working with an implementation of dynamic placeholders in Sitecore 8, I was not able to see my dynamic placeholders in the experience editor (aka page editor), yet I could use them by manually adding controls via the presentation details.

This was actually because of a setting in App_Config\Include\Sitecore.ExperienceEditor.config
WebEdit.PlaceholdersEditableWithoutSettings
Because the implementation of dynamic placeholders I was using at this time did not support placeholder settings, the config setting above (false by default) was stopping these placeholders from showing in page editor mode.

The fixes are to set this config setting to true (not recommended) or to find dynamic placeholders which support placeholder settings.