By Mats Halfvares, the Content Studio development team

Introduction

Starting with Content Studio version 5.2, a powerful subscription service is included with the product. The service is used to send messages to subscribers when a document is approved or when a document is forced to be sent out. By default, the source of the message to be sent out is a Content Studio document and the subscribers are stored in the new Subscriber repository in Content Studio. With the current implementation, the only supported messagetype is mail messages in Html format generated from the document that triggered the sending operation.

Sometimes the default implementation is not sufficient or does not meet the requirements from your customers. For example you might want to ...

  • get the subscribers from the Active Directory or a custom database.
  • apply special formatting to the messages based on each individual subscriber.
  • filter out the receiver based on som data in the document (i.e. an Ept-field)
  • provide detailed logging to a location of your choice
  • insert extended information in the message based on some personal data of the subscriber

As you can see, the list can get as long as you like. This article describes how to write a subscription handler from scratch or, preferably, a subscription handler that inherits one of the base classes provided in Content Studio. These base classes makes the task of creating a subscription handler far more easy that writing the handler from scratch.

Subscription Services under the hood

When a category is set up for subscription, its newly created and published documents are ready to be sent out to subscribers. For more information on how to configure and set up a category for subscription see the Subscription Services section in the Technical Overview documentation. When a document that is subject to subscription is published for the first time the event OnDocumentPublish is detected and triggered by DEPRO (Dynamic Event Processor, a part of the Content Studio Service Manager). DEPRO then queries Content Studio to see if the document's category happends to be enabled for subscription. When this is true DEPRO extracts all the relevant subscription data from the category subscription definition and creates a new OnDocumentSubscription Event Actions scheduled to be executed at the next point in time defined in the schedule set up on the category subscription definition. When this point in time has occured, Content Studio Service Manager finds the job and eventually calls the specified subscription event handler that performs the actual distribution of the message to its subscribers.

Subscription Services, technical overview
Subscription Services, technical overview

As mentioned above, the implementation provided with Content Studio creates a mail message based on the content of the document that triggered the subscription event and can only send messages to subscribers in the built in repository. However, this implementation is intended to satisfy the need for a vast majority of web sites and developers but in rare cases other needs exist. In this case you must create and provide your own event handler implementation to Content Studio Service Manager.

Extending the built in handlers

As mentioned earlier in this article, a Content Studio Subscription handler is a specialized form of an asynchronous event handler that gets executed in the background by the Service Manager whenever a subscription job has been submitted. Every Content Studio asynchronous event handler implements the ICSAsyncEventHandler interface and all of the work is done in its EventHandler method. The data passed in must be parsed, analyzed and type checked so in order to make this job far more convenient an easier for the developer Content Studio provides a specialized library that contains a set of abstract base classes the you can extend in order to write your functionality.
This library can be found in the CSSubscriptionEventHandler assembly that gets installed in the program files folder of Content Studio (by default C:\Program files\Teknikhuset\Content Studio 5\CSServer).

Launch Visual Studio 2005 or later and create a new class library project and set a reference to both the CSSubscriptionEventHandler.dll and CS5Interfaces.dll assemblies. CS5Interfaces.dll is installed in the GAC so you should be able to find it there or among the Content Studio program files.

Depending on the functionality to implement you can choose to derive from one of the three base classes provided by this library. These classes are:

