Mats Halfvares, the Content Studio Development team

Asynchronous event handlers in Content Studio

Asynchronous events is a powerful functionality that executes off-line when a user have performed a certain action in Content Studio. Unlike the synchronous events which behave very much the same way as a regular event in Windows program or as a trigger in SQL Server, asynchronous events are queued in Content Studio and their event handlers are executed by the Service Manager later. The user will not get any feedback of the outcome of the event handler but any queued event can be viewed in the JobQueue and the outcome of the event can be examined in the Content Studio event log.

Using asynchronous events

Asynchrous events are perfect to use when there is a need to performs something as a response to an action och a document in Content Studio and that action can be time consuming. This action can be executed in an asynchronous event handler object that gets invoked when the Content Studio Service Manager discovers that there is a job in the queue.

The following synchronus events are supported in Content Studio 5.2.

OnDocumentApprove
Occurs when a document has been approved.
OnDocumentCheckIn
Occurs when a document is checked in.
OnDocumentCheckOut
Occurs when a document is checked in.
OnDocumentCreate
Occurs when a document has been created.
OnDocumentDelete
Occurs when a document has been thrown in the recycling bin.
OnDocumentDestroy
Occurs when a document in the recycling bin is deleted.
OnDocumentExpire
Supported in Content Studio version 5.2 and later
Occurs when a document is no longer published because its publish data has expired (Archived).
OnDocumentPublish
Supported in Content Studio version 5.2 and later
Occurs when a document is published.
This event can occur when the document has been published because its publish date has occured (the document was queued) or when an editor changed the document's publish status from NotPublished to Published.
OnDocumentReject
Occurs when a document on workflow is is rejected by an editor.
OnDocumentRevision
Occurs when a document is sent on versioning so it can be approved by an editor.
OnDocumentSave
Occurs when a document draft is saved.
OnDocumentUnPublish
Supported in Content Studio version 5.2 and later
Occurs when a document is no longer published, i.e. when a published document's publish date is set to a value in the future or when the published flag is turned off.

Building an event handler from scratch

Event actions handlers are public objects that implement the ICSAsyncEventHandler interface. Even though you still can implement this interface directly there is very little use to do that when you can use the base classes in the ContentStudio.EventActions.AsynchronousEventHandlers namespace instead.

For more information and programming examples, see the ICSAsyncEventHandler.Eventhandler documentation .

Building an event handler using the AsynchronousEventHandlers library

When you create an event handler that can interact with Content Studio through asynchronous events you need to extend any of the base classes found in the ContentStudio.EventActions.AsynchronousEventHandlers namespace. If you use Visual Studio 2005 or later you need to start a new project of the class library type. You also need to reference CS5Interfaces.dll and AsynchronousEventHandlers.dll. These two libraries will be installed as a part of Content Studio and can be located in the Content Studio binaries folder, by default C:\Program Files\Teknikhuset\Content Studio 5\CSServer.

Classes to derive from when building asynchronous event handlers
Class name Usage
AsynchronousEventHandlerBase The ultimate base class for that all event handlers inherit from. Unless you would like to create an event handler that can handle more than one event type you should use one of the specialized classes instead.
DocumentApproveAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentApprove Content Studio event.
DocumentCheckInAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentCheckIn Content Studio event.
DocumentCheckoutAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentCheckout Content Studio event.
DocumentCreateAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentCheckIn Content Studio event.
DocumentDeleteAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentDelete Content Studio event.
DocumentDestroyAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentDestroy Content Studio event.
DocumentExpireAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentExpire Content Studio event.
DocumentPublishAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentPublish Content Studio event.
DocumentRejectAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentReject Content Studio event.
DocumentRevisionAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentRevision Content Studio event.
DocumentSaveAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentSave Content Studio event.
DocumentUnPublishAsyncHandler Derives directly from AsynchronousEventHandlerBase and is used to create event handlers that handle the OnDocumentUnPublish Content Studio event.

