Introduction
ServiceStatePublisher
extends the .NET System.ServiceProcess
library. The ServiceStatePublisher
library introduces the ability to observe or listen for state changes to a Windows Service.
Why create this library extension? To get experience using several design patterns. In particular, this project will incorporate 3 design patterns:
- Singleton
- State
- Observer/Publish-Subscribe
Having been a casual observer of this site for awhile, I know the design patterns topic is one of those religious hotbeds. For me, they represent a nice way to communicate what you're doing -- and if by chance someone recognizes the design pattern...you know, "hey, that's the fuzzy duck pattern"...then all the better. I don't take it as criticism or superiority, but rather as a compliment. The observer was excited to recognize something they learned can be used in practice. And, the code was clear enough for them to see the forest through the trees (i.e. beneath the surface of the code). Hopefully, this code will be clear enough. ;-)
To demonstrate the use of this library, I have constructed the Oracle Service Manager application, found elsewhere on this site. Oracle Service Manager mimics the Microsoft SQL Server Service Manager.
Background
I have listed the references, at the end of this article, from which I derived my work; however, there are many articles on this site, and the Internet, that are excellent sources for the topics covered. Topics covered include the design patterns identified above as well as .NET Framework topics such as the System.ServiceProcess
.NET namespace, delegates, and events.
Problem Analysis
Windows Services are not designed to fire events alerting observers when a service has changed state. This library extension provides a framework to add one or more service observers. For example, if you wanted to create a utility to monitor and control the Oracle Windows services – just like the SQL Server Service Manager – you would need 3 observers: a dialog observer, a tray icon observer, and a timer/polling observer. A change in the state of the server ought to be broadcasted to all listeners, so they can decide if there is any action to take – like updating a user interface.
I have created a project and wrote an article, found elsewhere on CodeProject, for just this purpose – monitoring and controlling Oracle services – it’s called Oracle Service Manager.
Using Design Patterns
There are two primary issues to solve. The first is the state of the service. The operations performed on a service (e.g. Start, Stop, Pause, and Continue) are dependent on the internal state of the service. In other words, a running service can be stopped and, possibly, paused; however it cannot be started or continued. Most of the time, this issue is solved with a big conditional statement. The State pattern puts each branch of the conditional statement in a separate class. [GHJV98]
The second issue concerns notifying interested parties of the change in state. When a running service stops, all parties interested in observing the service should be notified of the new state regardless of whether that party initiated the request. The Observer pattern allows us to notify other objects without making assumptions about who these objects are. In other words, you don't want these objects tightly coupled. [GHJV98]
Gamma et al, classify both patterns as behavioral. In addition, the two patterns have a relationship to one another in this library. That is, examining the two patterns reveals, the observer pattern means nothing unless the state of a service changes. This implies that the observer pattern has a dependency on the state pattern. Therefore, the observer pattern implementation needs to subclass the state pattern implementation. In short, the observer subject will derive from, or extend, the state context class.
The state pattern declares each state be represented by a concrete class. This introduces a decision of when to allocate and clean up the concrete state objects. Since we are only interested in tracking one service at a time, and we have a limited number of concrete states, we can minimize memory allocations by creating all concrete state objects at startup. This introduces a requirement to ensure and manage a single instance. This problem is solved by a third pattern – the Singleton pattern [GHJV98].
Below, I discuss the characteristics of the design pattern implementations. If you are only interested in using this library, your interest will lie in one of two classes: the ServiceContext
in the State pattern; or the ServiceSubject
in the Observer pattern.
State Pattern
The State pattern diagrammed above outlines the participants needed to support the model. The equivalent types needed for the ServiceStatePublisher
library are:
Context
ServiceContext
.
- Defines the interface of interest to clients. In other words, as a user of this pattern, this is the class to program against. This class exposes all supported behaviors and properties encapsulated by the
ConcreteState
classes.
- Maintains an instance of a
ConcreteState
subclass that defines the current state†
Context
delegates state-specific requests to the current ConcreteState
object†
Context
passes itself as an argument to the State
object handling the request. This lets the State
object access the context†
- Either
Context
or the ConcreteState
subclasses can decide which state succeeds another and under what circumstances†
public class ServiceContext
{
protected ServiceController _controller;
protected ServiceState _state;
public ServiceContext()
{
State = ServiceStateUnknown.Instance;
Controller = null;
}
public void Start()
{
State.Start( this);
}
public void Stop()
{
State.Stop( this);
}
public void Pause()
{
State.Pause( this);
}
public void Continue()
{
State.Continue( this);
}
}
State
ServiceState
.
- Defines an interface for encapsulating the behavior associated with a particular state of the
Context
†
- I did not implement this as an interface because the actual implementation has some important, but not obvious, default behavior. So, instead, I used a public abstract class to indicate this is a base class from which subclasses derive. More importantly, this class cannot be instantiated on its own. Indeed, this class is the base class for the
ConcreteState
classes discussed below.
- As a base class,
ServiceState
is the natural place to put common error logging for each of the subclasses.
- The important default behavior is the empty body for each virtual function. This means, by default, a call to a method that is not overridden will do nothing. This will ease the burden for each
ConcreteState
class.
public abstract class ServiceState
{
public virtual void Start( ServiceContext context) { }
public virtual void Start( ServiceContext context, string[] args) { }
public virtual void Stop( ServiceContext context) { }
public virtual void Pause( ServiceContext context) { }
public virtual void Continue( ServiceContext context) { }
…
protected virtual void LogError( string message, EventLogEntryType entryType)
{
if (!EventLog.SourceExists( SSP_SOURCE))
EventLog.CreateEventSource( SSP_SOURCE, SSP_EVENTLOG);
EventLog sspLog = new EventLog();
sspLog.Source = SSP_SOURCE;
sspLog.WriteEntry( message, entryType);
sspLog.Close();
}
}
ConcreteStates | | ServiceRunning , ServiceStopped , ServicePaused , |
| | ServicePendingStart , ServicePendingStop , ServicePendingPause , |
| | ServicePendingContinue , ServiceStateUnknown |
- Each subclass implements a behavior associated with a state of the
Context
†
- Each
ConcreteState
class is derived from ServiceState
.
- Each
ConreteState
has a one-to-one mapping with the ServiceControllerStatuses
in .NET, with one exception.
- Included is a class to reflect an unknown state, such as when a server or service has not been selected.
- Because the base class,
ServiceState
, implements a default behavior, only those methods requiring overrides must be implemented. In other words, the ServiceRunning
state need only override the methods that are allowable in that state – Stop()
and Pause()
. If the client program calls Start()
or Continue()
when the state is ServiceRunning
, the default behavior is executed – do nothing. This treatment means we do not need to explicitly check for invalid requests, because the invalid request will be dealt with gracefully without error.
public class ServiceRunning : ServiceState
{
…
public override void Stop( ServiceContext context)
{
try
{
context.Controller.Stop();
context.Controller.WaitForStatus(
ServiceControllerStatus.Stopped, new TimeSpan(0,0,30));
ChangeState( context, ServiceStopped.Instance);
}
catch (TimeoutException te)
{
LogError( te.Message, EventLogEntryType.Warning);
throw;
}
}
public override void Pause( ServiceContext context)
{
try
{
context.Controller.Pause();
context.Controller.WaitForStatus(
ServiceControllerStatus.Paused, new TimeSpan(0,0,30));
ChangeState( context, ServicePaused.Instance);
}
catch (TimeoutException te)
{
LogError( te.Message, EventLogEntryType.Warning);
throw;
}
}
}
Observer Pattern
The Observer pattern diagrammed above outlines the participants needed to support the model. The Observer pattern is also known as: Listener and Publish-and-Subscribe. One noteworthy item is how well .NET provides native support for this pattern through built-in functionality found in delegates and events. The equivalent types needed for the ServiceStatePublisher
library are:
Subject
Not applicable.
- Knows its observers. Any number of
Observer
objects may observe a subject
- Provides an interface for attaching and detaching
Observer
objects§ (e.g., in C#, the syntax to attach is object.event += new EventHandler()
; to detach, C# uses the -=
operator)
- .NET provides this functionality through its built-in support of delegates and events
- The
ConcreteSubject
establishes this contract through .NET’s event support – see below.
ConcreteSubject
ServiceSubject
.
- This is the publisher and defines the interface of interest to clients'. In other words, if you want published events, this is the class you should use, or program against, in your application.
- Stores state of interest to
ConcreteObserver
objects§
- Sends a notification to its observers when its state changes§ (i.e., publishes the event to subscribers)
- .NET supports this functionality through its built-in support of delegates and events
public class ServiceSubject : ServiceContext
{
public event EventHandler ServerChanged;
public event EventHandler ServiceChanged;
public event EventHandler StateChanged;
protected string _serverName;
public ServiceSubject() : base() { }
public ServiceSubject( ServiceController service)
{
Controller = service;
}
public virtual string ServerName
{
get { return _serverName; }
set
{
_serverName = value;
OnServerChanged( EventArgs.Empty);
if (Controller != null && _serverName != Controller.MachineName)
Controller = null;
}
}
public override ServiceController Controller
{
get { return _controller; }
set
{
_controller = value;
_state = QueryServiceState();
OnServiceChanged( EventArgs.Empty);
}
}
public override ServiceState State
{
get { return _state; }
set
{
_state = value;
OnStateChanged( EventArgs.Empty);
}
}
public virtual void RefreshServices()
{
OnServerChanged( EventArgs.Empty);
}
protected virtual void OnServerChanged( System.EventArgs e)
{
if (ServerChanged != null)
ServerChanged( this, e);
}
protected virtual void OnServiceChanged( System.EventArgs e)
{
if (ServiceChanged != null)
ServiceChanged( this, e);
}
protected virtual void OnStateChanged( System.EventArgs e)
{
if (StateChanged != null)
StateChanged( this, e);
}
}
Observer
Not applicable.
- Defines an updating interface for objects that should be notified of changes in a subject§
- .NET provides this functionality through its built-in support of delegates and events
ConcreteObservers
Implemented in client application.
- This is the actual subscriber or listener
- Maintains a reference to a
ConcreteSubject
object§
- Stores state that should stay consistent with the subject's§
- Implements the Observer updating interface to keep its state consistent with the subject's§
- .NET provides this functionality through its built-in support of delegates and events
- The observers are implemented in the client application. I created an example, Oracle Service Manager, for some “real” world applicability.
using System;
using System.ServiceProcess;
using kae.ServiceStatePublisher;
public class SampleObserver
{
public SampleObserver( ServiceSubject subject)
{
if (subject != null)
{
subject.ServerChanged += new EventHandler( this.OnServerChanged);
subject.ServiceChanged += new EventHandler( this.OnServiceChanged);
subject.StateChanged += new EventHandler( this.OnStateChanged);
}
}
private void OnServerChanged( object sender, EventArgs e)
{
ServiceSubject subject = (ServiceSubject) sender;
…
}
private void OnServiceChanged( object sender, EventArgs e)
{
ServiceSubject subject = (ServiceSubject) sender;
…
}
private void OnStateChanged( object sender, EventArgs e)
{
ServiceSubject subject = (ServiceSubject) sender;
…
}
}
Singleton Pattern
The Singleton pattern diagrammed above outlines the sole participant needed to support the model. The Singleton pattern is applied differently depending on whether you need a single instance inside a single process or across multiple processes. In the latter case, an operating system level resource must be used. In the former case, a static constructor can be used to create the one and only instance.
Singleton | | ServiceRunning , ServiceStopped , ServicePaused |
| | ServicePendingStart , ServicePendingStop , ServicePendingPause |
| | ServicePendingContinue , ServiceStateUnknown |
- Defines an
Instance
operation that lets clients access its unique instance. Instance
is a class operation**
- Responsible for creating its own unique instance**
- In C#, we use a static property as the accessor
public class ServiceRunning : ServiceState
{
private static ServiceRunning _instance;
static ServiceRunning()
{
lock (typeof(ServiceRunning))
{
if (_instance == null)
_instance = new ServiceRunning();
}
}
public static ServiceRunning Instance
{
get { return _instance; }
}
…
}
Future Directions
As I have mentioned above, the ServiceStatePublisher
library is used in a companion article, Oracle Service Manager, as an example application that may have some relevance to your development environment. In addition, using this library for a Visual Studio add-in to manage the Windows Services of any database (e.g., Oracle, DB2, MySQL, SQL Server, etc.) could be a nice alternative to the browsing mechanism offered by the Server Explorer in Visual Studio. Perhaps, there is also an enhancement to ensure changes in state happen in a worker thread, allowing the main thread to be more responsive to the interface.
Special Thanks
I wish to thank Liam Corner for lending me his expertise and review. He provided viewpoints I hadn’t previously considered. As a result, this project and future projects will be better for it.
Footnotes
- † Participant attributes and collaborations for State pattern [p.305] detailed in [GHJV98].
- § Participant attributes and collaborations for Observer pattern [p.293] detailed in [GHJV98].
- ** Participant attributes and collaborations for Singleton pattern [p.127] detailed in [GHJV98].
References
History
- 14 May 2004 - Initial release.