Coffee => Coder => Code
My ramblings on code, Sitecore and stuff

Nov
17

Recently I had a situation where I needed to run a lengthy process inside my Sitecore solution which needed to run some custom code. The actual process itself was to update a separate Lucene index we were using for a user search component on an intranet. I prefer to perform user search against an index rather than the user store itself. Primarily for performance and secondly to provide more options for the search.

The normal way I’ve seen a lot of this kind of thing done is someone will create a very plain looking web form (aspx) and drop it in a folder, then have the admin user hit that page directly. The problem with this approach is that the page is normally quite ugly and doesn’t feel like part of the solution. And if your process takes a while you would have to custom code the ajax calls to update your UI and provide feedback to the user.

ugly util page

Can you tell this page was designed by a developer? :)

Realistically it’s a sin to burdon your users with this ugly kind of page. Especially when Sitecore is so extensible and already contains a location for these occasional utilities; the control panel.

It’s very easy to add your own entries into the control panel. All the categories and utilities themselves are defined under /sitecore/content/Applications/Control Panel in the core database. To add your own utility it’s as simple as adding an item under an existing category (based on the /sitecore/templates/Sitecore Client/Tasks/Task page template) based on the /sitecore/templates/Sitecore Client/Tasks/Task option template. The Header field is just the text displayed on the UI and the Click field is the Sitecore command to execute when the utility link is clicked.

The command is normally in the form of category:command_name such as myproject:updateuserindex. This command needs to be defined either in the App_Config/Commands.config file or in a configuration include file. The command definition contains both the full command name and a .net type to instantiate when the command is executed. The .net type needs to inherit from Sitecore.Shell.Framework.Commands.Command and override the Execute method.

If we have a short process to perform then we could perform that process from this method, then use something like the ClientResponse.Alert method to pop up a javascript alert dialog to let the user know the outcome of the process (success, failure, etc). If it’s a lengthy process you could also kick off a job here and then start a client pipeline which could make periodic requests to the server to get the status of the job and display. For my example here I’m going to launch a sheer wizard to kick off the process. Sheer UI also provides a facility to periodically update the user on the progress of the job. Another reason I chose a wizard here is because it’s what the user would expect. None of the other utilities in the control panel execute the desired process directly, they launch up either a dialog or a wizard. A wizard is also a great idea if you need to collect information from the user to execute the process.

To have my wizard open and display in a modal dialog, launch the dialog with the following from within the Execute method of the command.

Context.ClientPage.ClientResponse.Broadcast(
  Context.ClientPage.ClientResponse.ShowModalDialog(
  "/sitecore/shell/default.aspx?xmlcontrol=RebuildUserSearchIndex"),
  "Shell");

The xmlcontrol parameter in the url needs to refer to a sheer control. Inside our sheer layout file (XAML like file with XML extension) the control name is given as the child of the root “control” element.

<control xmlns:def="Definition"
  xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
  <RebuildUserSearchIndex>
  </RebuildUserSearchIndex>
</control>

Note that because we’re not going to use this control in a presentation definition inside Sitecore, then we don’t need to register the control using the developer centre. We can just write it and as long as the XML file is in the correct location Sitecore will pick it up.

It’s a little funny how the sheer engine works, but it’s this element name that matters. Sheer doesn’t locate controls by filename, so it doesn’t actually matter what I call the file in which this control is defined, or even where I put it. As long as the location in which it exists is included in the controlSources element in web.config. It’s pretty safe to put your file below the /sitecore/shell/controls folder as this folder is required by Sitecore itself and is already included.

Sheer UI has built in support for wizards, so immediately inside my control name tag I can define a control of type WizardForm.

<WizardForm
  CodeBeside="MyProject.RebuildUserSearchIndex,MyProject">
</WizardForm>

Note the CodeBeside attribute in which I define the type which handles the forms behavior. Now I can fill in the pages of the wizard. A wizard form consists of a number of pages including a first page and a last page. The first page normally describes to the user what the wizard does and the last page provides a summary of the process that was executed. The first and last pages are given inside the WizardForm element with WizardFormFirstPage and WizardFormLastPage elements respectively. All the other pages will be given by WizardFormPage elements.

In my simple example above I will have 3 pages. The first page describing that the index will be rebuilt, a progress page to show the user the process is indeed executing, and the final page providing a summary of the operation. I’ll define my pages (and complete the rest of my XAML file) with the following which is inserted inside the WizardForm element.

<WizardFormFirstPage ID="FirstPage" Icon="People/32x32/User1.png">
  <Border Class="scWizardWelcomeTitle">
    <Literal ID="Welcome" Text="Welcome to the User Search Index Wizard."/>
  </Border>
  <Literal Text="This wizard helps you:"/>
  <ul>
    <li class="scWizardBullet">
      <Literal Text="Rebuild the user search index."/>
    </li>
  </ul>
  <Literal Text="To begin, click the 'rebuild' button below"/>
</WizardFormFirstPage>

<WizardFormPage ID="Rebuild" Header="Rebuilding"
  Text="This may take a few minutes." Icon="People/32x32/User1.png">
  <Edit Mode="Hidden" ID="JobHandle" Width="0" Height="0"/>
  <GridPanel Width="100%">
    <Space Height="64"/>

    <ThemedImage Src="People/48x48/User1.png" Width="48" Height="48"
      GridPanel.Align="center"/>

    <Space Height="16"/>

    <Border Align="center">
      <ThemedImage Src="Images/progress.gif" Width="94" Height="17"/>
    </Border>

    <Space Height="16"/>

    <Border Align="center" GridPanel.Align="center" GridPanel.Style="font-weight:bold">
      <Literal Text="Rebuilding..."/>
    </Border>

    <Space Height="16"/>

    <Border Align="center">
      <Literal ID="StatusMessage"/>
    </Border>

  </GridPanel>
</WizardFormPage>

<WizardFormLastPage ID="LastPage" Icon="People/32x32/User1.png">
  <Border ID="SuccessMessage">
    <Literal Text="The index has been rebuilt successfully."/>
  </Border>
  <Border ID="ErrorMessage">
    <Literal Text="The index rebuild failed." />
  </Border>

  <Border>
    <Literal ID="Status"/>
  </Border>
</WizardFormLastPage>

The WizardForm control provides the buttons and handling of switching pages of the wizard. You can also override the ActivePageChanged and ActivePageChanging methods to customise this behavior. By default the pages of the wizard are presented in the order in which they are defined in the XAML file. But using these method overrides you can conditionally skip certain pages. You can also cancel the transition in the ActivePageChanging method so you have opportunity to perform validation on user input.

If you’ve read the XAML above you’ll see that instead of having a “next” button on the welcome screen, I want to have it labelled “rebuild”. I also want to disable the navigate buttons when the index is being rebuilt. The following ActivePageChanged method will achieve this.

protected override void ActivePageChanged(string page, string oldPage)
{
  base.ActivePageChanged(page, oldPage);

  NextButton.Header = "Next >";

  if (page == "FirstPage")
    NextButton.Header = "Rebuild";

  if (page == "Rebuild")
  {
    BackButton.Disabled = true;
    NextButton.Disabled = true;
    StartRebuild();
  }
}

Note I also kick off the rebuild process when we change to the “Rebuild” page. I’ll run the rebuild in a job which is a background thread. This way the job can run asynchronously and my request can return.

private void StartRebuild()
{
  // Start job for index rebuild
  var options = new JobOptions("Rebuild User Search Index",
    "IndexRebuild", Context.Site.Name, this, "RebuildIndex");
  var job = JobManager.Start(options);
  JobHandle.Value = job.Handle.ToString();

  // Start client pipeline to check progress
  Context.ClientPage.ClientResponse.Timer("CheckStatus", 500);
}

When creating a JobOptions object you can specify an object to run your process as well as the method to invoke on that object (last 2 parameters in the above call). So the rebuild will be run using this form and calling the “IndexRebuild” method on it. The Timer method call down the bottom causes the client to wait the specified time (second parameter, milliseconds) then call the method given in the first parameter. So I’ll need to define the IndexRebuild and CheckStatus methods.

One of the nice things about Sitecore jobs is that they provide a way to update status across threads through the job’s Status.Processed property. There are a few ways to find an existing job when the call for CheckStatus comes in but I like to use the job handle of the job returned by the JobManager. I’ll need to store the handle so I also have access to it on the call to CheckStatus. For this I’ll use a hidden field and store the handle value in that.

To get access to the controls of the form we have to get a little “2003 old school” and define the variables in our code file. The controls come out of the Sitecore.Web.UI.HtmlControls namespace, so include a reference to that namespace at the top of your file, then inside your class define the controls as protected so they can be populated on the postback.

protected Edit JobHandle;
protected Literal StatusMessage;
protected Border SuccessMessage;
protected Border ErrorMessage;
protected Literal Status;

I won’t actually build the index in this example, as I’m only concerned with describing how to create such an application. Instead I’ll simulate the lengthy operation so we can witness the UI updating while the background thread runs.

private void RebuildIndex()
{
  // Simulate a lengthy operation so the client pipeline can execute
  Context.Job.Status.Processed = 0;
  for (int i = 0; i < 20; i++)
  {
    Thread.Sleep(500);
    Context.Job.Status.Processed++;
  }
}

So I update the processed count in the method above and I’ll read that status from the client pipeline server method.

protected void CheckStatus()
{
  var handle = Handle.Parse(JobHandle.Value);
  if (handle != null)
  {
    var job = JobManager.GetJob(handle);
    if (job != null)
    {
      StatusMessage.Text = "Processed: " + job.Status.Processed.ToString();
    }

    if (job.IsDone)
    {
      Active = "LastPage";
      ErrorMessage.Visible = false;
      Status.Text = StatusMessage.Text;
    }
    else
      Context.ClientPage.ClientResponse.Timer("CheckStatus", 500);
  }
}

This callback is also where we check on the status of the job and when complete move to the next page of the wizard by setting the “Active” property to the ID of the page you want to swap to. If the job isn’t finished we’ll tell the client to check the status in another 500 milliseconds.

And after you fill in the proper index creation method, you’re done! I know that may have been a little more work than the ugly util page that was whacked together above, but this wizard shows a lot of polish and enhances the user’s experience when using your solution. Not to mention the built in mechanisms for starting a background thread and having the client periodically check on the status of the running job. Once you get the hang of sheer you’ll be knocking out these wizards for those utils quicker than you can make that UX expert shake his head in dismay.

If you need some help with sheer, keep in mind that the XAML files are not compiled; meaning you can open up the XAML files for any of the Sitecore UI components. Especially when working with dialogs and wizards, the existing Sitecore controls are invaluable as a reference.

And here’s the final result in all it’s sheer glory!

control panel

Control panel showing my user search index rebuild option.

wizard first

Welcome page of the wizard

wizard second

Processing page of the wizard

wizard last

Summary page of the wizard

Oct
22

Sitecore 6.2 was released last night in probably the quietest release Sitecore has done in a long time. Why am I saying it was a quiet release? Well I had no idea it was coming and only found out after reading John West’s blog and finding a reference to the new version hidden in one of his posts. Normally I should get an email from Sitecore about new releases, but nothing.

So I’m doing my part for the publicising of Sitecore 6.2. I don’t normally post to tell you all about a release you probably already know about, but seeing as though I had no idea about 6.2 being released I’m assuming a lot of you haven’t heard yet either.

Anyway reading through the release notes for 6.2 I am filled with excitement. Some of the features I got a sneak peek at during an MVP only preview of Everest earlier this year. Let me point out a few here.

Word field type: Sitecore 6.2 includes a brand new field type; the word field. This field uses an Active X control to allow the user to use Word 2007 to edit text fields. This means users who are familiar with word can continue to use it. One caveat of this is of course that you’ll be lumped with Word’s awful HTML generation. So if you really don’t care about compliancy, then go for it :) .

WebDAV for drag and drop support of uploading media files: Sitecore 6.2 adds support for webDAV which allows users to drag media files straight from their desktop into the IE window and have those files uploaded into the media library. No more clicking around dialogs, just drag your files onto the Sitecore window.

Zipped icon files: When copying or zipping a Sitecore install folder, what takes the most time? The icon files of course. Sitecore has included since version 5 a very comprehensive library of icon files, all in different resolutions to really provide a good looking UI. Each icon file is only small, but there are so many of them. And the number of files is what slows down those zip and copy operations. From memory a standard install of Sitecore 6.1 contained about 24,000 files. The standard install of Sitecore 6.2 contains only 7,500 files. This should really help when moving Sitecore installs around.

Index folder default location: This is a little trick I leant from Mark Cassidy. Previously in a default Sitecore install the index folder would exist below the web root. Problem was that at times when packages were being installed or publishes being run with the index updates running crazy IIS would restart the application due to too much file activity (or something like that). So to solve this issue you can add a setting named “indexFolder” which provides the location of the index folder. With the indexes outside the webroot the index updates no longer affect the stability of the IIS application. Well, Sitecore 6.2 by default has added this setting into web.config and moved the index folder below the data folder.

And of course there are a heap of other updates, enhancements and bug fixes. Just be careful about the breaking chances in the API (mostly to do with the analytics API, check the release notes).

Oct
16

There’s a few things going on at the moment in the world of Sitecore.

Firstly the Sitecore Australian and New Zealand user group is having it’s second user group meeting, this time in Sydney Australia on 28th October 2009. Tim Ward, Solution Architect for Sitecore Australia will be taking attendees through some of the new features of Sitecore 6.1 and some of the up coming features of Sitecore 6.2. Mark Stanton from Gruden will also be speaking to the group. Everyone is welcome to attend this event, you just need to RSVP to Tim at tiw@sitecore.net so he can make sure he has enough beer. :) You can view the invitation online at http://img36.imageshack.us/img36/3907/usergroupinvite2.jpg .

Kind of in conjunction with the user group meeting, we’re also holding a Sitecore module competition. So if you’re in Australia or New Zealand and you have an awesome module you’d like to share with the world (and score a cool prize for it too) then make sure you get your entries in before 28th October 2009 to either Tim or myself.

And something that will interest more than just Sitecore devs in Australia and New Zealand, do you remember the Html Agility Pack? Sitecore bundles this library and I’ve spoken previously about how to use this library to aid in testing static behavior controls in Sitecore. Some of you may have looked for info on this module or found it’s CodePlex page and been turned off due to it’s lack of recent activity. Well, those days are now over.

Jeff Klawiter has picked up Html Agility Pack and started working on it again to fix bugs and add features which have been requested. So it is no longer an abandoned project. You can help Jeff out by telling him about how you use this library and if you’ve found any bugs, nuances or have feature requests. Kudos to Jeff for picking this project up. I look forward to seeing how it evolves.

So, just a little recap of why you should use Html Agility Pack for your testing. It allows you to easily load in HTML and treat it as XML, no matter what HTML standard it was written to. The earlier standards such as HTML 4.01 allowed you to leave off closing tags in some circumstances like on a paragraph. You can’t load HTML 4.01 into an XML document for testing as it’s not valid XML. But Html Agility Pack hides all that from you and just allows you to treat it as if it were XML.

But wouldn’t you want to know if your HTML was badly formed? Shouldn’t you be testing every single character of your control’s output? Well, that depends on how pedantic you want to get, and can even come down to which .net controls you may have used for your build. I would recommend against testing every single character as it doesn’t matter for (X)HTML. (X)HTML doesn’t care if you have a newline between a closing and separate opening tag, or you indented that tag too much. And how much time do you really have to spend on getting the output “character perfect”?

