Intro

We've been working on 3 larger Umbraco sites in which we've implemented our own Linq to Umbraco strategy. It's a fairly simple strategy, it doesn't involve custom expression trees or anything, it simply leverages Linq to Xml, IEnumerable<T> and extension methods. In simple terms, we basically convert Xml nodes into our own objects which we can then run typed Linq queries against. Since the performance of deserialization of this Xml into objects was one of our concerns, we implemented Microsoft's Policy Injection framework to handle the caching of our Umbraco objects which works really well (more on this later). Here's a quick example of what this allows you to do with Umbraco data (in this case Umbraco is storing information about events, such as festivals, etc...)

var todayEvents =(from e inGetEvents()
                   
where e.FromDate.Date==DateTime.Now.Date
                   
select e).ToList();

Background

In order for this to work, there is a bit of setup involved. The way that we've gone about this implementation is by defining a data model for each Document Type that you want to be able to use in this framework. In this example, Umbraco will need to be setup with a few Document Types: Event, EventComment, EventOrganizer, EventsContainer. I've included the definitions of these doc types in the source code so you can easily import them. For an event model, we would create an interface:

publicinterfaceIUmbEvent:IUmbracoItem 
{
   
System.Collections.Generic.ListEventComments{get;set;}
   
stringEventTitle{get;set;}
   
DateTimeFromDate{get;set;}
   
stringFullDescription{get;set;}
   
stringShortDescription{get;set;}
   
boolShowInListing{get;set;}
   
DateTime?ToDate{get;set;}
}

You'll notice this interface extends IUmbracoItem which I've defined in our code library. It contains all of the basic properties of an Umbraco node:

publicinterfaceIUmbracoItem 
{
   
DateTimeCreatedDate{get;set;}
   
intCreatorId{get;set;}
   
stringCreatorName{get;set;}
   
intId{get;set;}
   
intLevel{get;set;}
   
System.Collections.Generic.DictionaryNodeData{get;}
   
stringNodeName{get;set;}
   
intNodeType{get;set;}
   
stringNodeTypeAlias{get;set;}
   
intParentId{get;set;}
   
stringPath{get;set;}
   
intSortOrder{get;set;}
   
int?Template{get;set;}
   
DateTimeUpdateDate{get;set;}
   
stringUrlName{get;set;}
   
stringVersion{get;set;}
   
stringWriterName{get;set;}
}

Next I've created a class that implements this interface called UmbEvent which extends UmbracoItem(which in turn implements IUmbracoItem). The constructor function of these objects are what does the deserialization:

publicUmbEvent(XElement x) 
           
:base(x)
{
   
//Get the from date and use the DateConverter to convert it.
   
//If the returned date is MinValue, then the value in Umbraco has not actually
   
//been set and since it is mandatory, we'll throw an Exception.
   
FromDate= x.UmbSelectDataValue("FromDate",DateConverter);
   
if(FromDate==DateTime.MinValue)
       
thrownewException(string.Format(
               
"The node with id {0} does not have a start date set",
               
this.Id.ToString()));

   
//Get the ToDate using a NullableDateTimeConverter
   
ToDate= x.UmbSelectDataValue("ToDate",NullableDateTimeConverter);

   
EventTitle= x.UmbSelectDataValue("EventTitle");
   
ShortDescription= x.UmbSelectDataValue("ShortDescription");
   
FullDescription= x.UmbSelectDataValue("FullDescription");
   
   
//Get the ShowInListing using the IntConverter since the value stored in Umbraco is
   
//an integer, not a boolean.
   
ShowInListing= x.UmbSelectDataValue("ShowInListing",IntConverter)==1;            
   
   
//Select all child "node" nodes that have a node type alias
   
//of "Event Comment" from the current element, deserialize them
   
//to UmbEventComment objects, and store them in our List property.
   
EventComments= x.UmbSelectNodes()
       
.UmbSelectNodesWhereNodeTypeAlias("EventComment")
       
.Select(n =>newUmbEventComment(n))
       
.ToList();            
}

In the code for the UmbracoItem class are some helper methods for data conversion so exceptions are not thrown and that data is converted properly. 

The next step is to setup the data service layer. I generally create a different service class for each model and in this example we'll have IUmbEventService interface with an UmbEventService class defined as:

publicinterfaceIUmbEventService:IUmbracoService 
{
   
intCreateEvent(IUmbEvent cqEvent);
   
voidUpdateEvent(int eventId,IUmbEvent e);
   
ListGetEvents();
   
IUmbEventGetEvent(int eventId);
   
DateTimeLastEventDate{get;}
}

Using the code 

Service Layer 

Most of the important work is done in the service layer. The underlying method that is called for retreiving events is the GetEvents() method which returns a List<IUmbEvent> object which we can use to query. This method will look up all events in the Umbraco xml cache, convert each one to an UmbEvent and return a list of these deserialized objects. I've created a bunch of Linq to Xml extension methods for use with Umbraco which I use to query the Umbraco xml cache. This makes querying Umbraco xml much nicer, easier and allows you to use System.Xml.Linq objects instead of the old XPath objects. Here's the definition of the GetEvents() method:

[CachingCallHandler(1,0,0)] 
publicListGetEvents()
{

   
Console.WriteLine("Looking up and caching all published events...");

   
//Lookup the event container node in Umbraco
   
XElement xNode =UmbXmlLinqExtensions.GetNodeByXpath(EventContainerXPath);

   
var eventData = xNode
       
.UmbSelectNodes()//selects all descendant "node" nodes
       
//selects nodes of a certain alias
       
.UmbSelectNodesWhereNodeTypeAlias(EventNodeTypeAlias)
       
//This does the object conversion
       
.Select(x =>newUmbEvent(x))
       
//ensure we don't return events with no start date
       
.Where(x => x.FromDate!=DateTime.MinValue);

   
return eventData.Cast().ToList();
}

Now that this method is create, we can just query the result of the method to find Events by whatever criteria we've defined in the IUmbEvent interface. For example, the GetEvent method body is quite simple:

publicIUmbEventGetEvent(int eventId) 
{
   
var myEvent = m_This.GetEvents()
       
.Where(x => x.Id== eventId);

   
return myEvent.SingleOrDefault();
}

To expose my service layer classes, i have a service factory class that creates new instances of each service type when requested. This makes it easy to call services from code since there's always one point of entry.

Calling the services

I've included a class called Tests which calls the methods of the event service. With the above framework in place, using the code is extremely easy:

List events = m_Factory.EventService.GetEvents();

or

IUmbEvent e = m_Factory.EventService.GetEvent(LookupEventId);

Also in the source code are service methods to create and update Umbraco nodes. If you're not familiar with how to do this, this will show you a fairly easy way to get the job done. The CreateEvent method actually checks to see if a "Pending" event organizer node is there and if not it creates one and then creates new unpublished events under this node.

Running the tests

I've created a simple ashx handler that you can setup in your Umbraco project to test this data layer. You'll need to build the solution and copy the DLL files over from the bin folder of the UmbracoData project into your Umbraco bin folder (don't copy over the Umbraco DLLs if you don't want to ... these are Umbraco 4 DLLs). Then add an HttpHandler to your web.config:

<add verb="*" path="datatest.ashx" type="UmbracoData.TestHandler, UmbracoData" />

You'll have to make sure Umbraco is setup for this project so you'll need to: 

  • Import all of the document types
  • Create a node of type "EventsContainer" called "Events" under the root content node

Now you can navigate to datatest.ashx which will allow you to run the tests. If you want to step through the code, just Attach to your Asp.Net process with the solution open.

Policy Injection

As I mentioned before, performance will be a factor if for every event query, we need to deserialize every event xml node into an object. This is where caching comes in handy. We use Policy Injection quite a bit to do caching and logging in our applications as it makes these tasks incredibly easy. Policy Injection is a simple form of AOP (Aspect Oriented Programming) and is part of Microsoft's Enterprise Library. You'll see that this method is attributed with the CachingCallHandler which is going to cache the output of this method for 1 hour in this case. In each subsequent call to this method Policy Injection will intercept the call and determine if it has been cached, and if so, it will simply return the cached results and cancel the execution of the method.

In order for Policy Injection to work, you need to either create a new service class or wrap and existing service class the with Policy Injection. As i mentioned before I use a service factory to expose all of my service classes and in this class, it creates each service with Policy Injection:

publicclassServiceFactory 
{
   
publicIUmbEventServiceEventService
   
{
       
get
       
{
           
returnPolicyInjection.Create();
       
}
   
}        
}

There is a catch!! You'll notice in the above code snippet that defines the GetEvent method uses the syntax: m_This.GetEvents(). This is because we need a reference to the event service class wrapped in Policy Injection inside the event service class.... confusing in know. This is because the Policy Injection framework is the only thing that knows anything about the CachingCallHandler attribute and if we call GetEvents inside one of the methods of the UmbEventService, then Policy Injection is actually not playing any part of that method call. To get around this, we define an internal property in the UmbEventService class of type IUmbEventService and in the constructor function, we wrap this instance in Policy Injection and assign it to this property:

publicUmbEventService() 
{
   
//Ensure our internal object is wrapped in PolicyInjection
    m_This
=PolicyInjection.Wrap(this);
}

///  
/// We declare this variable as a "trick" to ensure that PolicyInjection
/// is still used when we call internal members of this class.
/// For example, any method of UmbEventService that calls GetEvents() will need
/// to call m_This.GetEvents() to ensure that it returns the cached data
/// and does not re-execute the code in body of the method.
///  
IUmbEventService m_This;

Another point of interest is that i've included an Umbraco action handler to refresh the Policy Injection cache whenever a node is published.