SubscriptionEventHandlerBase
This is the base class that all subscription event handler derive from. It implements the ICSAsyncEventHandler and IDisposable interfaces. This class parses and validates the passed in data from Content Studio and defines a number of methods that you should or must override.
MailSubscriptionHandler
This class inherits from SubscriptionEventHandlerBase and adds mail sending capabilities. This class implements the CreateMessage method where it creates an Html mail message based on the Content Studio document that triggered the event.
CSMailSubscriptionHandler
Provides a working implementation of a Content Studio Subscription Mail message event handler. This class generates inherits the MailSubscriptionHandler and adds functionality that to send out mails to subscribers stored Content Studio. CSMailSubscriptionHandler can handle both the OnDocumentSubscription and adds functionality that sends out mails to subscribers stored Content Studio. CSMailSubscriptionHandler data passed in to the job queue in Content Studio that and OnTestDocumentSubscription events. In the former case all subscribers that receives mails are read from the Content Studio Subscription definition repository on the actual category. In the latter case a singel subscriber is read from the data passed in to the job queue in Content Studio that defined the event handled.
This is the implementation that Content Studio uses by default when i sends out Subscription mail messages.
When you would like to... Class to extend Comments
Send messages that are not email SubscriptionEventHandlerBase
Send mails to a subscribers not stored in Content Studio MailSubscriptionHandler By default the mail message is constructed from the passed in document and the mail message has both a Html and text part. Override the ReadData method and return your own subscriber data.
Send mails to subscribers in Content Studio, but you need full control over how the message is created. CSMailSubscriptionHandler Override the CreateMessage method to take control over the message creation
Send mails to subscribers in Content Studio, but you need full control over to whom the message is sent. CSMailSubscriptionHandler Override the ValidateSubscriber method. Examine the subscriber and return true if she should receive the message and false otherwise.

During the life time of a handler there are a number of metods that gets called and events that fires, all which can be used by a developer to provide her own functionality.

ValidateEvent()
Derived classes override this method to specify which type of event can be handled by the handler.
CreateLogger()
Creates the log specified by the ISysLogWriter member of the extended properties passed in. Normally there is no reason to override this method.
Initialized
Subscribe to this event if you like to do your own initialization. This event is raised when the base class has fully parsed and validated the passed in xml data. This is the correct moment to use if there is a need to do some additional initialization of your own handler.
CreateMessage()
In this abstract (MustInherit in Visual Basic) method the message to send is created. Deriving classes creates a message to send in this method. Typically this message is a Content Studio document but developers can create any message to by overriding this method.
ReadData
This method reads a list of subscribers that should receive a message. The data is read in pages and gets called until no more data is returned.
ValidateSubscriber(SubscriptionInformation)
This method gets called for every subscriber returned by ReadData(Int32). Developers override this method whenever there is a need to apply custom validating for each found subscriber.
ValidateMessage(SubscriptionInformation)
This method is called for every individual copy of the message to be sent out. In this method implementations can process the message individually for each subscriber returned by ReadData(Int32).
SetMailMessageContent(String, String)
This metod is available for classes that inherit the MailSubscriptionHandler class and lets the developer set a new mail message content to the message already created. Typically this method is overridden when there is a need to do your own replacement or formatting operations to the mail message content sent out. This metod is called once for each individual copy of the message to be sent out
SendMessage(SubscriptionInformation)
When the subscriber and her message is validated, the message will be sent out by this method. Derived class implements their own send operation be it a regular mail message or some other message system, such as SMS or fax. For classes that inherit the MailSubscriptionHandler or its derived classes, there is little use of overriding this method since they already provides a full implementation of the mail sending process.
Finished
This event is fired at the end when the send operation has finshed successfully. Typically you will use this event to set the status message returned to the Content Studio Service Manager.

In addition to these action methods every implementation can call one of the meta data methods to provide the handler with information.

WriteToLog(SyslogPriority, String)
A developer can call this method to write her own messages to the log. If no logger has been specified, this method does nothing.
Internally this method checks if any logger exists and is writeable before it calls into the WriteMessage(SyslogPriority, String) method of the provided ISysLogWriter interface implementation.
SetStatus
Sets the status text that is sent back to the Service Manager after that the event handler has finished. This text is passed to the Content Studio Service Manager and logged as a part of the success message, for example, this might be the number of messages actually sent out and the number of receivers that failed. You should call this method in the Finished event that is raised just after that the message has been sent to all subscribers.
Do not use this method to pass error messages, for more information on how to handle exceptions, see the Exception handling section in this article.

Show code A sample handler