Instead I would suggest you treat any validation of control output as a separate activity in your test. You can use Html Agility Pack to test the markup as the browser would treat it and then you can test the conformity of the output to standards using something like HtmlTidy or the W3C validator .

And before you go jumping into vast string manipulation to verify your output, remember that it is much easier to navigate the structure of the document as XML. Especially when it comes to checking the value of an attribute on a tag. Rather than Substring and IndexOf I can just use XPath to find the attribute value.

Sep
22

Sitecore has done a very good job of making item management in Sitecore very much like file management in your OS. Operations such as moving items, ordering items, copying and pasting items is as simple as a drag or a click. All this works quite well, but only for the current server you’re on.

If you have a look at the data on the clipboard after a copy command has run you’ll see it contains the ID of the item you wish to copy along with some meta data about the action to perform.

sitecore:copy:{39F265B4-1FF0-4DA2-BCA6-A3056D5BCCFE}

But often I find myself wanting to be able to copy an item from one server and paste it onto another. This normally happens when I want to pull specific content back from a QA or production server and paste it onto dev. Sitecore already provides a mechanism to do this; packages. But a package feels like a lot of effort when all I want is a single item (or tree).

So I got to working on a way in which to do this. My first thought was to alter the copy command to also place the URL of the server into the clipboard. Then provide a WCF service on the source server that the paste command could call to pull the data back. But after a little more consideration I worked out how to avoid the WCF service as well.

The Item class in Sitecore provides the ability to copy an item’s XML data, and create new items based on this XML through the GetOuterXml(bool) and Paste(string, bool, PasteMode) methods respectively. So if I could get the item’s entire data onto the clipboard, there would be no need for a WCF service.

First things first. Let’s create our custom copy command which will copy the item’s XML onto the clipboard. Our CopyItemXml command will extend Sitecore.Shell.Framework.Commands.ClipboardComman as we’ll be using the clipboard. We’ll also override the Execute and QueryState methods. The heavy lifting of the Execute method will be done in another method to allow us to run client pipelines to prompt and receive input from the user.

public override CommandState QueryState(CommandContext context)
{
  if (!ClipboardCommand.IsSupported(false))
    return CommandState.Hidden;

  if (context.Items == null || context.Items.Length == 0)
    return CommandState.Disabled;

  if (!context.Items[0].Access.CanRead())
    return CommandState.Disabled;

  return base.QueryState(context);
}

The QueryState method is called by Sitecore to determine what state to give the command in the UI such as hidden, disable or enabled.

public override void Execute(CommandContext context)
{
  if (ClipboardCommand.IsSupported(true) &&
    context.Items != null && context.Items.Length >= 1)
  {
    var parameters = new NameValueCollection();
    parameters.Add("id", context.Items[0].ID.ToString());
    parameters.Add("dbname", context.Items[0].Database.Name);
    parameters.Add("initialCall", "1");
    Context.ClientPage.Start(this, "Run", parameters);
  }
}

The Execute method does a few checks to ensure this command is supported by the browser (clipboard access) and then starts the client pipeline with the appropriate parameters.

protected void Run(ClientPipelineArgs args)
{
  if (args.Parameters["initialCall"] == "1")
  {
    args.Parameters["initialCall"] = "0";
    Context.ClientPage.ClientResponse.YesNoCancel(
      "Do you want to copy the children as well?", "200", "100");
    args.WaitForPostBack();
  }
  else
  {
    if (args.Result == "yes" || args.Result == "no")
    {
      var includeChildren = args.Result == "yes";
      var db = Sitecore.Configuration.Factory.GetDatabase(
        args.Parameters["dbname"]);

      if (db == null)
      {
        SheerResponse.Alert("Failed to locate target database");
        return;
      }

      var item = db.GetItem(args.Parameters["id"]);
      if (item == null)
      {
        SheerResponse.Alert("Failed to locate source item");
        return;
      }

      var data = item.GetOuterXml(includeChildren);
      data = data.Replace("\\", "\\\\");
      data = data.Replace("\"", "\\\"");
      data = EscapeEscapables(data);

      SheerResponse.Eval(
        "window.clipboardData.setData(\"Text\", \"" + data + "\")");
    }
  }
}

The Run method first checks to see if this is the first call to the method as a result of starting the client pipeline. If it is it sets into the response to prompt the user if they wish to also copy the children of the current item. If this is not the initial call then we grab the parameters, perform some error checking then find the source item and copy it’s XML data onto the clipboard. Note the formatting calls on the data variable towards the end of that method which will help escape the XML data to be shot back down to the browser in the response and executed in javascript. And here’s the EscapeEscapables util method which is also used.

private string EscapeEscapables(string input)
{
  var builder = new StringBuilder();
  builder.Append(input);
  builder.Replace("\r", "\\r");
  builder.Replace("\t", "\\t");
  builder.Replace("\n", "\\n");
  return builder.ToString();
}

Now we need to bind this command into the UI so we can execute it from the content editor. I’ll add this new command to the context menu. You could also add it to a ribbon if you liked.

Log into the Sitecore desktop and swap over to the core database. Next, open a content editor and navigate down to /sitecore/content/Applications/Content Editor/Context Menus/Default item. Here you can either create a new menu item definition, or copy the existing copy menu item definition. Either way the most important fields to fill in of the menu item are as follows:

Field Value
Display Name Copy XML
Icon Applications/16×16/copy.png
Message item:copyXMLtoclipboard(id=$Target)

The Message field specifies a command to execute when this menu item is clicked. This command name needs to map to our custom copy class above in the App_Config/commands.config file. Open that file and scroll down until you find the existing item:copy command, then copy that line and adjust it as follows:

<command name="item:copyXMLtoclipboard"
  type="CustomCommands.CopyItemXML,CustomCommands" />

That’s the first half done. We now have a custom command on the content editor context menu which can be used to copy an item’s XML data onto the clipboard.

copyitemxml

Now we have the data on the clipboard we need to create the paste menu item to handle pasting the item data onto a different server. But rather than creating a brand new menu item, command and command class, why not just extend the existing paste command to be able to deal with item XML data as well?

public class ExtendedItemPaste :
  Sitecore.Shell.Framework.Commands.PasteFromClipboard

I know the normal paste command above is structured very similarly to the copy command we created. So I can leave the QueryState method and Execute method alone and just override the Run method which is invoked as a result of the Execute method running a client pipeline.

protected new void Run(ClientPipelineArgs args)
{
  if (args.Parameters["fetched"] == "0")
  {
    args.Parameters["fetched"] = "1";
    Context.ClientPage.ClientResponse.Eval(
      "window.clipboardData.getData(\"Text\")").Attributes["response"] = "1";
    args.WaitForPostBack();
  }
  else
  {
    if (args.Result.StartsWith("<item "))
    {
      var db = Sitecore.Configuration.Factory.GetDatabase(
        args.Parameters["database"]);

      if (db == null)
      {
        SheerResponse.Alert("Failed to locate target database");
        return;
      }

      var parent = db.GetItem(args.Parameters["id"]);
      if (parent == null)
      {
        SheerResponse.Alert("Failed to locate parent item");
        return;
      }

      parent.Paste(args.Result, false, PasteMode.Merge);
    }
    else
      base.Run(args);
  }
}

In the paste method we need to use the ClientResponse.Eval method in conjunction with the ClientPipelineArgs.WaitForPostBack method to pull the data off the clipboard in javascript and return the result to our method. Next we check to see if the data from the clipboard is item XML or not. If it is we will handle it, otherwise let the default paste class handle it.

Now I have to override the existing command to use my new command class. Open the App_Config/commands.config file and find the command definition with name item:pastefromclipboard. Replace the type attribute with our ExtendedItemPaste command from above.

