Click here to Skip to main content
15,885,213 members
Articles / Desktop Programming / Windows Forms

Windows Services Made Simple

Rate me:
Please Sign up or sign in to vote.
4.62/5 (10 votes)
27 Jun 2007CPOL10 min read 94.3K   6.9K   69  
Describes how to build a Windows Service using the Pegasus Library.
using System;
using System.Collections.Generic;
using System.IO;

using Pegasus.Diagnostics;
using Pegasus.Log4Net;
using Pegasus.Runtime.Serialization.Formatters.Xml;

namespace Pegasus.Workflow.Service.FileServices
{
	/// <summary>
	/// Uses the file system to persist the workflow context objects
	/// </summary>
	public class FilePersistenceService : WorkflowPersistenceService, IDisposable
	{
		// Local Instance Values
		private ILog m_log = LogManager.GetLogger( typeof( FilePersistenceService ) );

		private object m_lock = new object();

		private string m_directory;
		private Dictionary<int, WorkflowContext> m_contextTable = new Dictionary<int, WorkflowContext>();

		// Local Const Values 
		private const string ContextFileLocked = "_FilePersistenceService.FileLocked";

		/// <summary>
		/// Initializes a new instance of the <see cref="T:FilePersistenceService"/> class.
		/// </summary>
		/// <param name="directory">The directory.</param>
		public FilePersistenceService( string directory )
		{
			m_log.DebugFormat( "FilePersistenceService( directory = {0} )", directory );

			// Check Parameters
			ParamCode.AssertNotEmpty( directory, "directory" );
			ParamCode.Assert( Directory.Exists( directory ), "directory", "The directory {0} does not exist or is inaccessable", directory );

			m_directory = directory;
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			m_log.Debug( "FilePersistenceService:Dispose()" );
			m_contextTable.Clear();
		}

		/// <summary>
		/// Starts this instance of the service.
		/// </summary>
		/// <param name="workflowService"></param>
		public override void Start( WorkflowService workflowService )
		{
			m_log.DebugFormat( "FilePersistenceService:Start( workflowService = {0} )", workflowService );

			lock( m_lock )
			{
				base.Start( workflowService );
				m_contextTable.Clear();
			}
		}

		/// <summary>
		/// Stops this instance of the service.
		/// </summary>
		public override void Stop()
		{
			m_log.DebugFormat( "FilePersistenceService:Stop()" );

			lock( m_lock )
			{
				base.Stop();
				Dispose();
			}
		}

		/// <summary>
		/// Registers a new workflow context with the persistence service.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <returns></returns>
		protected override int OnRegisterNewWorkflowContext( WorkflowContext context )
		{
			m_log.DebugFormat( "FilePersistenceService:OnRegisterNewWorkflowContext( context = {0} )", context );

			// Check Parameters
			ParamCode.AssertNotNull( context, "context" );

			lock( m_lock )
			{
				// Find a new workflowId/filename for the workflow
				int workflowId = GetNextWorkflowId();

				// Open/Create the file so it exist on the disk
				using( FileStream fileStream = OpenWorkflowFile( workflowId, FileMode.Create ) )
				{
					context[ ContextFileLocked ] = true;
				}

				// Set the new workflow context
				InitializeNewContext( context, workflowId );
				m_contextTable[ workflowId ] = context;

				return workflowId;
			}
		}


		/// <summary>
		/// Saves the workflow context.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <param name="unlock">if set to <c>true</c> [unlock].</param>
		protected override void OnSaveWorkflowContext( WorkflowContext context, bool unlock )
		{
			m_log.DebugFormat( "FilePersistenceService:OnSaveWorkflowContext( context = {0}, unlock = {1} )", context, unlock );

			// Check Parameters
			ParamCode.AssertNotNull( context, "context" );

			lock( m_lock )
			{
				// Is the context locked so that we can write the file out
				bool fileLocked = false;
				if( context.TryGetValue<bool>( ContextFileLocked, out fileLocked ) )
				{
					if( !fileLocked )
					{
						throw new WorkflowLockedException( context.WorkflowId, "Can not save the workflow because the context is not locked." );
					}

					if( context.IsReadOnly )
					{
						throw new WorkflowLockedException( context.WorkflowId, "Can not save the workflow because the context is read-only." );
					}

					// Open the file and write out the data
					try
					{
						using( FileStream fileStream = OpenWorkflowFile( context.WorkflowId, FileMode.Create ) )
						{
							// Write out the context object
							new XmlFormatter2().Serialize( fileStream, context );
						}
					}
					catch( Exception e )
					{
						m_log.Error( "FilePersistenceService:OnSaveWorkflowContext: Exception", e );
						throw;
					}
					finally
					{
						if( unlock )
						{
							OnReleaseWorkflowContext( context );
						}
					}
				}
				else
				{
					throw new WorkflowLockedException( context.WorkflowId, "Can not save the workflow because the context is not locked (missing lock value)." );
				}
			}
		}

		/// <summary>
		/// Loads the workflow context.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="readOnly">if set to <c>true</c> [read only].</param>
		/// <returns></returns>
		protected override WorkflowContext OnLoadWorkflowContext( int workflowId, bool readOnly )
		{
			m_log.DebugFormat( "FilePersistenceService:OnLoadWorkflowContext( workflowId = {0}, readOnly = {1} )", workflowId, readOnly );

			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );

			lock( m_lock )
			{
				if( readOnly )
				{
					return LoadReadOnlyWorkflowContext( workflowId );
				}

				return LockAndLoadWorkflowContext( workflowId );
			}
		}

		/// <summary>
		/// Releases the workflow context.
		/// </summary>
		/// <param name="context">The context.</param>
		protected override void OnReleaseWorkflowContext( WorkflowContext context )
		{
			m_log.DebugFormat( "FilePersistenceService:OnReleaseWorkflowContext( context = {0} )", context );

			// Check Parameter
			ParamCode.AssertNotNull( context, "context" );

			lock( m_lock )
			{
				// Set the lock value to false
				context[ ContextFileLocked ] = false;

				// Release the context and remove it from our table.
				SetContextAsReadOnly( context );
				m_contextTable.Remove( context.WorkflowId );
			}
		}

		/// <summary>
		/// Locks the and load workflow context.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <returns></returns>
		private WorkflowContext LockAndLoadWorkflowContext( int workflowId )
		{
			m_log.DebugFormat( "FilePersistenceService:LockAndLoadWorkflowContext( workflowId = {0} )", workflowId );

			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );

			// If the file already loaded return it
			if( m_contextTable.ContainsKey( workflowId ) )
			{
				return m_contextTable[ workflowId ];
			}

			WorkflowContext context = null;
			using( FileStream fileStream = OpenWorkflowFile( workflowId, FileMode.Open ) )
			{
				context = ReadInWorkflowContext( fileStream );
				context[ ContextFileLocked ] = true;
			}

			InitializeExistingContext( workflowId, context, false );
			m_contextTable[ workflowId ] = context;

			return context;
		}

		/// <summary>
		/// Loads the read only workflow context.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <returns></returns>
		private WorkflowContext LoadReadOnlyWorkflowContext( int workflowId )
		{
			m_log.DebugFormat( "FilePersistenceService:LoadReadOnlyWorkflowContext( workflowId = {0} )", workflowId );

			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );

			using( FileStream fileStream = OpenWorkflowFile( workflowId, FileMode.Open ) )
			{
				WorkflowContext context = ReadInWorkflowContext( fileStream );
				InitializeExistingContext( workflowId, context, true );
				return context;
			}
		}

		/// <summary>
		/// Opens the filename.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="mode">The open mode for the file.</param>
		/// <returns></returns>
		private FileStream OpenWorkflowFile( int workflowId, FileMode mode )
		{
			m_log.DebugFormat( "FilePersistenceService:OpenWorkflowFile( workflowId = {0}, mode = {1} )", workflowId, mode );

			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );

			FileStream fileStream = null;
			try
			{
				// Open the file
				fileStream = File.Open( GetFilenameFromWorkflowId( workflowId ), mode, FileAccess.ReadWrite );
			}
			catch( FileNotFoundException e )
			{
				m_log.Error( "FilePersistenceService:OpenWorkflowFile: File not found exception.", e );
				throw new WorkflowNotFoundException( workflowId, e );
			}
			catch( Exception e )
			{
				m_log.Error( "FilePersistenceService:OpenWorkflowFile: Exception.", e );
				throw new WorkflowException( e, "Unable to open workflow {0}", workflowId );
			}

			return fileStream;
		}

		/// <summary>
		/// Reads the in workflow context.
		/// </summary>
		/// <param name="fileStream">The file stream.</param>
		/// <returns></returns>
		private WorkflowContext ReadInWorkflowContext( FileStream fileStream )
		{
			m_log.DebugFormat( "FilePersistenceService:ReadInWorkflowContext( fileStream = {0} )", fileStream );

			try
			{
				// Read in the context object.
				return (WorkflowContext) new XmlFormatter2().Deserialize( fileStream );
			}
			catch( Exception e )
			{
				m_log.Error( "FilePersistenceService:ReadInWorkflowContext: Exception.", e );
				throw new WorkflowException( e, "Unable to deserialize the workflow context" );
			}
		}

		/// <summary>
		/// Gets the filename from workflow id.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <returns></returns>
		private string GetFilenameFromWorkflowId( int workflowId )
		{
			m_log.DebugFormat( "FilePersistenceService:GetFilenameFromWorkflowId( workflowId = {0} )", workflowId );

			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );

			return Path.Combine( m_directory, string.Format( "Workflow_{0}.xml", workflowId ) );
		}

		/// <summary>
		/// Gets the workflow next workflow id.
		/// </summary>
		/// <returns></returns>
		private int GetNextWorkflowId()
		{
			m_log.Debug( "FilePersistenceService:GetNextWorkflowId()" );

			int workflowId = 1;

			string infoFilename = Path.Combine( m_directory, "FilePersistenceServiceInfo.xml" );

			// If the files does not exist then create the default
			if( File.Exists( infoFilename ) )
			{
				using( FileStream input = File.Open( infoFilename, FileMode.Open, FileAccess.Read, FileShare.None ) )
				{
					workflowId = (int) new XmlFormatter2().Deserialize( input );
				}

				workflowId++;
			}

			// Find a new workflowId/filename for the workflow
			string filename = GetFilenameFromWorkflowId( workflowId );
			while( File.Exists( filename ) )
			{
				workflowId++;
				filename = GetFilenameFromWorkflowId( workflowId );
			}

			// Update/Create the file.
			using( FileStream output = File.Open( infoFilename, FileMode.Create, FileAccess.Write, FileShare.None ) )
			{
				new XmlFormatter2().Serialize( output, workflowId );
			}

			return workflowId;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions