NCIM Sitecore Team

Just another WordPress.com site

Sitecore Item Mapping (part 1)

leave a comment »

Problem: Missing items

Avatar Chantal WeijersThere’s an issue with our website since one of our content authors made some changes to the Sitecore content tree. Some items are not showing up on the site. Could you have a look what could be wrong?

Avatar Marc DuikerIt appears that some content items have been moved. Since the current code uses hard-coded item paths to locate the items there’s now a mismatch between the code and the items in Sitecore. There are various options how this can be solved and prevented in the future, let me explain…

Solution: Item mapping using GUIDs

The first thing that comes to mind is that the developers could use item GUIDs in their code instead of (or better: in combination with) item paths or names when retrieving items from the database. The reason is that the item GUID remains the same even when the item is renamed or moved. This would prevent mapping mismatches in such cases.

Warning: Do not use  GUIDs to retrieve content items which are supposed to be dynamic or volatile. Access those items via their static parent item. For a truly dynamic & flexible approach a Setting item could be used which contains GUIDs and paths to the dynamic content items (this last approach is not covered in this post).

Basic Example

Consider the following example of an online bookstore where individual books are stored as content items as children of the Books folder. The book items are based on the Book template (Figure 1). The individual books are considered dynamic content while the Books folder is considered to be static and therefore suitable to be accessed by the GUID.

Sitecore master tree highlighted

Figure 1

The next three code samples each show a GetBooks method which returns the exact same items: a list of books (from the Books folder) that match the Book template.

Approach using only paths and names

public static IEnumerable<Item> GetBooks()
{
    const string booksItemPath = "/sitecore/content/Home/Books";
    const string bookTemplateName = "Book";

    return Sitecore.Context.Database.GetItem(booksItemPath).GetChildren().Where(
        item => item.TemplateName == bookTemplateName);
}

Pro: If  items are deleted and replaced by new items with the same name and location the solution will still work.
Con: If items are moved or renamed the solution will break.

Approach using only GUIDs

public static IEnumerable<Item> GetBooks()
{
    const string booksItemGuid = "{0842454E-361B-4AD3-B03F-43E89779A40F}";
    const string bookTemplateGuid = "{54C24C4E-AAA9-41D3-9977-73C81D5E7747}";

    return Sitecore.Context.Database.GetItem(booksItemGuid).GetChildren().Where(
        item => item.TemplateID.ToString() == bookTemplateGuid);
}

Pro: If items are renamed or moved the solution will still work.
Con: If items are deleted and replaced by new items with the same name the solution will break.

Combined approach using both identifiers

public static IEnumerable<Item> GetBooks()
{
    const string booksItemPath = "/sitecore/content/Home/Books";
    const string booksItemGuid = "{0842454E-361B-4AD3-B03F-43E89779A40F}";
    const string bookTemplateName = "Book";
    const string bookTemplateGuid = "{54C24C4E-AAA9-41D3-9977-73C81D5E7747}";

    return GetContentItem(booksItemGuid, booksItemPath).GetChildren().Where(
        item => IsTemplateMatch(item, bookTemplateGuid, bookTemplateName));
}

private static Item GetContentItem(string id, string path)
{
    // First try to get item via ID.
    // If no item is found try to use the item path.
    return Sitecore.Context.Database.GetItem(new ID(id)) ?? Sitecore.Context.Database.GetItem(path);
}

private static bool IsTemplateMatch(Item item, string expectedId, string expectedName)
{
    return (item.TemplateID.ToString() == expectedId || item.TemplateName == expectedName);
}

Pro: Works in both situations where items are renamed/moved or deleted and replaced with other items having equal names.
Con: Matching templates by name (as a fallback) might not always be a good choice. You really need to make sure the solution uses uniquely named templates.

How to get the GUID

The easiest way to copy an item GUID is to use Sitecore Rocks, double-click the item to open it and then click the small clipboard icon next to the Item ID (Figure 2).

Sitecore Books folder item highlighted

Figure 2

Structuring the identifiers

Although the usage of GUIDs in addition to paths and names result in more reliable solutions (which perform better than only relying on paths) the GUIDs are not very developer friendly with respect to readability and ease of use.

The following code samples show how item mapping can be further abstracted by placing the identifiers (GUIDs, paths, names) in a static class. The structure of this static class can be modeled to mimic the content tree in Sitecore. This allows a more fluent use of identifiers for the developer and by keeping all the GUIDs, paths and names in one place it also improves the maintainability of the code.