<command name="item:pastefromclipboard"
  type="CustomCommands.ExtendedItemPaste,CustomCommands" />

Now when we paste, the paste command will recognise the item XML and create new items based on that. To get the most out of this you’ll want to set up another server with these custom commands so you can copy from one server and paste to the other. If you paste onto the same server you’ll notice that no new item is created. This is because of the second parameter we’re passing to the Item.Paste method, changeIDs. If this parameter were true then the IDs in the XML would be replaced with new IDs and new items would be created. By not changing the IDs the existing item will have it’s data and fields updated from the XML. I left this parameter as false as the issue I am trying to solve with this solution is to be able to copy items from one server to another. I retain the original ID incase something else references this item by ID rather than name or path.

But if you really wanted new IDs you could change the paste code to set changeIDs to true, or even better, prompt the user using the same technique as used in the copy command to ask if they want new items or update existing (if found).

So my original issue is solved right? Well…if you’ve tried this technique using 2 separate and different servers you may have noticed 1 significant issue. If the template which the item is based on does not exist on the target server then my new item will not contain any fields.

missing template

So to have a full solution I should really grab the item’s template too. What about the template’s base template (and there could be multiple of those)? What about any references the item might use such as droplists or link fields to internal items? What about the presentation components used by the item (renderings, layouts, sublayouts, etc)? What about media? …

As you can see, once you start down the dependency paths you realise how big the graph can be. The easiest thing is to draw some definite boundries around what you will include in a copy and what you won’t. Below is a quick list of things that you might want to include and whether you easily can include them onto the clipboard as text.

Dependency Easily include as text?
Item’s template Yes
Item’s template’s base templates Yes
Linked items (references) Yes
Linked item’s templates Yes
Linked media library items No
Presentation definition items Yes
XSLT code Yes
Markup files (aspx, ascx) Yes
Code behind No

For my solution I’ll draw the following boundaries. I’ll copy the item (and children if required), the template hierarchies of the items and any item references. Basically just the data required to support my copied item.

The following shows my completed CopyItemXML command class.

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using Sitecore;
using Sitecore.Data.Items;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Web.UI.Sheer;

namespace CustomCommands
{
  public class CopyItemXML : ClipboardCommand
  {
    public override void Execute(CommandContext context)
    {
      if (ClipboardCommand.IsSupported(true) &&
        context.Items != null && context.Items.Length >= 1)
      {

        var parameters = new NameValueCollection();
        parameters.Add("id", context.Items[0].ID.ToString());
        parameters.Add("dbname", context.Items[0].Database.Name);
        parameters.Add("initialCall", "1");
        Context.ClientPage.Start(this, "Run", parameters);
      }
    }

    public override CommandState QueryState(CommandContext context)
    {
      if (!ClipboardCommand.IsSupported(false))
        return CommandState.Hidden;

      if (context.Items == null || context.Items.Length == 0)
        return CommandState.Disabled;

      if (!context.Items[0].Access.CanRead())
        return CommandState.Disabled;

      return base.QueryState(context);
    }

    protected void Run(ClientPipelineArgs args)
    {
      if (args.Parameters["initialCall"] == "1")
      {
        args.Parameters["initialCall"] = "0";
        Context.ClientPage.ClientResponse.YesNoCancel(
          "Do you want to copy the children as well?", "200", "100");
        args.WaitForPostBack();
      }
      else
      {
        if (args.Result == "yes" || args.Result == "no")
        {
          var includeChildren = args.Result == "yes";
          var db = Sitecore.Configuration.Factory.GetDatabase(
            args.Parameters["dbname"]);
          if (db == null)
          {
            SheerResponse.Alert("Failed to locate target database");
            return;
          }

          var item = db.GetItem(args.Parameters["id"]);
          if (item == null)
          {
            SheerResponse.Alert("Failed to locate source item");
            return;
          }

          var items = new List<Item>();
          var templates = new List<Item>();
          AddItems(item, includeChildren, true, items, templates);

          var data = GetXMLData(items, templates, item);
          data = data.Replace("\\", "\\\\");
          data = data.Replace("\"", "\\\"");
          data = EscapeEscapables(data);

          SheerResponse.Eval(
            "window.clipboardData.setData(\"Text\", \"" + data + "\")");
        }
      }
    }

    private void AddItems(Item item, bool includeChildren,
      bool includeReferences, List<Item> items, List<Item> templates)
    {
      // Add the item itself
      if (!ListContainsItem(items, item))
        items.Add(item);

      // Add any children (if required)
      if (includeChildren)
      {
        var children = item.GetChildren();
        for (int i = 0; i < children.Count; i++)
        {
          AddItems(children[i], includeChildren, false,
            items, templates);
        }
      }

      // Add references
      var links = item.Links.GetValidLinks();
      for (int i = 0; i < links.Length; i++)
      {
        var linkItem = links[i].SourceItemID == item.ID ?
          links[i].GetTargetItem() : links[i].GetSourceItem();

        var path = linkItem.Paths.FullPath.ToLower();
        if (!ListContainsItem(items, linkItem) && (
          path.StartsWith("/sitecore/content") ||
          path.StartsWith("/sitecore/templates")))
        {
          if (path.StartsWith("/sitecore/templates"))
            AddTemplateHeirarchy(linkItem, templates);
          else
            items.Add(linkItem);
        }
      }
    }

    private void AddTemplateHeirarchy(Item template, List<Item> items)
    {
      items.Add(template);
      items.AddRange(template.Axes.GetDescendants());
      var templateItem = (TemplateItem)template;
      for (int i = 0; i < templateItem.BaseTemplates.Length; i++)
        {
          if (templateItem.BaseTemplates[i].ID !=
            Sitecore.TemplateIDs.StandardTemplate &&
            !ListContainsItem(items, templateItem.BaseTemplates[i]))
            AddTemplateHeirarchy(templateItem.BaseTemplates[i], items);
        }
      }

    private bool ListContainsItem(List<Item> items,
      Item itemToFind)
    {
      return items.Exists(item => item.ID == itemToFind.ID);
    }

    private string GetXMLData(List<Item> items,
      List<Item> templates, Item rootItem)
    {
      var builder = new StringBuilder();
      builder.Append("<itemData>");
      builder.Append("<items>");

      for (int i = 0; i < items.Count; i++)
      {
        builder.Append("<itemData>");
        builder.Append("<path>");
        if (items[i].ID != rootItem.ID &&
          !items[i].Axes.IsDescendantOf(rootItem))
          builder.Append(items[i].Paths.FullPath);

        builder.Append("</path>");
        builder.Append(items[i].GetOuterXml(false));
        builder.Append("</itemData>");
      }

      builder.Append("</items>");
      builder.Append("<templates>");

      for (int i = 0; i < templates.Count; i++)
      {
        builder.Append("<itemData>");
        builder.Append("<path>");
        builder.Append(templates[i].Paths.FullPath);
        builder.Append("</path>");
        builder.Append(templates[i].GetOuterXml(false));
        builder.Append("</itemData>");
      }

      builder.Append("</templates>");
      builder.Append("</itemData>");
      return builder.ToString();
    }

    private string EscapeEscapables(string input)
    {
      var builder = new StringBuilder();
      builder.Append(input);

      builder.Replace("\r", "\\r");
      builder.Replace("\t", "\\t");
      builder.Replace("\n", "\\n");
      return builder.ToString();
    }
  }
}

You’ll see from above that I recursively (if required) collect the selected item and it’s descendants then on each item in the AddItems method I add any references as found in the links property of the item. For a good introduction to using the links database have a look at Mark Cassidy’s post Listing Related Articles with Sitecore using the LinkDatabase. Note how I’m only adding links under the content and templates sections of the content tree. The AddTemplateHeirarchy method adds the template hierarchy of the given item. When I construct the XML data I need to add the original path of any items not under the direct item being copied. Things like the templates and the references items need to be place in an appropriate location. I’ll use the path element in the paste command.

