Monday, September 27, 2010

Image Field in Page Editor [Common Errors]


This issue is related to the latest *6.2 rev.100831 (Update-4)* only.

You may be getting the following error when launching Page Editor on a page with an image field rendered via <sc:image /> Web or XSL Control:

at Sitecore.Diagnostics.Assert.ArgumentNotNull(Object argument, String argumentName)
  at Sitecore.Resources.Media.MediaProvider.GetMediaUrl(MediaItem item, MediaUrlOptions options)
  at Sitecore.Xml.Xsl.ImageRenderer.GetSource()
  at Sitecore.Xml.Xsl.ImageRenderer.Render()
  at Sitecore.Pipelines.RenderField.GetImageFieldValue.Process(RenderFieldArgs args)
  at (Object , Object[] )
  at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
  at Sitecore.Web.UI.WebControls.FieldRenderer.RenderField()
  at Sitecore.Xml.Xsl.XslHelper.image(String fieldName, XPathNodeIterator iterator, String parameters)

While our team is working on an official document about, here is a quick 3 step solution:

1. Create a custom ImageRenderer class:

using System;
using Sitecore.Data.Fields;
namespace SitecoreSupport.Shell
{
   public class ImageRenderer : Sitecore.Xml.Xsl.ImageRenderer
   {
      protected override string GetSource()
      {
         var innerField = Item.Fields[FieldName];
         var imageField = new ImageField(innerField, FieldValue);
         ParseField(imageField);
         if (imageField.MediaItem != null)
         {
            return base.GetSource();
         }
         return String.Empty;
      }
   }
}

2. Create a custom implementation of GetImageFieldValue processor in the renderField pipeline:

namespace SitecoreSupport.Shell
{
   public class GetImageFieldValue : Sitecore.Pipelines.RenderField.GetImageFieldValue
   {
      protected override Sitecore.Xml.Xsl.ImageRenderer CreateRenderer()
      {
         return new ImageRenderer();
      }
   }
}

3. Create a .config file with the following content and place it under /App_Config/Include:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
   <sitecore>
      <pipelines>
         <renderField>
            <processor type="Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel">
               <patch:attribute name="type">SitecoreSupport.Shell.GetImageFieldValue, SitecoreSupport.Shell</patch:attribute>
            </processor>
         </renderField>
      </pipelines>
   </sitecore>
</configuration>

Monday, September 20, 2010

Optimize Sitecore Performance Checklist


Greetings! I’ve stumbled upon this hot discussion on StackOverflow about Sitecore CMS performance and could help but sharing a few thoughts on the subject.
The message I am trying to convey is that the product is built for performance and scalability. After seeing a fair number of successful implementations that have the unlimited scaling capacity, it is sad for me to see some developers struggle.
We all love checklists, so here is a slightly modified list of things to look out for and try out when you start having performance degradation. Each case is special, so apologies if most of this is not applicable to your situation.

Friday, September 10, 2010

Campaign Summary report is throwing exception [Common Errors]


A quick post today in the Common Errors category. You may be getting the following exception when trying to run Summary report on your Campaign:

image


Object reference not set to an instance of an object.

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.NullReferenceException: Object reference not set to an instance of an object.

Stack Trace:

[NullReferenceException: Object reference not set to an instance of an object.]
   Stimulsoft.Report.StiReport.GetReferencedAssemblies() +1179
...

To resolve this, you need a reference to Sitecore.Oracle.dll within the source of Campaign Summary report.

1. Open the following file: \sitecore\shell\Applications\Analytics\Reports\Campaigns\Summary.mrt

2. Add the following to the <ReferencedAssemblies /> section:
image

That’s all!

Friday, September 03, 2010

Optimizing Sitecore Link Database Rebuild


Greetings! One of the topics I covered during my Dreamcore presentation on Data Access Techniques with Sitecore CMS was Link Database. While it is a great feature that enables fast and efficient retrieval of item relations, there is one caveat, it requires some maintenance and configuration to be working properly in multi server environment. Many customers face this challenge, and while there is a number of ways you can tackle this, the approach I want to share today seems like the most appropriate to me.