This section describes a custom subscription handler that sends mails to all enabled users that belong to a specific group in a directory server such as Active Directory.

Since handler uses the Content Studio document that triggered the event as a source of the mail the main work will take place in the overidden ReadData(System.Int32) method.
The implementation of this method requires that we supply three dynamic properties that contains the data needed to find the specific group in the Directory. Additionally this sample requires .NET Framework 3.5 and a reference to the assembly System.DirectoryServices.AccountManagement in order to build.

  • myCorp.cs.eventHandlers.directoryMailHandler.ldapPath
    specifies the ldap path (not including the schema part i.e. LDAP://).
    Ex. OU=Personel,OU=Management,DC=mycorp,DC=com
  • myCorp.cs.eventHandlers.directoryMailHandler.server
    specifies the server that manages the directory.
    For Active Directory this can be the DNS name of the directory ex. mycorp.com or the name of a Domain Controller.
  • myCorp.cs.eventHandlers.directoryMailHandler.groupName
    specifies the name of the group ex. Managers

For more information on dynamic properties in the Subscription Services see the Subscription services section in the Technical overview.

In addition to this the handler overrides a number of other methods mainly for the reason to add logging functionality.

using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using ContentStudio.ServiceManager.Logging;

namespace MyCorp.CS.EventHandlers
{
    public class DirectoryMailHandler : MailSubscriptionHandler
    {
        
        public DirectoryMailHandler()
            : base(SubscriptionToHandle.Newsletter)
        {
            //Add a subscription on the Finished event
            this.Finished += new FinishedEventHandler(OnFinished);
        }

        private void OnFinished(object sender, EventArgs e)
        {
            WriteToLog(SyslogPriority.Informational, "The mail sending operation has finished.");
            string message = String.Format("OK: {0} mail(s) sent. {1} mail(s) could not be sent.", SendCount, FailedCount);
            WriteToLog(SyslogPriority.Informational, message);
            SetStatus(message);
            WriteToLog(SyslogPriority.Informational, "Job finished successfully");
        }

        protected override IList<SubscriptionInformation> ReadData(int dataIndex)
        {

            List<SubscriptionInformation> theList = new List<SubscriptionInformation>();
            //we do not use paging here, so just return an empty list 
            //if data index is anything but zero.
            if(dataIndex != 0)
                return theList;

            //the OnTestDocumentSubscription event only sends mail to the supplied receiver
            EventActions.ContentStudioEvents ev = (EventActions.ContentStudioEvents)EventId;
            if (ev == ContentStudio.EventActions.ContentStudioEvents.OnTestDocumentSubscription)
            {
               SubscriptionInformation info =
                    SubscriptionInformation.Create(Guid.Empty,
                                                   Properties.Receiver,
                                                   base.SubscriptionTypeHandled,
                                                   SubscriptionInformation.TypeOfAddress.Mail,
                                                   String.Empty,
                                                   true);
               theList.Add(info);
               return theList;
            }



            //code to read from a specific OU in a directory
            //this assumes that you a set of custom dynamic properties
            string ldap = ExtendedProperties["myCorp.cs.eventHandlers.directoryMailHandler.ldapPath"];
            string server = ExtendedProperties["myCorp.cs.eventHandlers.directoryMailHandler.server"];
            string groupName = ExtendedProperties["myCorp.cs.eventHandlers.directoryMailHandler.groupName"];
            //check the passed in data
            if (String.IsNullOrEmpty(ldap))
                throw new ArgumentException("Extended property \"myCorp.cs.eventHandlers.directoryMailHandler.ldapPath\" must be defined.");
            if (String.IsNullOrEmpty(server))
                throw new ArgumentException("Extended property \"myCorp.cs.eventHandlers.directoryMailHandler.server\" must be defined.");
            if (String.IsNullOrEmpty(groupName))
                throw new ArgumentException("Extended property \"myCorp.cs.eventHandlers.directoryMailHandler.groupName\" must be defined.");


            using (PrincipalContext priCx = new PrincipalContext(ContextType.Domain, server, ldap))
            {
                PrincipalSearcher priS = new PrincipalSearcher();
                using (GroupPrincipal theGroup = new GroupPrincipal(priCx))
                {
                    //if the group does not exist, an ArgumentException will be thrown
                    theGroup.Name = groupName;
                    priS.QueryFilter = theGroup;
                    using (GroupPrincipal requestedGroup = priS.FindOne() as GroupPrincipal)
                    {
                        if (requestedGroup == null)
                            throw new ArgumentException(String.Format("Group {0} not found.", theGroup.Name));
                        //go through all groups to find all members
                        foreach (Principal pu in requestedGroup.Members)
                        {
                            using (UserPrincipal user = pu as UserPrincipal)
                            {
                                if (user != null && user.HasValue && user.Enabled.Value)
                                {
                                    SubscriptionInformation sui = 
                                        SubscriptionInformation.Create(Guid.Empty,
                                                                       user.EmailAddress,
                                                                       base.SubscriptionTypeHandled,
                                                                       SubscriptionInformation.TypeOfAddress.Mail,
                                                                       user.Name,
                                                                       true);
                                    theList.Add(sui);
                                }
                            }
                        }
                    }
                }
            }
            return theList;
        }

        protected override void CreateMessage()
        {
            base.CreateMessage();
            WriteToLog(SyslogPriority.Informational, "A message to send has been created.");
            WriteToLog(SyslogPriority.Informational, "Starting the mail sending operation.");
        }

        protected override bool SendMessage(SubscriptionInformation subscriber)
        {
            if (base.SendMessage(subscriber))
            {
                WriteToLog(SyslogPriority.Informational, String.Format("Mail sent to {0}.", subscriber));
                return true;
            }
            WriteToLog(SyslogPriority.Warning, String.Format("Mail could not be sent to {0}.", subscriber));
            return false;
        }
    }
}

The IDisposable interface

Since a subscription event handler probably will use resources such as file handles, database connections or mail messages your event handler should implement the IDisposable interface. That implementation must ensure that all resources are closed in order to free system memory. When implemented, Content Studio Service Manager that calls your implementation of the ICSAsyncEventHandler.EventHandler method, will ensure that your IDisposable implementation gets called when the job is finished.
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
 

Creating an event handler from scratch

Implementing an event handler from scratch needs far more work and is much more difficult than extending one of the existing base classes. For this reason there is very little reason to do so unless you need complete control over the event handler. This process is documented here mainly for the reason to provide you with information on how a subscription event handler works internally.

When you implement an event handler from scratch you must provide you own implementation of the ICSAsyncEventHandler interface. this interface has one single method ICSAsyncEventHandler.EventHandler , that acts as the entry point of the event handler. The data needed is passed in as xml to the event handler via the eventXmlArguments and customData. The eventXmlArguments contains standard information about the document that triggered the OnDocumentSubscription event. You must parse this Xml and extract the values you need in order to sent out the message. For more information on this Xml see the ICSAsyncEventHandler.EventHandler method documentation. In addition to the standard Xml you also find the following set of properties in this Xml.

Additional properties for the eventXmlArguments parameter
property name value
receiver The receiver of the message, only available for OnTestDocumentSubscription events
subscriptiondefinitionid A guid that identifies the subscription definition. You can use this value if you need to open up the definition for the document's category.
baseurl The url to use if the event handler need to browse to the document
sender The sender of the message
presentationtemplate If specified, the numeric indentifier of a presentaion template to use. Only used with Ept documents.
server The name or the IP-address of the message server.
authenticationSchema A value that indicates the authentication scheme to use. This value is one of the members if the AuthenticationSchemes enumeration.

The customData parameter will contain all of the extended properties in one Xml document with the following format.

<properties>
    <property name="myProperty" value="property value" />
    <!-- more properties can follow -->
</properties>

For more information on the extended properties, see the Subscription Services section in the Technical Overview documentation.

When the arguments have been parsed you should create a message to send to your subscribers. A message can preferably be created from the content of the document passed in and be formatted according to your needs or combined with some external data of your choice. The format of your message is of course dependent on the type of message to send (ex. text mails, html mails or SMS messages) and the capability of the client that the user will use to read the message. For example mobile devices has very limited capabilities when it comes to reading Html mails - only a basic set of Html elements usually are supported and often CSS-formatting will not work.

The next step to implement is to get a list of subscribers to send the message to. You can read this list from the Content Studio repository by using the SubscriptionDefinition class and the Subscriptions property which returns a SubscriptionCollection that can enumerate through the collection of subscribers on the document's category.
You can obtain the list of subscribers from any repository available such as the Active Directory, an Microsoft Excel document or a plain text file.

You now should send the message, through the message server, to each one of the subscribers found. Before sending the message you might like to modify the message to make it more personal etc.

When the send operation is completed you can return a success message back to the Service Manager via the statusText output parameter. This status message should not be an error message - just some additional information back to the Service Manager such as the number of messages sent or so. All fatal exceptions should be thrown so that they can be catched by Service Manager and logged as errors in the Content Studio Event log.

 

Exception handling

The statusText output parameter of the ICSAsyncEventHandler.EventHandler method passes messages back to the Content Studio Service Manager after that the Event Handler has finished its execution. This message should not be error messages unless they are harmless and do not prevent the sending operation from beeing successful. A typical message could contain the number of successfully sent messages along with the number of failed recipients found during the send operation.

Whenever a critical condition occurs you should throw the exception or re-throw the exception so it can be catched by the Service Manager. Any unhandled exception in your event handler will be caught by the Service Manager and logged as failures in the Content Studio log. If you do catch and handle critical errors in you event handler and just exit the sending operation at that point and use the statusText parameter to pass the error information back to the Service Manager the job will be logged as a success, something that we do not want.

 

Installing and activating an Event handler

When you have created and fully debugged an assembly that implements your event handler you must provide information on how to create it and where to find the assembly. Since your implementation is called directly by the Content Studio Service Manager you just need to place your assembly in the same directory where the Service Manager (CSServMgr.exe) is installed. By default this is Program Files\Teknikhuset\Content Studio 5\CSServer. If your event handler inherit from one of the base classes provided you should specify the AssemblyQualifiedName of your assembly in the Custom Event handler field in the subscription properties tab of the category properties dialog in the Content Studio administrative interface. For more information on how to configure and set up a category for subscription, see the Subscription Services section in the Technical Overview documentation. If the AssemblyQualifiedName is correct and the Service Manager can find your assembly, the event handler will call your event handler when the OnDocumentSubscription event is handled. The format of AssemblyQualifiedNames has been thoroughly documented by Microsoft at this location AssemblyQualifiedName.

 

Creating a custom log writer

Often there is a need to supply background processes with some sort of logging functionality and the easiest way of doing this is to create an assembly that implements the ISysLogWriter interface defined in the CS5Interfaces.dll assembly installed with Content Studio 5.2 and later. Even though you can implement a log in any way you like, the usage of this interface makes things much easier to you.

The ISysLogWriter interface

The ISysLogWriter interface is designed to comply with the standard syslog specification and protocol. For more information on the syslog specification see http://en.wikipedia.org/wiki/Syslog.

The ISysLogWriter interface has two properties and two methods that you must implement.

bool CanWrite
{
    get;
}

Your implementation of the read-only property CanWrite should return false by default, only if your log writer works correct and is prepared to receive log message this propert should return true.

SyslogFacility Facility
{
    get;
}

Facility is one of the custom facilities in the standardized Syslog specification and is used to distinguish different log sources from each other. This property should be initialized in the Open method.

void Open(SyslogFacility facility,
	      IDictionary<string, string> properties,
	      IFormatProvider format
         )

When you inherit one of the provided base classes, the Open method is called once when the log writer have been created. Your implementation should do all initialization tasks in this method and, provided that all went well, your implementation of the CanWrite property now must return true. In the properties argument you will receive all the extended properties you specified in the Dynamic property editor dialog available in the subscription tab of the category properties dialog interface. For more information see the Subscription Services section in the Technical Overview documentation. Please, feel free to specify any property you need your users to provide among these properties in order to have your Log Writer initalized.

The first argument, facility, is used when you need to distinguish different log sources from each other and Content Studio uses the value SyslogFacility.Local0. If you would like another value you can pass another facility value in one of the properties or if you implement your own event handler you just override the CreateLogger() method, provided that you inherit from the SubscriptionEventHandlerBase class or one of its descendant classes. .
If the initialization fails for some reason but you do not want to interrupt the message sending operation, make sure that the CanWrite property returns false.

void WriteMessage(SyslogPriority priority,
	              string message
                 )

When you inherit one of the provided base classes, the WriteMessage method gets called every time the event handler writes to the log via a call to its WriteToLog(SyslogPriority, String) method. The provided implementation does not attempt to write to the log if the CanWrite property returns false. A typical implementation of this method writes a new record with supplied priority and message.

IDiposable interface

Since a log writer most likely will use resources that need to be closed too free system memory (ex. database connection or file handles) every LogWriter should provide an implementation of the IDisposable interface where the resources are freed. If you are unsure on how to implement this interface please make sure to read the official documentation from Microsoft for the IDisposable interface. Provided that you inherit from one of the base classes provided, or your event handler implements IDisposable Content Studio Service Manager will ensure that your implementation gets called when the event handler has finished its job.

Show code A sample log writer

The sample below shows a simple log writer that writes messages to a file in the file system. This is the implementation included in Content Studio.

using System.IO;
using System;
using System.Globalization;
using System.Collections.Generic;
using ContentStudio.ServiceManager.Logging;
namespace MyNamespace.LogWriter
{
    public class FileLogger : ISysLogWriter
    {
        StreamWriter writer;
        string fileName;
        SyslogFacility facility = SyslogFacility.Local0;
        int row;
        IFormatProvider format;

        /// <summary>
        /// Initializes a new instance of the <see cref="FileLogger"/> class.
        /// </summary>
        public FileLogger()
        {

        }

        /// <summary>
        /// Opens the file.
        /// </summary>
        /// <param name="directory">The directory.</param>
        /// <param name="fileName">Name of the file.</param>
        private void OpenFile(string directory, string fileName) 
        {
            if (String.IsNullOrEmpty(directory))
                directory = Path.GetTempPath(); // @"C:\Temp";

            //the directory must exist
            if(!Directory.Exists(directory))
                throw new ArgumentException(String.Format("The directory \"{0}\" does not exist.", directory), "directory");

            if(String.IsNullOrEmpty(fileName))
                fileName = GenerateFileName();
            //
            if(!IsValidFileName(fileName))
                throw new ArgumentException(String.Format("The file name \"{0}\" is not valid.", fileName), "fileName");

            this.fileName = Path.Combine(directory, fileName);
        }

        private bool IsValidFileName(string name)
        {
            foreach(char c in Path.GetInvalidPathChars())
            {
                if(name.IndexOf(c) > -1)
                    return false;
            }
            return true;
        }

        private string GenerateFileName()
        {
            DateTime now = DateTime.Now;
            return (String.Format("CSFL-{0}${1}.{2}", now.ToString("yyMMddHHmmss"), now.ToString("FFF") , "log"));
        }

        #region ISysLogWriter Members

        /// <summary>
        /// Gets a value indicating whether this instance can write to its log.
        /// </summary>
        /// <value><c>true</c> if this instance can write; otherwise, <c>false</c>.</value>
        /// <remarks>
        /// Implementation can use this value to indicate to the logging application whether or not
        /// the log system is working. Caller should call this property to ensure that the
        /// logging implementation is working properly.
        /// </remarks>
        public bool CanWrite
        {
            get 
            { 
                if(writer == null)
                    return false;
                return true;
            }
        }

        /// <summary>
        /// Gets the syslog facility (source) in use.
        /// </summary>
        /// <value>The facility.</value>
        public SyslogFacility Facility
        {
            get { return facility; }
        }

        /// <summary>
        /// Opens the log with a specific facility, a formatter and prepares the log for writing.
        /// </summary>
        /// <param name="facility">The facility to use.</param>
        /// <param name="properties">The properties passed in.</param>
        /// <param name="format">The format.</param>
        public void Open(SyslogFacility facility, IDictionary<string, string> properties, IFormatProvider format)
        {
            if (!Enum.IsDefined(typeof(SyslogFacility), facility))
                throw new ArgumentOutOfRangeException("facility");

            if (disposed)
                throw new ObjectDisposedException("ISysLogWriter");

            if (writer == null)
            {
                //Set the directory and file name to write to.
                if (properties == null)
                    properties = new Dictionary<string, string>();
                string directory;
                string filename;
                properties.TryGetValue("ContentStudio.ServiceManager.Logging.FileLogger.Directory", out directory);
                properties.TryGetValue("ContentStudio.ServiceManager.Logging.FileLogger.FileName", out filename);
                OpenFile(directory, filename);

                try
                {
                    writer = new StreamWriter(this.fileName, true, System.Text.Encoding.UTF8);
                    //Write a blank line to separated jobs from each other
                    writer.WriteLine();
                }
                catch (IOException) 
                { 
                }
                if (format != null)
                    format = CultureInfo.CurrentCulture;
                this.format = format;
            }
            else
                throw new InvalidOperationException("Logger already open");
        }

        /// <summary>
        /// Writes a message to the syslog.
        /// </summary>
        /// <param name="priority">The priority.</param>
        /// <param name="message">The message.</param>
        public void WriteMessage(SyslogPriority priority, string message)
        {
            if(!CanWrite)
                throw new InvalidProgramException("Logger not open");
            
            row++;
            string m = String.Format("{0}\t{1}\t{2}\t{3}", 
                                     row.ToString(format), 
                                     DateTime.Now.ToString(format), 
                                     priority, 
                                     message.ToString(format));

            writer.WriteLine(m);
            writer.Flush();
        }

        #endregion

        #region IDisposable Members
        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="FileLogger"/> is reclaimed by garbage collection.
        /// </summary>
        ~FileLogger()
        {
            Dispose(false);
        }
       private bool disposed;
        /// <summary>
        /// Performs application-defined tasks associated with freeing
        /// , releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and  
        /// unmanaged resources; <c>false</c> to release only unmanaged resources.
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    if(writer != null)
                        writer.Dispose();
                }
                // Note disposing has been done.
                disposed = true;
                writer = null;
            }
        }


        #endregion
    }         
}
 

Installing and enabling your log writer

When you have created and fully debugged an assembly that implements the ISysLogWriter interface you must provide information on how to create it and where to find the assembly. Since your implementation is called directly by the Content Studio Service Manager you just need to place your assembly in the same directory where the Service Manager (CSServMgr.exe) is installed. By default this is Program Files\Teknikhuset\Content Studio 5\CSServer. If your event handler inherit from one of the base classes provided you should specify the AssemblyQualifiedName of your assembly as the value of the ContentStudio.ServiceManager.Logging.ISysLogWriter extended property. Extended properties can be specified in the subscription properties tab of the category properties dialog in the Content Studio administrative interface. For more information on how to configure and set up a category for subscription see the Subscription Services section in the Technical Overview documentation. If the AssemblyQualifiedName is correct and the Service Manager can find your implementation the event handler will write to your log write whenever needed. The format of AssemblyQualifiedNames has been thoroughly documented by Microsoft at this location AssemblyQualifiedName.