The following paste command is used to create items from this extended XML data block.

using System;
using System.Xml;
using Sitecore;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Web.UI.Sheer;

namespace CustomCommands
{
  public class ExtendedItemPaste :
    Sitecore.Shell.Framework.Commands.PasteFromClipboard
  {
    protected new void Run(ClientPipelineArgs args)
    {
      if (args.Parameters["fetched"] == "0")
      {
        args.Parameters["fetched"] = "1";
        Context.ClientPage.ClientResponse.Eval(
"window.clipboardData.getData(\"Text\")").Attributes["response"] = "1";
        args.WaitForPostBack();
      }
      else
      {
        if (args.Result.StartsWith("<itemData>"))
        {
          var db = Sitecore.Configuration.Factory.GetDatabase(
            args.Parameters["database"]);
          if (db == null)
          {
            SheerResponse.Alert("Failed to locate target database");
            return;
          }

          var parent = db.GetItem(args.Parameters["id"]);
          if (parent == null)
          {
            SheerResponse.Alert("Failed to locate parent item");
            return;
          }

          var doc = new XmlDocument();
          doc.LoadXml(args.Result);
          CreateTemplates(doc, db);
          CreateItems(doc, parent);
        }
        else
          base.Run(args);
      }
    }

    private void CreateTemplates(XmlDocument doc, Database database)
    {
      var templateNodes = doc.SelectNodes(
        "/itemData/templates/itemData");
      for (int i = 0; i < templateNodes.Count; i++)
      {
        var pathNode = templateNodes[i].SelectSingleNode("path");
        if (pathNode != null)
        {
          var path = pathNode.InnerText;
          var parent = GetParent(path, database);
          var itemNode = templateNodes[i].SelectSingleNode("item");

          if (itemNode != null)
            parent.Paste(itemNode.OuterXml, false, PasteMode.Merge);
        }
      }
    }

    private void CreateItems(XmlDocument doc, Item parent)
    {
      var itemNodes = doc.SelectNodes("/itemData/items/itemData");
      for (int i = 0; i < itemNodes.Count; i++)
      {
        var pathNode = itemNodes[i].SelectSingleNode("path");
        if (pathNode != null)
        {
          var path = pathNode.InnerText;
          var target = parent;
          var itemNode = itemNodes[i].SelectSingleNode("item");

          if (path != string.Empty)
            target = GetParent(path, parent.Database);
          else
          {
            if (itemNode != null)
            {
              var itemParent = parent.Database.GetItem(
                itemNode.Attributes["parentid"].Value);
              if (itemParent != null)
                target = itemParent;
            }
          }

          if (itemNode != null)
            target.Paste(itemNode.OuterXml, false, PasteMode.Merge);
        }
      }
    }

    private Item GetParent(string itemPath, Database database)
    {
      var parts = itemPath.Split(new string[]{"/"},
        StringSplitOptions.RemoveEmptyEntries);
      Item item = null;
      for (int i = 0; i < parts.Length - 1; i++) // Don't include last item
      {
        if (item == null)
          item = database.GetRootItem();
        else
        {
          var next = item.Axes.GetChild(parts[i]);
          if (next != null)
            item = next;
          else
            item = item.Add(parts[i],
              new TemplateID(Sitecore.TemplateIDs.Folder));
        }
      }

      return item;
    }
  }
}

In the Run method I load up the XML as an XML document which allows me to select the appropriate items out of the document for each operation. I also create the templates through the CreateTemplates method before I create the items which use the templates in the CreateItems method.

And there we have a slightly more robust solution.

Sep
04

There are a lot of qualities that constitute a professional. Take this take-away coffee that I just ordered.

Take away coffee with latte art

Take away coffee with latte art


This is a take away coffee, served in a paper cup with one of those plastic lids. Even though the consumer can’t see the top of the coffee due to the lid, the barista still went to the effort of pouring some latte art on it. This is one of those qualities that constitute a professional. Even when your hard efforts may go unnoticed and unseen, you still perform your work at the highest level.

Aug
28

Most of the Sitecore implementations I’m involved in have me working in a team environment. In fact, this is pretty normal for any IT project. Although a team environment has many benefits, there are also some complexities that need to be addressed in terms of keeping the development environment in sync.

There are two things that need to be synchronised across all the development environments; code and data. The code synchronisation issue has been dealt with long ago by using source control. I wish I could say at this point that I haven’t encountered a team not using source control for a long time, but alas I cannot. Realistically I cannot believe that any professional developer would not use source control these days. This is the number one tool required for easier team development for any project.

I also use source control for 1 person projects. Along with synchronisation, source control provides other benefits such as versioning of the code so you can roll back changes, and providing a central location for all code. New dev comes onto the project? Just get the source from here. Old project needs maintenance? Just get the source from here. Original developer left the company? Just get the source from here.

The other part to the synchronisation issue is the data. I’m a strong believer of putting all database related things in source control. Your development database on a SQL server is just an instance of the structure and data you define in script files. Do not treat the database server as the place where your database lives. It should live as scripts in source control. The database on the database server is just a single instance of that database.

Another benefit of storing your schema and data in source control is the ability for accessing the data without a direct connection to the database server. For example, when working from home or a remote location away from the office. If the database scripts are kept in source control it’s straight forward to get the latest versions of the files and create a local copy to work against.

Although I want my data to live in source control as scripts I also am a strong believer of not accessing proprietry databases directly if an API exists. This is the case for Sitecore. Not to mention the complexity of the data stored in Sitecore.

So when working with Sitecore, how do you get your data to live in source control rather than the database? A common practice for Sitecore projects is to create Sitecore packages of the project specific data. This package can then be put in source control. This can also be seen as common practice through many of the shared source projects for Sitecore. Problem here is the package is a zip file, not text so your source control will not try and merge the package if multiple developers have updated multiple items and updated the package independent of each other.

Sitecore 6 introduced item serialization. This allows you to export item data in a text format at the click of a button to disk. And also to import it at the click of a button. This is a much better approach than the package approach. The only drawback to Sitecore 6 item serialization is that media library items cannot be exported in this way, so you’ll still have to drop back to a package for these.

So now we have the data on disk, we can include it in source control. Well… it depends on your project structure how easy this will be. The item data is placed on disk under the data/serialization folder. I don’t like to write my Sitecore code in the same folder that Sitecore runs. I like to separate my custom code from the system I’m building on top of, so in source control I don’t have the Sitecore root at the same location as my dev root. This makes including the serialized items more difficult.

Now let me step down off my soapbox and confess that although I want my Sitecore items in source control, generally I don’t do this due to the reasons listed above. At Next Digital we normally have a shared development database with each developer running their own instance of Sitecore. This normally works quite well due to the fact that this environment is development and each instance of Sitecore (on the dev’s machine) is restarted due to code changes quite often. This restarting means there are minimal issues with caching of the data.

OK, so now for the easy way. I was privileged enough to be included in the beta program for a new team development tool for Sitecore. I would like to introduce you to “Team Development for Sitecore” (or TDS for short). TDS is developed by Sitecore partner Hedgehog Development in North America. It is a plugin for Visual Studio which allows you to serialize and deserialize Sitecore items for your project directly from within Visual Studio. The items can also be added to source control as the items exist in a custom TDS project as text files. Being text files most source control system should have no problem merging changes.

tds item

You’ll notice from the above screenshot that the text format of an item in your TDS project is the Sitecore 6 item serialization format.

TDS also allows synchronising the items in your project with your Sitecore server. You can also select which items to sync and which ones to leave alone.

tds select items

In general you’ll only want to sync project specific items and leave the Sitecore standard items alone.

tds items in project

