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:
- Write an ItemEventProvider which calls the standard umbraco
document api to delete a document
- 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:
public class UnPublishDocument : ItemEventProvider
{
public override string Alias
{
get{ return "DeleteDocument"; }
}
public override void Execute(ItemIdentifier itemId, SerializableDictionary<string, string> Parameters)
{
Guid g;
//if the itemId is a valid Guid we will fetch a umbraco document from it
if (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 subscriptions
public class 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:
public class 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