« Modifying the HttpRequest pipeline, custom 404 handler | HomePage | Sitecore on CM Innovation 2007 »

Wednesday, 24 October 2007

Sitecores abstract data layer

This is an area that I know for sure most people have not been working with at all in Sitecore. It’s the abstract data layer which lies between the physical layer (data providers), and the API.

 

The traditional description of the abstract data layer is that it handles Sitecore caching. However, it does a bit more than that; it parses the actual requests from the API, - figures out which data provider the current branch uses, and then passes the request to the data provider.

f5ed4d1d714b27843076cbdb8a2d968e.png

 

Usually developers would not hook into the abstract data layer, - partly because it’s a very complex layer, - and normally there’s no reason to do so. However, a better understanding of this layer comes in handy, and in very rare occasions hooking into the layer may be a good way to tamper with data on a overall level.

The abstract data manager holds an interesting class, - ItemManager which in passes all request from the API to an ItemProvider object. Methods such as GetParent, GetChildren and GetItem can be found in this object. The ItemProviders job is to pass the requests on to the physical layer, - the data providers.

If you override the existing provider, you can hook into the data stream and tamper with it, - and ultimately the data after it’s retrieved from the physical layer. Modifying the data is not depending on an individual database or database provider, but will happen across all databases.

An example of such modification would be adding a filter to the abstract data layer which may remove unwanted items, such as items that does not have a specific value or data specified; and this will be across all database providers (e.g. filter data regardless of it coming from SQL server, SharePoint or any other database).

To create such a hook, create a class that inherits from ItemProvider; and the override appropriate methods (for example, GetChildren). Register the class by setting the ItemManager.provider property to your custom class. This, - however, is not easy as the provider is registered automatically when Sitecore initializes, - and it’s not an entry in the web configuration file. This can be done, however, by creating a piece of code that too is run when Sitecore initializes, and from this point setting the property.

 

Example: Filter children

The following example will filter all child items of a parent in the master database starting with the value provided by a field in the parent item (this field is conveniently named filter). Here’s some before/after screenshots:

Before filter has been applied:
12b3046aad0f5066a1146d9bee60212d.png

 

A screenshot of the content editor after the filter has been applied: Notice that the two printers starting with BL has disappeared.
b06df3b03b747bea7780e9d8e8c6a5b3.png

 

In order to implement this code, we need to write (or rather, override) the item provider itself. We also need to write a bit of code which, - upon initialization, sets the ItemManager provider property to our custom class; to do so, we use the initialization pipeline (which can be found in the web configuration file).

To hook into the initialization pipeline, we must create a small processor; let’s name it ReplaceItemProvider: We add this provider to the end of our initialization pipeline:

 

<pipelines>
  <initialize>
     :
    <processor type="Sitecore.DynamicXslExtensions.Loader,
      Sitecore.DynamicXslExtensions"
/>
    <processor type="Sitecore.Testing.ReplaceItemProvider,
      ItemProviderExtender"
/>
  </initialize>
  :

 

I have added the processor to the end of the initialize processor, in order to ensure that all existing classes and objects has been created and registered. The following code dynamically sets the property of the ItemManager to our custom ItemProvider:

 

using Sitecore.Data.Managers;
using Sitecore.Pipelines;

namespace Sitecore.Testing {
  public class ReplaceItemProvider {
    public void Process(PipelineArgs args) {
      ItemManager.Provider = new FilterItemProvider();
    }
  }
}

 

The FilterItemProvider class filters the actual data:

 

using System;
using Sitecore.Collections;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;
using Sitecore.SecurityModel;

namespace Sitecore.Testing {
  public class FilterItemProvider : ItemProvider{

    public override ChildList GetChildren(Item item,
      SecurityCheck securityCheck) {
      Assert.ArgumentNotNull(item, "item");
      if (item.Database.Name!="master"){
        return base.GetChildren(item, securityCheck);
      }
      ChildList children = base.GetChildren(item, securityCheck);
      if(children == null){
        return null;
      }
      return Filter(children, item);
    }

    ChildList Filter(ChildList children, Item parent) {
      string filter = parent["filter"];
      if(filter.Length <= 0) {
        return children;
      }
      ItemList filtered = new ItemList();
      foreach(Item child in children) {
        if(!child.Name.StartsWith(filter, 
            StringComparison.OrdinalIgnoreCase)) {
          filtered.Add(child);
        }
      }
      return new ChildList(parent, filtered);
    }
  }
}

 

The code is rather self-explaining, but there are a few interesting parts here:
I override the GetChildren method only, and call’s the base method: The base method returns all the items from the data provider layer; filtering is done after by creating a new (filtered) list:

 

public override ChildList GetChildren(
     Item item, SecurityCheck securityCheck) {
  :
  ChildList children =
       base.GetChildren(item, securityCheck);
  :
  return Filter(children, item);
  :

 

I apply the filter by calling the internal Filter method: If the parent item has a “filter” field, and it’s not blank, items will be added to a new list, if they match the criteria:

 

:
foreach(Item child in children) {
  if(!child.Name.StartsWith(filter,
      StringComparison.OrdinalIgnoreCase)) {
    filtered.Add(child);
  }
}

13:55 Posted in Sitecore | Permalink | Email this

Comments

wicked! i -so- need to take a stab at this with a few ideas i've been having :)

Posted by: Peter | Thursday, 25 October 2007