The idea behind the scenes is to separate the Link Database used on the Content Delivery side from the one used on the Content Management side by introducing a second link database. This database will be updated during item publishing. This way if CMS decides to update its own Link DB, the one used on the front-end will not be affected.

So, here is what you need to do:

1. Add the FrontEndLinkDatabase definition below the default <LinkDatabase> in web.config file of the CMS server:

<!-- LINK DATABASE -->
<LinkDatabase type="Sitecore.Data.$(database).$(database)LinkDatabase,
                    Sitecore.Kernel">
   <param connectionStringName="core" />
</LinkDatabase>
<!-- FRONT END LINK DATABASE -->
<FrontEndLinkDatabase type="Sitecore.Data.$(database).$(database)LinkDatabase,
                            Sitecore.$(database)">
   <param desc="connection" ref="connections/web" />
</FrontEndLinkDatabase>

2. Compile the following code that contains two Sitecore event handlers (publish:itemprocessed and publish:itemprocessing)

using System;
using Sitecore.Configuration;
using Sitecore.Links;
using Sitecore.Publishing;
using Sitecore.Publishing.Pipelines.PublishItem;
using Sitecore.Diagnostics;
namespace Sitecore.SharedSource.LinkDatabaseRebuilder
{
   public class EventHandler
   {
      protected static LinkDatabase FrontEndLinkDatabase
      {
         get
         {
            return (Factory.CreateObject("FrontEndLinkDatabase", true) 
                     as LinkDatabase);
         }
      }
      public string Database { get; set; }
      public void OnItemProcessed(object sender, EventArgs args)
      {
         var context = ((ItemProcessedEventArgs)args).Context;
         Assert.IsNotNull(context, 
         "Cannot get PublishItem context");
         Assert.IsNotNull(FrontEndLinkDatabase,
         "Cannot resolve FrontEndLinkDatabase from config");
         if (context.PublishOptions.TargetDatabase.Name.Equals(Database))
         {
            var item = context.PublishHelper.GetTargetItem(context.ItemId);
            // if an item was not unpublished, 
            // the call below will reintroduce the reference
            // removed within OnItemProcessing method
            if (item != null)
            {
               FrontEndLinkDatabase.UpdateReferences(item);
            }
         }
      }
      public void OnItemProcessing(object sender, EventArgs args)
      {
         var context = ((ItemProcessingEventArgs)args).Context;
         Assert.IsNotNull(context,
         "Cannot get PublishItem context");
         Assert.IsNotNull(FrontEndLinkDatabase, 
         "Cannot resolve FrontEndLinkDatabase from config");
         if (context.PublishOptions.TargetDatabase.Name.Equals(Database))
         {
            if (context.Action == PublishAction.DeleteTargetItem)
            {
               var item = context.PublishHelper.GetTargetItem(context.ItemId);
               Assert.IsNotNull(item, "Source item cannot be found");
               FrontEndLinkDatabase.RemoveReferences(item);
            }
         }
      }
   }
}

3. Last thing to do is to define the two event handler mapping in web.config.

<events>
   <event name="publish:itemProcessing">
    <handler type="Sitecore.SharedSource.LinkDatabaseRebuilder.EventHandler, 
Sitecore.SharedSource.LinkDatabaseRebuilder"
method="OnItemProcessing">
      <database>web</database>
     </handler>
   </event>
   <event name="publish:itemProcessed">
     <handler type="Sitecore.SharedSource.LinkDatabaseRebuilder.EventHandler,
Sitecore.SharedSource.LinkDatabaseRebuilder"
method="OnItemProcessed">
       <database>web</database>
      </handler>
   </event>
 </events>

That’s all you need to do. For the sake of simplicity, here is the link to the whole VS project where you can find the auto-include file.

While this solution has been tested on 6.2, it is expected to work on all Sitecore 6 installations.

Let me know if it does not ;-)

Thursday, September 02, 2010

Publish to pre-production web database. Part 2.


A while back I shared a technique of publishing to a pre-production database via a custom ExtendedPublishProvider. As much as I like the clarity of this approach, as Kamsar mentioned in the comments, “…it has a downside: if you run a smart publish site to your staging database it will "unstage" any items that workflow staged into them with the current published version, ignoring the workflow state.”

Valid point.

After some internal brainstorming on the subject with our US based tech team, here is another approach to the problem that eliminates the drawback mentioned above.

What you can actually do is plug in a custom WorkflowProvider which will refer to a custom implementation of Sitecore.Workflows.Simple.Workflow class. This class has a method IsApproved(Item item) that is called from all publishing operations. We are going to override that and check if the item we are checking approval for is currently in a “semi-final” workflow state and if the database we publish to matches the designated “pre-production” database. Pretty simple, huh?

Here is the code.

1. First you need a custom version of the WorkflowProvider implementation:

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Workflows;
namespace Sitecore.Starterkit.Workflow
{
   public class WorkflowProvider : Sitecore.Workflows.Simple.WorkflowProvider
   {
      public WorkflowProvider(string databaseName, HistoryStore historyStore)
         : base(databaseName, historyStore)
      {
      }
      public override IWorkflow GetWorkflow(Item item)
      {
         Assert.ArgumentNotNull(item, "item");
         string workflowID = GetWorkflowID(item);
         if (workflowID.Length > 0)
         {
            // customization
            return new AdvancedWorkflow(workflowID, this);
            // customization
         }
         return null;
      }
      private static string GetWorkflowID(Item item)
      {
         Assert.ArgumentNotNull(item, "item");
         WorkflowInfo workflowInfo = item.Database.DataManager.GetWorkflowInfo(item);
         if (workflowInfo != null)
         {
            return workflowInfo.WorkflowID;
         }
         return string.Empty;
      }
      public override IWorkflow GetWorkflow(string workflowID)
      {
         Assert.ArgumentNotNullOrEmpty(workflowID, "workflowID");
         Error.Assert(ID.IsID(workflowID), "The parameter 'workflowID' must be parseable to an ID");
         if (Database.Items[ID.Parse(workflowID)] != null)
         {
            // customization
            return new AdvancedWorkflow(workflowID, this);
            // customization
         }
         return null;
      }
      public override IWorkflow[] GetWorkflows()
      {
         Item item = this.Database.Items[ItemIDs.WorkflowRoot];
         if (item == null)
         {
            return new IWorkflow[0];
         }
         Item[] itemArray = item.Children.ToArray();
         IWorkflow[] workflowArray = new IWorkflow[itemArray.Length];
         for (int i = 0; i < itemArray.Length; i++)
         {
            // customization
            var wfId = itemArray[i].ID.ToString();
            workflowArray[i] = new AdvancedWorkflow(wfId, this);
            // customization
         }
         return workflowArray;
      }
   }
}

2. Then you will need to attach it to the “master” database:

<!-- master -->
<database id="master" singleInstance="true" type="Sitecore.Data.Database, Sitecore.Kernel">
   <param desc="name">$(id)</param>
   ...
   <workflowProvider hint="defer" type="Sitecore.Starterkit.Workflow.WorkflowProvider, Sitecore.Starterkit">
      <param desc="database">$(id)</param>
      <param desc="history store" ref="workflowHistoryStores/main" param1="$(id)" />
    </workflowProvider>

 

3. Since the custom WorkflowProvider is referring to AdvancedWorkflow implementation, you need the code for that:

using System;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.SecurityModel;
using Sitecore.Workflows;
using Version = Sitecore.Data.Version;
namespace Sitecore.Starterkit.Workflow
{
   public class AdvancedWorkflow : Sitecore.Workflows.Simple.Workflow
   {
      private readonly WorkflowProvider _owner;
      public AdvancedWorkflow(string workflowID, WorkflowProvider owner)
                             : base(workflowID, owner)
      {
         _owner = owner;
      }
      private Database Database
      {
         get
         {
            return _owner.Database;
         }
      }
      public override bool IsApproved(Item item)
      {
         var result = base.IsApproved(item);
         if (!result && 
            Context.Site.Name.Equals("publisher",
                                     StringComparison.InvariantCultureIgnoreCase))
         {
            var stateItem = GetStateItem(item);
            if (stateItem != null &&
                MatchTargetDatabase(stateItem) &&
                IgnoreWorkflow(stateItem))
            {
               result = true;
            }
         }
         return result;
      }
      protected virtual bool MatchTargetDatabase(Item stateItem)
      {
         if (Context.Job != null && !String.IsNullOrEmpty(Context.Job.Name))
         {
            var target = TargetDatabase(stateItem);
            return Context.Job.Name.Equals(
                     String.Format("Publish to '{0}'", target), StringComparison.InvariantCultureIgnoreCase);
         }
         return false;
      }
      protected virtual string TargetDatabase(Item stateItem)
      {
         var publishTargetId = stateItem["Semi-Final Target Database"];
         var publishTargetItem = PublishActionHelper.GetItemById(publishTargetId);
         if(publishTargetItem != null)
         {
            return PublishActionHelper.GetFieldValue(publishTargetItem, "Target database");
         }
         return String.Empty;
      }
      protected virtual bool IgnoreWorkflow(Item stateItem)
      {
         return stateItem["Semi-Final"] == "1";
      }
      private Item GetStateItem(Item item)
      {
         string stateID = GetStateID(item);
         if (stateID.Length > 0)
         {
            return GetStateItem(stateID);
         }
         return null;
      }
      private Item GetStateItem(ID stateId)
      {
         return ItemManager.GetItem(stateId, Language.Current, Version.Latest, Database, SecurityCheck.Disable);
      }
      private Item GetStateItem(string stateId)
      {
         ID id = MainUtil.GetID(stateId, null);
         return id == (ID)null ? null : this.GetStateItem(id);
      }
      private string GetStateID(Item item)
      {
         Assert.ArgumentNotNull(item, "item");
         WorkflowInfo workflowInfo = item.Database.DataManager.GetWorkflowInfo(item);
         if (workflowInfo != null)
         {
            return workflowInfo.StateID;
         }
         return string.Empty;
      }
   }
}

All the magic is happening within “IsApproved” method that we override. We check if both “MatchTargetDatabase()” and “IgnoreWorkflow()” methods return true. The majority of other methods in this class are here because the derived class could not inherit those.

4. I reference PublishActionHelper class here too, it provides some utility methods for me:

 public class PublishActionHelper
   {
      public static Database Db
      {
         get { return Context.ContentDatabase ?? Context.Database ?? Factory.GetDatabase("master"); }
      }
      public static string GetFieldValue(Item item, string fieldName)
      {
         return item[fieldName] ?? String.Empty;
      }
      public static Item GetItemById(string id)
      {
         if (ID.IsID(id))
         {
            return Db.GetItem(ID.Parse(id));
         }
         return null;
      }
      public static string GetTargetDatabaseName(string targetId)
      {
         var publishingTarget = Db.SelectSingleItem(targetId);
         return publishingTarget["Target database"] ?? String.Empty;
      }
      public static IEnumerable<Item> GetItemsFromMultilist(Item carrier, string fieldName)
      {
         var multilistField = carrier.Fields[fieldName];
         if(FieldTypeManager.GetField(multilistField) is MultilistField)
         {
            return ((MultilistField)multilistField).GetItems();
         }
         return new Item[0];
      }
   }

5. Finally, you will need to have the extended version of the WorkflowState template where you can set the needed flags and settings:

image

I created my own “Semi Final State” template which inherits from standard /System/Workflow/State template.

image

This solution has been provided to a customer, and appears to be working fine in production for a couple of months now.

Let me know what you think!

Wednesday, September 01, 2010

Exception in alarm clock event subscriber [Common Errors]


