Umbraco Action Handlers 101

Posted Wednesday, February 13, 2008 8:12 PM by Kevin
Something I have done a few times in Sitecore is to have new news articles or blog posts automatically organize themselves within year/month folders in my content tree.  The URL to a specific news article might be /news/2008/02/ActionHandler.aspx for instance.  When I create the ActionHandler content item, the 2008 folder and the 02 folder would be created automatically if they did not already exist and my posting would be placed under these folders for me.  In Sitecore, I do this with an event handler for item:creating.  Tonight, I set out to do the same thing with Umbraco.

First, let me present an example of  what I want to see when I'm done...



I want to be able to right-click on "Blog" and create a new document of type "BlogPost".  Given today's date, I want a year folder and a month folder to be created (if they don't already exist) and my new post moved into the proper folder automatically.

A bit of research uncovered Umbraco's concept of Action Handlers.  Basically, these are your own classes you can use to perform custom actions when certain events occur in Umbraco's content editor.  They just have to implement the umbraco.BusinessLogic.Actions.IActionHandler interface and exist in an assembly in the Umbraco bin folder.

So, I set out to implement my DateFolderActionHandler class...  I created a new class library project, added references to Umbraco's businesslogic.dll, cms.dll, and interfaces.dll, and added a class that implements IActionHandler.  Visual Studio was kind enough to fill in a skeleton implementation for me - here's what it looked like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using umbraco.BusinessLogic.Actions;
using umbraco.BusinessLogic.console;
using umbraco.cms.businesslogic.web;
 
namespace Demo
{
    class DateFolderActionHandler : IActionHandler
    {
        #region IActionHandler Members
 
        public bool Execute(Document documentObject, umbraco.interfaces.IAction action)
        {
            throw new NotImplementedException();
        }
 
        public string HandlerName()
        {
            throw new NotImplementedException();
        }
 
        public umbraco.interfaces.IAction[] ReturnActions()
        {
            throw new NotImplementedException();
        }
 
        #endregion
    }
}

The first method that grabbed my attention was HandlerName() - all I have to do is return a string!  I did some digging with Reflector and it doesn't appear to be used for anything important, so return anything you want from this method.  I used "DateFolderActionHandler" - the name of my class.

Next up - ReturnActions().  This appears to return a list of actions your handler wants to be notified of.  The actions you can choose from are members of the umbraco.BusinessLogic.Actions namespace.  My handler needs to execute when a new content item is created, so I'm using ActionNew().  ReturnActions() now looks like this:

public umbraco.interfaces.IAction[] ReturnActions()
{
    return new umbraco.interfaces.IAction[] { new umbraco.BusinessLogic.Actions.ActionNew() };
}

Last to implement is the Execute() method.  I'm guessing Execute() gets called when one of the actions you have subscribed to occurs.  The Document must be the content item that was acted upon and the IAction must be the action that was triggered.  In my case, I am only subscribing to ActionNew so I don't have to check the action, but I will in this example to demonstrate how it's done.

// Not interested in anything but "create" events.
if (action.Alias != "create") return true;

My action handler will be called for every new content item that is created, so the first thing I will want to do is verify that this document is one that I'm interested in.  I checked the content type like this:

// Not interested if the item being added is not a blog post.
if (documentObject.ContentType.Alias != "BlogPost") return true;

Note that I am returning true on both of these cases.  My assumption was that the return value from Execute() indicated if the action should be allowed to continue or not.  After playing around with returning false (and peeking at the code with Reflector), it appears the return value is just ignored.  Shame on Umbraco - there does not appear to be any way to cancel an action!  Even throwing an exception here doesn't keep the action from happening.  Anyway - I will continue to return true when I want the action to complete and false if I don't in hopes that it will someday make a difference. :)

So if my code is still executing, I know I have a new document that I'm interested in placing in date folders.  I want a folder for the year and a folder for the month, so I'll start with the current date.

string year = DateTime.Now.ToString("yyyy");
string month = DateTime.Now.ToString("MM");

As I mentioned at the start of this article, I expect the new document to be created as a child of "Blog".  The year folder should also exist as a child of "Blog".  My first task is to check if that already exists.  Here's the code I used:

Document yearDocument = null;
foreach (IconI child in documentObject.Parent.Children)
{
    if (child.Text == year)
    {
        yearDocument = new Document(child.Id);
        break;
    }
}

(note - I have no idea why the members of documentObject.Parent.Children are of type "IconI".  Maybe someone that knows Umbraco better can answer that?)

What this does is get the new item's parent's children (children of "Blog") and loops through them looking for one whose name matches the current year.  If we make it through this loop and yearDocument is still null, we know the folder doesn't exist and needs to be created.  Here is how I handle that:

// If the year folder doesn't exist, create it.
if (yearDocument == null)
{
    yearDocument = Document.MakeNew(year, DocumentType.GetByAlias("YearFolder"), documentObject.User, documentObject.Parent.Id);
}

The Document.MakeNew() call creates a new content item named for the current year, using the document type "YearFolder", created by the same user that created the blog post, underneath the parent of the blog post ("Blog").

Now I have to repeat the same process for the month folder...

Document monthDocument = null;
foreach (IconI child in yearDocument.Children)
{
    if (child.Text == month)
    {
        monthDocument = new Document(child.Id);
        break;
    }
}
 
// If the month folder doesn't exist, create it.
if (monthDocument == null)
{
    monthDocument = Document.MakeNew(month, DocumentType.GetByAlias("MonthFolder"), documentObject.User, yearDocument.Id);
}

And the final step is to move the new content item into the month folder...

// Move the document into the month folder.
documentObject.Move(monthDocument.Id);

Then I can just return true and I'm done!

If you want to see the final DateFolderActionHandler.cs source file, I have attached it to this blog post.

Comments

# re: Umbraco Action Handlers 101

Wednesday, February 13, 2008 10:14 PM by Kevin

Wow, the formatting was really messed up in that the first time around.  Took me a few tries to get it fixed (hopefully it didn't re-syndicate it to RSS a bunch of times - if so, SORRY!), but I think it looks ok now.

# Shared Tutorials » Blog Archive » Umbraco Action Handlers 101

Pingback from  Shared Tutorials  » Blog Archive   » Umbraco Action Handlers 101

# re: Umbraco Action Handlers 101

Tuesday, September 15, 2009 12:05 AM by sam7

I want create  this (year,month,date) folders for more than one document Type..

so if we have a BlogPost and a NewsPost , it fires on both document types... .

Thanks..