There are multiple synchronisation options to determine how TDS should handle such cases as when an item exists in the project but not in Sitecore and when an item exists in Sitecore but not in the project.

Another nice feature of TDS is the item designed. Hedgehog built on the concept of the Visual Studio Class designer to provide a similar tool for viewing the relationship between your templates. This is a huge help, especially as Sitecore supports multiple inheritance through templates which can lead to lengthy investigations when trying to work out which template defines which field on an item.

tds item designer

I find it very useful when designing a solution to design and rework the templates in a UML diagram. The item designer at least gives you the view part of this.

Overall I think TDS is a very good start. There’s heaps of potential there, like making the item designer allow updating of templates and relationships. I’m quite eager to see what Hedgehog do next with TDS. Even so, just having the ability to synchronise Sitecore items across multiple locations is worth the price tag.

Aug
11

I have often wanted a way in Sitecore to be able to define most of my pages presentation in a base template but customise that presentation on more specific items.

Sitecore has a facility to inherit default values through the use of data template standard values. This mechanism works for all fields including presentation (__renderings field). Sitecore 6 introduced inheritance through standard values meaning that if a base template defines a particular standard value and the standard value in a derived template is null, the derived template will inherit the standard value for that field from it’s base template.

This is really cool and makes maintenance a lot easier. Rather than having to set the default value per template, I can set the value on a base template and let inheritance through the standard values take care of the rest. For example, let’s say I have a “show on sitemap” checkbox for all page type items. I have a base template called page base and derived templates called article and news. I’ll set the default value for “show on sitemap” to true on the page base template’s standard values. Because I don’t set this on the article or news templates they inherit through their standard values the standard value of the parent template’s field.

This mechanism is also very handy when it comes to defining presentation for your templates. You can define the basic presentation on a base template and then when you come to define the presentation on derived templates you already have a starting point as this initial presentation is inherited from the base template. The problem is, as soon as you adjust the presentation on a derived template you are then overridding the standard values of the base template so changes to this base template will not flow through to the derived templates.

Towards the end of last year I proposed a solution to this problem. But a recent post by Thomas Eldblom gave me an idea of how I might be able to solve this problem. Thomas described to us his “composite renderings” in which he defines a new presentation type which allows him to place a group of controls in presentation as a set. He then updated the renderLayout pipeline to expand the composite renderings when a request was made and the presentation for an item was being built. I could use a similar approach to realise my “Composite Presentation Inheritance”.

Instead of expanding out groups of controls I’ll instead need to pull in presentation from a parent template’s standard values and aggregate them with that of the current item. To specify that composite presentation inheritance is being used, similar to Thomas’ approach I’ll define a special layout to be used on the item.

Let’s start by defining this special layout called “Inherit”.

inherit-layout

It doesn’t matter what is in the ASPX file this layout refers to as it will never be used.

Next we need to ensure the correct layout is used when an item which uses the inherit layout is requested. The best place to do this would be immediately after Sitecore has resolved which layout is being used, and swap out inherit for the layout we need.

The layout is resolved during the httpRequestBegin pipeline by the LayoutResolver processor. So we’ll need to create an HttpRequest processor which we can insert into the pipeline immediately after the layout resolver has run.

The following class shows an implementation for our inheritance layout resolver.

using System;
using System;
using Sitecore;
using Sitecore.Diagnostics;
using Sitecore.Data.Items;
using Sitecore.Pipelines.HttpRequest;

namespace Composite_Presentation_Inheritance
{
  public class InheritLayoutResolver : LayoutResolver
  {
    public override void Process(HttpRequestArgs args)
    {
      if (Context.Item != null)
        ProcessItem(Context.Item, true);
    }

    protected void ProcessItem(Item item, bool contentItem)
    {
      if (item.Visualization.Layout.Name.ToLower() == "inherit")
      {
        var baseTemplates = item.Template.BaseTemplates;
        for (int i = 0; i < baseTemplates.Length; i++)
        {
          if (baseTemplates[i].StandardValues != null)
            ProcessItem(baseTemplates[i].StandardValues, false);
        }
      }
      else if(!contentItem)
      {
        Context.Page.FilePath = item.Visualization.Layout.FilePath;
        Tracer.Info("Swapped inherit layout to " + Context.Page.FilePath);
      }
    }
  }
}

Note above how I recursively call the process item method with the item’s base templates standard values. I use a recursive method to make sure we could support any number of inheritance levels through layout. If the layout of the current item (whether that be the item itself or a set of standard values) uses the inherit layout, continue the recursion path, otherwise bail out. We also need to do checks to ensure we only process if we’re requesting content. This same resolver will run for any requests made for the Sitecore interfaces themselves, so this check makes sure we don’t upset normal Sitecore behavior.

That’s the layout done, now we need to aggregate the presentation controls. The presentation for the page being requested in build in the renderLayout pipeline by the InsertRenderings processor. We need to create a renderLayout pipeline processor which will recursively aggregate our presentation through each set of standard values in the template hierarchy of the current item, but only while the layout of the standard values is our special inherit layout. The following class shows an implementation for this.

using System;
using Sitecore.Pipelines.RenderLayout;
using Sitecore;
using Sitecore.Data.Items;

namespace Composite_Presentation_Inheritance
{
  public class PresentationAggregator : RenderLayoutProcessor
  {
    public override void Process(RenderLayoutArgs args)
    {
      if(Context.Item != null)
        process item(Context.Item, false);
    }

    protected void process item(Item item, bool addRenderings)
    {
      if (item.Visualization.Layout.Name.ToLower() == "inherit")
      {
        var baseTemplates = item.Template.BaseTemplates;
        for (int i = 0; i < baseTemplates.Length; i++)
        {
          if(baseTemplates[i].StandardValues != null)
            process item(baseTemplates[i].StandardValues, true);
        }
      }

      if (addRenderings)
      {
        var renderings =
          item.Visualization.GetRenderings(Context.Device, true);
        for (int i = 0; i < renderings.Length; i++)
        {
          Context.Page.AddRendering(renderings[i]);        }
      }
    }
  }
}

Note how we do the recursive call before we add the renderings. This will have the effect of adding the standard values renderings from the very base template down through the hierarchy to our more specific templates. I also have a condition to check if the renderings should be added as we want to leave the adding of the item’s renderings to the default processor. The default processor also takes care of adding in other controls to support the Sitecore interfaces.

Now all that’s left is to configure our installation to use these new components. Jump into your web.config and find the httpRequestBegin pipeline and the LayoutResolver processor in that pipeline. We want to insert our composite presentation inheritance layout resolver immediately after it in the pipeline to allow us to swap the layout if the inherit layout is used.

<processor
  type="Composite_Presentation_Inheritance.InheritLayoutResolver,
  Composite Presentation Inheritance" />

Next we need to add our presentation aggregator into the renderLayout pipeline just before the InsertRenderings processor.

<processor
  type="Composite_Presentation_Inheritance.PresentationAggregator,
  Composite Presentation Inheritance" />

Our processor is inserted before the default processor to ensure our inherited renderings are placed into the layout before the item specific renderings.

And that’s it. We now have an implementation of a composite presentation inheritance. I can now define presentation on a base template and make it more specific by adding specific renderings for derived templates.

I’ll illustrate this by adding a new “specific page” template to good old Nicam which will inherit from simple page. The purpose of this new template is to provide a “related pages” field and rendering. All I need for the presentation for this new template is to add a new related pages rendering after the main content area. Let’s review the simple page presentation.

simple page presso

I want my related links rendering to be inserted after the SimpleText rendering above which is bound into the /phcontent/phcenter placeholder. So in my “specific page” presentation I need to use the inherit layout so we inherit the base templates presentation and then add in the related links rendering.

specific page pres

And now, requesting an item based on the “specific page” template I get the presentation combined.

specific page