Greetings! As you are going into production, hopefully you allocate a few days for exception sweep up. This is applicable to any website running on Sitecore CMS or any platform, really.

One of the common exceptions that I see in customer’s log files these days is related to “alarm clock” event subscriber and can be encountered quite frequently.

Here is the error stack trace that you may be seeing in your logs:

ERROR Exception in alarm clock event subscriber.
Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
   at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Sitecore.Reflection.ReflectionUtil.InvokeMethod(MethodInfo method, Object[] parameters, Object obj)
   at Sitecore.Configuration.Factory.AssignProperties(Object obj, Object[] properties)
   at Sitecore.Configuration.Factory.AssignProperties(XmlNode configNode, String[] parameters, Object obj, Boolean assert, Boolean deferred, IFactoryHelper helper)
   at Sitecore.Configuration.Factory.CreateObject(XmlNode configNode, String[] parameters, Boolean assert, IFactoryHelper helper)
   at Sitecore.Pipelines.CorePipelineFactory.GetObjectFromType(String type, XmlNode processorNode)
   at Sitecore.Pipelines.CoreProcessor.GetMethod(Object[] parameters)
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
   at Sitecore.Services.AlarmClock.Heartbeat_Beat(Object sender, EventArgs e)

Nested Exception

Exception: System.InvalidOperationException
Message: Category does not exist.
Source: System
   at System.Diagnostics.PerformanceCounterLib.GetCategorySample(String machine, String category)
   at System.Diagnostics.PerformanceCounterCategory.GetCounterInstances(String categoryName, String machineName)
   at Sitecore.Diagnostics.PerformanceCounters.PerformanceCounter.GetInstanceName()
   at Sitecore.Pipelines.HealthMonitor.HealthMonitor.AddCounter(XmlNode configNode)

================================

After a quick research in our knowledge base, I’ve discovered an answer. Apparently, this has been addressed in Sitecore CMS 6.2.0 rev.100104 (Also known as 6.2.0 Update-1).

For 6.0/6.1 instances, you can comment out the following part of your web.config file as a temporary workaround:

<processor type="Sitecore.Pipelines.HealthMonitor.HealthMonitor, Sitecore.Kernel" method="LogCounterStatus">
   <counters hint="raw:AddCounter">
    <!--  <counter category="Process" name="Private Bytes" />
      <counter category="Process" name="Virtual Bytes" />
      <counter category="Process" name="Page File Bytes" />
      <counter category=".net CLR Memory" name="# Bytes in all Heaps" />
      <counter category=".net CLR Memory" name="% Time in GC" />
      <counter category=".net CLR Memory" name="Large Object Heap size" />
      <counter category=".net CLR Loading" name="Bytes in Loader Heap" />
      <counter category=".net CLR Loading" name="Current Assemblies" />-->
   </counters>
</processor>

If you are still on 6.0 or 6.1, start thinking about upgrading. It is especially easy to do it from 6.1! Check the official steps here.

Publish to pre-production web database


Update 3/11/2010: Correction » the last parameter of the ExtendedPublishOptions constructor should be “false”.  Please see below for details.

Update 9/2/2010: Addition » published an alternative solution to this problem here.