The classes above make it easy to create an event handler. Apart from the code to implement the actual work to be done by the handler, the work needed is restricted to implementing the abstract (MustInherit in Visual Basic) DoWork method as the sample code below shows. Other methods to override and that can be of interest includes the Finish method, which can be used perform additional tasks after a completed operation and to communicate a message back to Content Studio by setting the Status property. There is also a possibility to pass custom data, defined in the Event Actions dialog interface, to the event handler, which can be read and parsed in the ParseCustomData method.

Show code A sample asynchronous event handler

The following sample shows a simple asynchronous event handler that executes whenever a document in a certain category is saved. It uses the optional CustomData to pass in the name of a logon domain that the caller must belong to in order to be allowed to perform some action. The actual action is not shown in this example.

using System;
using ContentStudio.Document;
using ContentStudio.Document.EPT;
using ContentStudio.EventActions.AsynchronousEventHandlers;

namespace MyEventHandlers
{
    public class MyOnDocumentSaveAsyncHandler : DocumentSaveAsyncHandler
    {
        string theStatus;
        
        string allowedDomain;
        
        protected override void ParseCustomData(string customData)
        {
            //get the value of the passed in allowed domain
            allowedDomain = customData == null ? String.Empty : customData;   
        }
                
        protected override void DoWork()
        {
            //Check the permission, only members in a specific domain can execute the 
            //operation.
            if (!CallerLogOnName.StartsWith(allowedDomain, StringComparison.OrdinalIgnoreCase))
            {
                //Create a message that indicates that the operation was a failure
                //but the event handler itself did not fail.
                theStatus = String.Format("Operation was not performed, Permission denied for user '{0}'.", CallerLogOnName);
                return;
            }

            //Do some work here.
            // 
            // 

            //create a success message to return to Content Studio
            theStatus = String.Format("The operation has been performed by '{0}'.", CallerLogOnName);
        }

        protected override void Finish()
        {
            //Tell Content Studio about the outcome of the operation
            Status = theStatus;
        }
        
    }
}
Imports ContentStudio.Document
Imports ContentStudio.Document.EPT
Imports ContentStudio.EventActions.AsynchronousEventHandlers