One consideration if you use this technique in production is to keep in mind that the rendering now takes more time as it’s doing more. A potential performance tweak to address this concern would be to supplement the pipeline processors with an event handler that would aggregate the presentation on composite presentation inheritance layouts and store the resulting presentation into the item itself inside the publishing target databases when a publish operation finished. You still need the pipeline processors to allow content authors to preview their content appropriately and the publish:end event handler to enhance performance when using this technique.

Jul
28

This post is based on the materials I recently presented to the Sitecore Australia & New Zealand User Group.

Sitecore allows the sharing of content due to the separation of content from presentation. In fact, you can consider the presentation to itself be content as the presentation definition for an item is stored in a field; the __renderings field, whether the data for that field comes from the item itself (bad) or from template standard values (good).

Sharing a single piece of content between different sections of the same site is pretty straight forward. Th easiest way to do this would be to use proxy items in Sitecore. Proxy items are like symbolic links (for those that have used “real” operating systems) or shortcuts (for those that have used Windows :) Sorry, Windows has symbolic links now too. ). The item exists in a separate location but appears in multiple other locations. To use proxy items I need to ensure they are enabled for the database I’m working in which would be master when content editing.

To enable proxy items for a database, open the web.config file, find the database definition element and change the inner text of the proxiesEnabled element to true.

<database id="master" singleInstance="true"
  type="Sitecore.Data.Database, Sitecore.Kernel">
<proxiesEnabled>true</proxiesEnabled>

We don’t need to enable proxies in the web database. If the target database doesn’t support proxy items then published proxy items end up as real items in the target database. This is because proxy items behave like real items, even to the point of publishing. This also helps with performance because the item can be resolved by the data provider rather than the proxy data provider which I believe is accessed after the data provider.

The biggest problem we face with sharing content using proxies is that all fields are shared, including the __renderings field. So the shared item will always have the same presentation.

A potential advantage to using proxy items for sharing content is that depending on how your site is structured, the author may have more control over which items are proxied into a location rather than just taking all items in a particular location. It depends on how you view this though as some people will actually want all items proxied. It will depend on the circumstances of the site you’re building.

For the following examples, my site has an article listing item to list the proxied shared articles. The presentation for the shared article comes from the presentation definition of the shared article itself. Below I provide extracts from the renderings used on the list and article items.

I’ll include the list rendering xsl:template and item display xsl:template here for completenes. I’m sure we all already know how to enumerate child items. But this code snippet does again illustrate that proxy items behave like normal items.

List rendering template:

<xsl:template match="*" mode="main">
  <ul>
    <xsl:for-each select="child::item">
      <li>
        <sc:link/>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

Article rendering template:

<h1><sc:text field="title"/></h1>
<p>
  <sc:html field="text"/>
</p>

So what if I want to be able to change the presentation of the shared item depending on which site I’m currently in? I’ll have to somehow override the __renderings field on a site by site basis. To do this I’ll instead use a “view” item in each site which is used to display the shared article.

The rendering I use on this view item allows me to pick and choose the fields I pull out of the shared item. In this case, I’m pulling the normal content fields in. The presentation is defined on the view item itself. This allows me to define the presentation to be used for the shared articles view item by view item, site by site and even location by location (in the content tree).

The article list in this case doesn’t enumerate the children of the current item, but instead enumerates the shared articles. We’ll use the ID of the article as a query string to the view item so it knows which article it needs to render.

View item list rendering template:

<xsl:variable name="shared" select="sc:item('/sitecore/
  content/shared articles',.)"/>

<xsl:template match="*" mode="main">
  <xsl:variable name="link"
    select="sc:path(child::item[@key='detail'])"/>
  <ul>
    <xsl:for-each select="$shared/child::item">
      <li>
        <a href="{$link}?id={@id}">
          <xsl:value-of select="@name"/>
        </a>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

Note how I grab a reference to the shared articles in the variable “shared” and then enumerate it’s children. To gain control over whether a shared article should be included in my site I could add another field to the shared article which would determine which sites to share the article into, then check for that condition in my list rendering above.

View item article rendering:

<xsl:template match="*" mode="main">
  <xsl:variable name="item" select="sc:item(sc:qs('id'),.)"/>
  <h1>
    <sc:text field="title" select="$item"/>
  </h1>
  <p>
    <sc:html field="text" select="$item"/>
  </p>
</xsl:template>

All I need to do above is grab the ID of the article from the query string, find that item then render the appropriate fields.

This technique has allowed me to define a custom presentation for the shared articles per view item, but it leaves me with ugly URLs. Instead of having a nice URL such as in the proxy item case (1st case above) like http://sc61.localhost/Articles/Article2.aspx, instead I end up with an ugly URL using query string parameters like http://sc61.localhost/view/Articles/Detail.aspx?id={FCCC6478-9BA5-4634-B793-45DBC800EDEC}.

But don’t despair, there is one last technique which still allows presentation to be defined per view item, but doesn’t require ugly URLs. This is done using wildcard items. A wildcard item will handle the request for any item at that level if another item doesn’t match by name. A wildcard item is created the same as any other item. It’s name just needs to be * (star).

wildcard item

Mark Cassidy recently posted an article about wildcard nodes in which he refers to a post made by Lars Nielsen about using wildcard items and dynamic URLs to avoid query strings.

So given the above content tree, if I make a request for /articles/myarticle I will get the myarticle item. If I make a request for /articles/somethingelse the wildcard item will be used to handle the request. This opens the opportunity to handle the request in a different way. Now I can have the wildcard item list rendering generate a link for each shared article as if the article was in the same location as the wildcard item. The wildcard article rendering will need to parse the current URL to determine which article it needs to render.

Wildcard item list rendering template:

<xsl:variable name="shared" select="sc:item('/sitecore/
  content/shared articles',.)"/>

<xsl:template match="*" mode="main">
  <xsl:variable name="listroot" select="concat(substring-before(
    sc:path($sc_currentitem), '.aspx'), '/')"/>
  <ul>
    <xsl:for-each select="$shared/child::item">
      <li>
        <a href="{concat($listroot,@name)}.aspx">
          <xsl:value-of select="@name"/>
        </a>
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

Note how I have to manually create the URL for the article myself. This is because the URL we’re generating doesn’t point to a real item. The wildcard item will handle the request instead.

Wildcard item article rendering template:

<xsl:variable name="shared" select="sc:item('/sitecore/
  content/shared articles',.)"/>

<xsl:template match="*" mode="main">
  <xsl:variable name="item" select="$shared/*[@key = sc:ToLower(
    sc:urlname(0))]"/>
  <h1>
    <sc:text field="title" select="$item"/>
  </h1>
  <p>
    <sc:html field="text" select="$item"/>
  </p>
</xsl:template>

And in the article rendering I need to parse the URL to work out which item to render.

That has now solved our problems. I can change the presentation per wildcard item and I don’t have ugly URLs anymore. They look the same as the proxy technique above.

But wildcard has introduced 1 small potential issue. Although the wildcard item in the above screenshot will handle myarticle, it won’t handle myarticle\somethingelse. Wildcard items will only handle requests for a single path and level in the hierarchy. If my shared articles were created in a hierarchical structure I would need a wildcard item for each level of that hierarchy. However this may not be a problem, depending on if you intent to have hierarchical shared articles or not.

hierarchical wildcard item

All the techniques presented above don’t require any change to normal Sitecore behavior and would be preferred by most. But as you know Sitecore has a huge strength in allowing it’s behavior to be changed. I could override the default ItemResolver in the httpRequestBegin pipeline to have it route requests differently to cover off that last issue with the wildcard item technique, the hierarchical shared articles.

For this tweak I want to be able to specify a root path and a handler item path. The root path will define the root of any request which should be handled by the handler item which the handler item path references. In this way we only require a single handler item in the content tree which will handle all requests below a certain path. I’ll also make sure I can have multiple handler definitions. After all, the whole purpose for what we’re doing is to allow sharing of content across multiple sites and each site will have a different handler item.

To allow multiple root item / handler item pair definitions, I’ll need to use a list add method in my class to allow the default Sitecore class factory to handle creation of my class from web.config. This method will need to take an instance of a custom class which contains both pieces of data I need per definition.

Let’s start by defining this custom data class.

public class Handler
{
  public string RootPath { get; set; }
  public string HandlerItemPath { get; set; }

  public Handler(string rootPath, string handlerItemPath)
  {
    RootPath = rootPath;
    HandlerItemPath = handlerItemPath;
  }
}

Now let’s define the structure of our processor class including the list add method.

public class ItemResolver :
  Sitecore.Pipelines.HttpRequest.ItemResolver
{
  private List<Handler> m_handlers = null;

  public ItemResolver()
  {
    m_handlers = new List<Handler>();
  }

  public void AddHandler(Handler handler)
  {
    m_handlers.Add(handler);
  }
}

I’ll also need the following using’s to allow my code to compile.

using System;
using System.Collections.Generic;
using Sitecore;

Now to fill in the Process method which is called when a request is made.

public override void Process(
  Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
  var handled = false;
  for(int i = 0; i < m_handlers.Count; i++)
  {
    var currentPath = args.Url.ItemPath.ToLower();
    var toComp = m_handlers[i].RootPath.ToLower();
    if (currentPath.StartsWith(toComp) && currentPath != toComp)
    {
      var item = args.GetItem(m_handlers[i].HandlerItemPath);
      if (item != null)
      {
        Context.Item = item;
        handled = true;
        break;
      }
    }
  }

  if(!handled)
    base.Process(args);
}

In the code above we loop through each of the handlers checking if the current request item’s path starts with the root path of the handler. If it does then we populate the context with out handler item instead.

And then we need to alter the web.config to use and configure our new ItemResolver. Open your web.config file and find the current ItemResolver in the httpRequestBegin pipeline and replace it with the following:

<processor type="HandlerItem.ItemResolver, HandlerItem">
  <handlers hint="list:AddHandler">
    <site1 type="HandlerItem.Handler">
      <param desc="rootPath">
        /sitecore/content/Site1/Articles
      </param>
      <param desc="handlerItemPath">
        /sitecore/content/Site1/Articles
      </param>
    </site1>
    <site2 type="HandlerItem.Handler">
      <param desc="rootPath">
        /sitecore/content/Site2/Articles
      </param>
      <param desc="handlerItemPath">
        /sitecore/content/Site2/Articles
      </param>
    </site2>
  </handlers>
</processor>

Now when anything below the root path items above is requested the request will get handed over to the appropriate handler item.

We would still use the same renderings as we used for the wildcard item technique above, although we need to ensure the wildcard item rendering can handle the parsing of multiple levels in the URL and not just the current level.

And there you have a few techniques and tweaks to share content between different sites in Sitecore. The one you should use will depend largely on your requirements.

Jul
23

Last night we held the first Sitecore Australia and New Zealand User Group meeting in Melbourne, Victoria, Australia. Note for anyone wanting to hold a user group meeting, don’t do it in a noisy pub if you have presentations to make. :(

I’d chosen what I thought would be a quiet pub for a Wednesday night. Unfortunately it wasn’t so quiet. Tim Ward and myself both made presentations to the users. Tim talked about OMS and Sitecore 6.1 and I talked about Techniques for sharing content in Sitecore between sites (details on that in another post). It would have been fine if not for the noise which prevented most of the users from hearing anything Tim or I were trying to say.

So for all those that came, thank you and apologies for the noise. We’ll select the next user group meeting venue more carefully. The next meeting we’re also aiming to have in a different city to give more users the opportunity to come to a meeting.

Here’s a few pictures from the meeting.

102

Some of the users gathering before the presentations.

103

Start of the presentations.

104

Tim Ward (Sitecore Solution Architect for Sitecore Australia) presenting Sitecore 6.1 and OMS.

Jul
09

With the release of Sitecore 6.1 and OMS last week, I can finally write about it! I was lucky enough a few months ago to be invited to a sneak peak session for Sitecore’s new product the Online Marketing Suite (OMS), previously called “Everest” (Everest was actually the title given to Sitecore 6.1 and OMS 1.0). OMS is built on top of Sitecore 6.1 and provides more advanced marketing capabilities, but these capabilities rely on specific new features in Sitecore 6.1.

There were a few new features that really stuck in my mind from the presentation and I’ll discuss one of those now; analytics.

Analytics? But we’ve been doing analytics for ages using a variety of commercial and free products. What could Sitecore really do so great in the area of analytics?

It all comes down to how the analytics data is interpreted to determine what is going on on the website. Generally analytics products come in two major varieties and can be categorised by the technique they use to collect the page hits. These categories are the javascript ones and the log parser ones. Both of these techniques have certain advantages and disadvantages. Sitecore analytics in OMS covers the pitfalls of both of these previous analytics techniques.

Let’s look at javascript type analytics products. The most commonly used of these would probably be Google Analytics (based on what I’ve seen). The issue with javascript products is they can’t collect data if the user disables javascript in their browser. And there’s no fallback either. Also, usually to improve page load times and promote SEO, developers will place the javascript code required to make the analytics work at the bottom of the page. If the user is quick enough they are likely to navigate to a different page before the current page is finished loading (javascript and CSS are usually loaded sequentially by a single thread in the browser) and the code doesn’t have a chance to run and hence the analytics data is not collected.

And IE 8 provides more issues with javascript type products. IE 8 comes with a new “private browsing” feature to enhance the security and anonymity of internet users. This feature called “InPrivate blocking filter” (or so I’m told) is turned on by default and will block third party content on a website, meaning it won’t even load the google analytics javascript code. This will effectively make default IE 8 users invisible to these types of analytics products. IE 8 isn’t the only web browser that contributes to this issue. There are various Firefox plugins to enhance security and will do the same thing.

The other major type of analytics product are those based on log parsing such as Web Trends. These kinds of products parse your web servers request log and present the data to you. These kinds of products will always record data for every request made to the server. And another benefit is that they don’t require any code changes to the website, and can be implemented and changed completely independently of the website.

So that would make log parser products better than javascript right? Well, it depends on what data you’re interested in. Google analytics for example offers a very rich collection of data about the user, their browser, screen resolution, operating system, etc. With log parsers you’re limited to the data the web server logs. Much of the extended data I mention above is not even available to the web server.

Both major categories of analytics product suffer from a lack of understanding how ASP.NET works. In ASP.NET a user will make a request for a page with their browser issuing an HTTP GET request to the server. If that web page contains form controls such as buttons and these buttons use postbacks (the default behavior of Button controls in ASP.NET) then the browser will issue an HTTP POST request to send the data to the server. And for each postback control (button, droplist, linkbutton, etc) that is used on that form the same will happen; an additional request. So even though I’m just interacting with the same page, analytics products that don’t understand ASP.NET will see this essentially single request as multiple requests and the data is now skewed. Keep in mind that most analytics products are not built for ASP.NET. They are built to be generic and usable on any technology.

Potentially another issue may be to do with content reuse. Sitecore provides a few mechanisms for having the same piece of content appear in multiple locations of the website. The analytics product types above are generally just URL based. Being that they are generic and don’t know what if any CMS is being used they will see each URL as being a unique asset on the server. If we were to bring the analytics into the CMS then these statistics can be collated properly against the CMS assets where a single asset may be known by many URLs.

Given the above issues it’s plain to see that the analytics provided in OMS are going to be much more accurate and tell you much more about the web site visitor. I’d never really given a great deal of thought to how Google Analytics works and how it’s data is going to be skewed in comparison to something built directly into the CMS.