/// <summary> Static class which mimics the structure of the Sitecore content and templates tree
/// in order to provide fluent access to content item and template identifiers.
/// </summary>
public static class Model
{
    public static class Templates
    {
        public static class Book
        {
            public static TemplateID TemplateId
            {
                get { return new TemplateID(new ID("{54C24C4E-AAA9-41D3-9977-73C81D5E7747}")); }
            }

            public static string Name
            {
                get { return "Book"; }
            }
        }
    }

    public static class Content
    {
        public static class Books
        {
            public static ID Id
            {
                get { return new ID("{0842454E-361B-4AD3-B03F-43E89779A40F}"); }
            }

            public static string Path
            {
                get { return "/sitecore/content/Home/Books"; }
            }
        }
    }
}

Once the Model class is in place the GetBooks method can be refactored as follows:

public static IEnumerable<Item> GetBooks()
{
    return GetContentItem(
        Model.Content.Books.Id, Model.Content.Books.Path).GetChildren().Where(
            item => IsTemplateMatch(
                item, Model.Templates.Book.TemplateId, Model.Templates.Book.Name)
    );
}

(The GetContentItem and IsTemplateMatch methods are slightly adapted as well to use TemplateID and ID objects directly instead of the GUID strings.)

Sitecore does it too!

You don’t need to create static model classes for standard Sitecore templates and items since they already exist! Have a look at the Sitecore.TemplateIDs and Sitecore.ItemIDs classes in the Sitecore.Kernel.dll. Here’s an example how to retrieve the content root item using the Sitecore.ItemIDs class.

Item contentRoot = Sitecore.Context.Database.GetItem(Sitecore.ItemIDs.ContentRoot);

Static vs Instance

As you can see in the last GetBooks method which uses the model, using static nested classes result in quite some code bloat when calling the GetContentItem and IsTemplateMatch helper methods since the full nested hierarchy of the model needs to be specified as parameters.

An alternative would be to use instance classes to define content and template items and implement interfaces for them. The helper methods could then accept these interfaces which further decouples the code and improves testability as well.

The following code sample shows one possibility how instance classes and interfaces could be used to create a model.

public static class Model
{
    public static IContentItem GetBooksItem()
    {
        return new Books();
    }

    public static ITemplate GetBookTemplate()
    {
        return new Book();
    }
}

public class Books : IContentItem
{
    public ID Id
    {
        get { return new ID("{0842454E-361B-4AD3-B03F-43E89779A40F}"); }
    }

    public string Path
    {
        get { return "/sitecore/content/Home/Books"; }
    }
}

public class Book : ITemplate
{
    public TemplateID TemplateId
    {
        get { return new TemplateID(new ID("{54C24C4E-AAA9-41D3-9977-73C81D5E7747}")); }
    }

    public string Name
    {
        get { return "Book"; }
    }
}

public interface IContentItem
{
    ID Id { get; }
    string Path { get; }
}

public interface ITemplate
{
    TemplateID TemplateId { get; }
    string Name { get; }
}

Using this new Model class the GetBooks method and the helper methods can be refactored as follows:

public static IEnumerable GetBooksUsingNewModel()
{
    return GetContentItem(Model.GetBooksItem()).GetChildren().Where(
        item =>; IsTemplateMatch(item, Model.GetBookTemplate()));
}

private static Item GetContentItem(IContentItem contentItem)
{
    return Sitecore.Context.Database.GetItem(contentItem.Id) ??
        Sitecore.Context.Database.GetItem(contentItem.Path);
}

private static bool IsTemplateMatch(Item item, ITemplate template)
{
     return (item.TemplateID == template.TemplateId || item.TemplateName == template.Name);
}

What’s next?

Manually creating model classes in order to map items is ok for a small project with just a couple of items and templates. But in a real world project, where dozens of models with all their properties would be required, manual creation would not be the way to go. Thankfully there are some alternatives…

Automated Model Generation

There are various solutions available which generate code in order to do item mapping. The solutions I currently have listed are:

  1. CodeGen
  2. Custom Item Generator
  3. Compiled Domain Model
  4. Glass.Sitecore.Mapper
  5. Sitecore Rocks

To avoid one very, very long blog post I will cover each solution in a separate post over the next few weeks. For each solution I’ll discuss the following aspects in order to make a good comparison:

  • Installation & Configuration
  • Ease of use
  • Features & Extensibility

If you know any other code or item generation products or want to include other aspects, leave a comment here on the blog or send me an email!

(This post was updated on June 15 to include samples of retrieving items using both GUIDs and paths and mentioning the pros and cons of each method. The Static vs Instance section was also added.)

Advertisements

Written by marcduiker

June 11, 2012 at 8:27 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: