Monday, December 12, 2011

Extending Courier 2 with a custom event

I got an email today from a developer who wanted to delete documents with Courier. As you might know, Courier does not support this, as the focus is moving and deploying content, not removing it. You can ofcourse unpublish items, and Courier will then unpublish them from your site. But incase of accidents, they will still be there.

What we could do however, was to force Courier to delete any document which is unpublished.

A problem thats easy to solve

However, because of the architecture behind Courier, we have an easy and clean way of doing it, we simply need to do 2 things:

  1. Write an ItemEventProvider which calls the standard umbraco document api to delete a document
  2. Subscribe to the Courier Document Item providers Extraction event to trigger the Delete document EventProvider

Writing a custom ItemEventProvider

An ItemEventProvider is a simple class, that has nothing else but an alias, and a Execute() method. In the Execute() method you can then add whatever code you want. Through Courier you can either tell it to execute the event, or you can tell it to add the Event to a specific queue which will execute later. We will get to that part later.

First we need a couple of references to the Courier dlls: Umbraco.Courier.Core.dll, Umbraco.Courier.ItemProviders.dll, and the Umbraco dlls: cms.dll, businesslogic.dll and interfaces.dll

With these usings in place:

using Umbraco.Courier.Core; using Umbraco.Courier.Core.ProviderModel; using Umbraco.Courier.Core.Diagnostics.Logging; 

We can now implement the following class:

publicclass UnPublishDocument : ItemEventProvider { publicoverridestring Alias { get{ return"DeleteDocument"; } } publicoverridevoid Execute(ItemIdentifier itemId, SerializableDictionary<string, string> Parameters) { Guid g; //if the itemId is a valid Guid we will fetch a umbraco document from itif (Guid.TryParse(itemId.Id, out g)) { try { //get the native umbraco document object umbraco.cms.businesslogic.web.Document d = new umbraco.cms.businesslogic.web.Document(g); //call the native umbraco document api .delete();if (d != null) d.delete(); } catch (Exception ex) { //logging of errors RevisionLog.Instance.AddItemEntry(itemId, this.GetType(), "DeleteDocument", ex.ToString(), LogItemEntryType.Error); } } } } 

So we have an event with the alias "DeleteDocument" inside of its execute() method we lookup an ItemIdentifier, in the case of document items, this always referes to the Unique ID of the document, which is a Guid.

We then fetch the document with the native umbraco document api and delete it.

All done.

Hooking into the Courier event model

So we now have an event, we then need to trigger this event at the appropriate time. Due to the way Courier runs an Extraction as a Database transaction, we need to wait untill after this is done, as we cannot access data inside the transaction while it runs. So we cannot call umbraco.cms.businesslogic.document, as it cannot get access to the document table.

The solution is to use the Build-in Event queue that Courier uses to delay events like these, untill after the database transaction is complete, that means that we can collect a ton of events during the extraction, and then execute all of them after the transaction is done. To do this, we simply need access to a ItemProvider while the extraction is running, as Couirer keeps the context synced between the source and target machine.

using Umbraco.Courier.Core; using Umbraco.Courier.ItemProviders; using Umbraco.Courier.Core.Enums; 

And then we use the good ol' umbraco ApplicationBase to subscribe to events on Application start, in this case we call Umbraco.Courier.ItemProviders.DocumentItemProvider, which is the provider that handles deployment of documents and hook into it's Extracted event, which is triggered after the extraction / deployment of a document is done

//hook into the standard applicationbase for event subscriptionspublicclass EventSubscriber : ApplicationBase { public EventSubscriber() { //get the DocumentItemProvider DocumentItemProvider.Instance().Extracted += new EventHandler<ItemEventArgs>(DataResolver_Extracted); } } 

Inside the event we access the current document being extracted, and the Item Provider being used

//this is Courier Item, not a native umbraco document Document d = e.Item as Document; //the current provider used ItemProvider provider = sender as ItemProvider; 

Finally we check if the document is unPublished, if it is, then we add a "DeleteDocument" event the queue that runs after the Database Transaction is Complete (DBTransactionComplete)

if(!d.Published) provider.ExecutionContext.QueueEvent("DeleteDocument", d.ItemId, null, EventManagerSystemQueues.DBTransactionComplete); 

The provider has access to an ExecutionContext, which has access to currently active queues, we pass in the alias of the DeleteDocument ItemEvent Provider, then the ItemId, and finally the name of the queue we want it to be in.

The complete code looks like so:

publicclass EventSubscriber : ApplicationBase { public EventSubscriber() { //get the DocumentItemProvider DocumentItemProvider.Instance().Extracted += new EventHandler<ItemEventArgs>(DataResolver_Extracted); } void DataResolver_Extracted(object sender, ItemEventArgs e) { //this is Courier Item, not a native umbraco document Document d = e.Item as Document; //the current provider used ItemProvider provider = sender as ItemProvider; //if the document is not published, we queue the event "DeleteDocument" in the DBtransactionComplete queue//this will then be executed after the DB transaction is done.if(!d.Published) provider.ExecutionContext.QueueEvent("DeleteDocument", d.ItemId, null, EventManagerSystemQueues.DBTransactionComplete); } } } 

So with a total of ten lines of Code (excluding all the namespace and using declarations) we have now enabled Courier to delete documnets that are set to an unpublished state

We have full documentation on ItemEventProviders, the EventQueues, and Dataresolvers in this PDF

And you can download the source for the above example here

Want to be updated on everything Umbraco?

Sign up for the Umbraco newsletter and get the latest news and special offers send directly to your inbox