Namespace MyEventHandlers
    Public Class MyOnDocumentSaveAsyncHandler 
                 Inherits DocumentSaveAsyncHandler
    {
        Dim theStatus As String
        
        Dim allowedDomain As String
        
        Protected Override Sub ParseCustomData(customData As String)
            'get the value of the passed in allowed domain
            If IsNothing(customData) Then
               allowedDomain = String.Empty
            Else
               allowedDomain = customData
            End   
        End Sub
                
        Protected Overrides Sub DoWork()
        {
            'Check the permission, only members in a specific domain can execute the 
            'operation.
            If Not CallerLogOnName.StartsWith(allowedDomain, StringComparison.OrdinalIgnoreCase) Then
                'Create a message that indicates that the operation was a failure
                'but the event handler itself did not fail.
                theStatus = String.Format("Operation was not performed, Permission denied for user '{0}'.", CallerLogOnName)
                Return
            End If

            'Do some work here.
            ' 
            ' 

            'create a success message to return to Content Studio
            theStatus = String.Format("The operation has been performed by '{0}'.", CallerLogOnName)
        End Sub

        Protected Overrides Sub Finish()
            'Tell Content Studio about the outcome of the operation
            Status = theStatus
        End Sub
        
    End Class
End Namespace

Implementing IDisposable

If you need to release some resources in your handler, you should override the Dispose method, free all the resources and eventually call the base class implementation of this method. When Content Studio executes your event handler it makes shure that the you dispose implementation gets called as a part of the execution process.
The code sample is taken from the official MSDN documentation provided by Microsoft.

protected override void Dispose(bool disposing) 
{
   if (disposing) 
   {
      // Release managed resources.
   }
   // Release unmanaged resources.
   // Set large fields to null.
   // Call Dispose on your base class.
   base.Dispose(disposing);
}
Protected Overloads Overrides Sub Dispose(disposing As Boolean) 
   If disposed = False Then
      If disposing Then 
        'Release managed resources.
      End If
   End If
   'Release unmanaged resources.
   'Set large fields to null.
   'Call Dispose on your base class.
   Mybase.Dispose(disposing)
End Sub

Installing and testing your event handler

Compile the code as a class library (.dll file). In Visual Studio this is done automatically for you and outside Visual Studio you can use one of the compilers that is a part of the freely .NET Framework SDK. For more information about the command line compilers see the .NET Framework SDK documentation. You can name your .dll AsynchronousEventHandler.dll, the name of the file is normally also the Assembly name and the assembly name is important when you use the event handler from Content Studio later.
NOTE
You must use at least version 2.0 of the .NET Framework SDK or Visual Studio 2005 or later to be able to build the event handler.

Now, locate the installation directory of the Content Studio Service Manager (CSServMgr.exe) that normally gets installed in the C:\Program files\Teknikhuset\Content Studio 5\CSServer folder. Copy the dll into this directory and you are ready to go! As an alternative you can also install your .dll in the Global Assembly Cache (GAC) so that it can be shared among different applications but in this case there is barely any need for that.

Use the event handler

When you have properly installed your event handler it is time to test it from Content Studio.
Select a category where you would like the event handler to be used. Be default only members of the administrators group has permissions to manage event handlers in Content Studio. This permission can be delegated on the site root level or on a single category but event actions permissions are not inherited to child categories.
On the category of choise, create a new Event handler. This will bring up the "Properties for Event actions" dialog.

Properties for an event actions event
The EventActions properties dialog
  • Name your event handler Perform action after save
  • Set the type to Asynchronous Event handler object
  • Select the OnDocumentSave event
  • In the command textbox enter the custom data, in this case, the name of the allowed logon domain, ex. TheCompany\.
    * This field is not required, in this case we use it to demonstrate the custom data functionality.

The User data button opens up the User Credentials dialog that makes it possible to provide credentials that the event handler receives through the ICSCredentials interface but in this case there is no need for this functionality. Credentials, however, can be useful if you need to communicate with an external system and could contain login credentials for that system.

The ProgID field specifies the programmatic name of your event handler and can be written in serveral ways where the simplest form is

Namespace.Class, Assembly name

The complete format looks like:

Namespace.Class, Assembly name, Version=value, Culture=value, PublicKeyToken=value
Examples
The first example is as simple as it gets where only the full name and the assembly name are given. The assembly name is normally the same as the name of the dll file minus the .dll extension.
MyEventHandlers.MyOnDocumentSaveAsyncHandler, MyEventHandlers
The second example is more specific an specifies that the event handler has been given a strong name and we also needs a specific version of the assembly to be loaded with a specific culture. This advanced type info is needed when your event handler assembly has been registered in the global assembly cache (GAC).
MyEventHandlers.MyOnDocumentSaveAsyncHandler, MyEventHandlers, Culture=neutral, Version=1.0.0.0, PublicKeyToken=absc6d462bd33a40

Now, when you save a document located in the category where the event action is defined the Service Manager will execute the code in your event handler and log the result of the operation in the Content Studio Event log. Additionally you should also be able to see the event action and whether it has been executed or not in the JobQueue dialog, that you can reach from the Category property dialog:

The Job queue button
The Job queue button

Hit that button and it will bring up the Job queue dialog which lets you examine and analyze the content of the event actions queue.

The Job queue dialog
The Job queue dialog

To see the actual outcome of the event handler you should examine the Content Studio event log which can be reached from the site properties dialog and here you will be able to see the message generated by the event handler.

The Content Studio event log
The Content Studio event log

Debugging the event handler

The simples way of debugging the event handler "in action" is when you have Visual Studio and Content Studio installed on the same machine. The only thing you need to do in order to be able to set breakpoints and step through the code of the event handler is to attach the debugger to the CSServMan.exe process. When that has succeeded and the Service Manager executes your event handler the execution will stop at your breakpoint and you will be able to analyze your code. Whenever you make a change to your code you must update the .dll file where you installed it. This will probably not succeed until you have restarted the Content Studio Service Manager service.


See Also

Creating a synchronous event handler