In my experience with enterprise level implementations, there is often a need for a separate “stage environment” for final content preview or a pre-production phase of workflow.
It is also often the case that the environmental limitations or restrictions make the content delivery part of the authoring environment suitable for such purpose. For example, your e-commerce infrastructure could not be available from the authoring instance. One of the possible ways to approach such a requirement is to configure a stand-alone staging instance of Sitecore which will be used for content delivery instance and is commonly configured exactly like one of the servers in the web farm. There is one exception though – the database that is used for content delivery is generally different from the one that is used in production content delivery.
With this approach, now the question is how to plug in workflow in a way that content could go via this “stage” environment and then only upon final approval will be able to get to “production”.
Now there is a problem that you cannot get get items published until they are in final workflow state. You could have two workflow states “Staged” and “Published”, both marked as final and with auto publish actions connected.
I don’t like this approach since it goes against the workflow nature – only one state should be marked as final.
Alternatively, you can plug in a “copy item” workflow action to the “Staged” workflow state where you can programmatically copy an item from master database to stage web.
While this approach does seem legitimate, it works around the need of publishing thus any additional processing you may be having in publish(item) pipeline would not work.
I looked into the option of having publishing process ignore workflow when publishing to stage. While this seemed like a dangerous path to go, I have soon discovered that the PublishHelper’s GetVersionToPublish method already accepts this notion of passing a “requireapproval” flag to the underlying publishing mechanism. Since it is always passes “true”, I started looking into ways to make this flag dynamic and figured that setting it on the level of PublishOptions could be a good idea. For example, from the code of my workflow action I would be able to define whether I want to have workflow respected or not.
Here is what you will need to do to make it work.
First, override the default PipelinePublishProvider and plug it into the web.config:
<publishManager defaultProvider="default" enabled="true">
    <providers>
        <clear />
         <!--<add name="default" type="Sitecore.Publishing.PipelinePublishProvider, Sitecore.Kernel" />-->
         <add name="default" type="SCUSAINC.Publishing.ExtendedPublishProvider, SCUSAINC.Publishing" />
     </providers>
</publishManager>
Secondly, you will need to override the CreatePublishHelper class:
public class ExtendedPublishProvider : PipelinePublishProvider
    {
        public override PublishHelper CreatePublishHelper(PublishOptions options)
        {
            Assert.ArgumentNotNull(options, "options");
            if (options is ExtendedPublishOptions)
                return new ExtendedPublishHelper(options as ExtendedPublishOptions);

            return new PublishHelper(options);
        }
    }

After that, introduce two more classes – ExtendedPublishHelper and ExtendedPublishOptions:
public class ExtendedPublishHelper : PublishHelper
    {
        private readonly ExtendedPublishOptions _options;

        public ExtendedPublishHelper(ExtendedPublishOptions options)
            : base(options)
        {
            _options = options;
        }

        public override Item GetVersionToPublish(Item sourceItem)
        {
            Assert.ArgumentNotNull(sourceItem, "sourceItem");
            if (Options is ExtendedPublishOptions)
            {
                return sourceItem.Publishing.GetValidVersion(Options.PublishDate, _options.RequireApproval);
            }

            return sourceItem.Publishing.GetValidVersion(Options.PublishDate, true);
        }
    }

public class ExtendedPublishOptions : PublishOptions
    {
        public ExtendedPublishOptions(Database sourceDatabase, Database targetDatabase, PublishMode mode, Language language, DateTime publishDate, bool requireApproval)
            : base(sourceDatabase, targetDatabase, mode, language, publishDate)
        {
            RequireApproval = requireApproval;
        }

        public bool RequireApproval { get; set; }
    }
Now you are ready to launch publishing with workflow settings completely ignored. For example, this is how you can do it from the workflow action:
var database = Factory.GetDatabase(databaseName);

var options = new ExtendedPublishOptions(dataItem.Database, database, PublishMode.SingleItem, dataItem.Language, DateTime.Now, false)
{
         RootItem = dataItem,
         Deep = true;
};

new Publisher(options).PublishAsync();
3/11/2010: Correction » the last parameter of the ExtendedPublishOptions constructor should be “false”.
Instead of hardcoding it, I’d suggest having a parameter on the level of a workflow action which you can read and pass on.

private static bool RequireApproval(string parameters)
{
         return WebUtil.ParseUrlParameters(parameters)["RequireApproval"] == "true";
 }

public void Process(WorkflowPipelineArgs args)
{
                ...
               var requireApproval = RequireApproval(innerItem["parameters"]);
               var options = new ExtendedPublishOptions(dataItem.Database, database, PublishMode.SingleItem, dataItem.Language, DateTime.Now, requireApproval)
              
                ...
           
 }

That’s it!
Tested on Sitecore 6.1. Expected to work with 6.2.