Part 14 - Startable Facility
So this time we're going to have a look at the startable facility (you can find more info on it here in the wiki) - the concept of the startable facility is to ensure that components are started as soon as possible, which translates to as soon as all dependencies are available.
Effectively what happens is you create a component, and implement a special interface called IStartable, which looks like this and is found in the Castle.Core assembly.
///
/// Interface for components that wish to be started by the container
///
public interface IStartable
{
///
/// Starts this instance.
///
void Start();///
/// Stops this instance.
///
void Stop();
}
And then the startable facility detects any components registered in the container which implement the IStartable interface and determines if they have any dependencies which still need to be satisfied, if not, then the container will Resolve the component, and start it immediately (by invoking the IStartable.Start method on it) otherwise it's added to a list of services awaiting startup.
From then on, as components are registered in the container the startable facility will check the list of services awating startup, to see if any of them have all their dependencies available now, if so it will then Resolve the service and again invoke IStartable.Start on it.
Now at this point it's worth noting that Start is not executed asynchronously - if you block within the Start method then your container will halt loading the configuration etc. till it completes... be careful not to slow down your container startup too much with slow blocking operations with the Start() method.
So let's get into some code... first off I'm going to create a simple http listening service which is capable of serving up a single file... exciting stuff!
Here's the code:
public class LameHttpFileServer : IStartable
{
private string _path;
private HttpListener _listener;
private Thread _listenerThread;
private ILogger _logger;public LameHttpFileServer(string prefix, string path)
{
_path = path;
_listener = new HttpListener();
_listener.Prefixes.Add(prefix);
}public ILogger Logger
{
get
{
if (_logger == null) _logger = NullLogger.Instance;
return _logger;
}
set { _logger = value; }
}public void Start()
{
Logger.Debug("Starting LameWebService...");
_listener.Start();
_listenerThread = new Thread(RequestWorker);
_listenerThread.Start();
Logger.Info("LameWebService Started.");
}public void Stop()
{
Logger.Debug("Stopping LameWebService...");
_listenerThread.Abort();
_listener.Stop();
Logger.Info("Stopped LameWebService.");
}private void RequestWorker()
{
while (true)
{
HttpListenerContext context = null;try
{
context = _listener.GetContext();
}
catch (ObjectDisposedException)
{
return;
}ServeContent(context.Response, _path);
}
}private void ServeContent(HttpListenerResponse response, string path)
{
string content = File.ReadAllText(path);
byte[] buffer = Encoding.UTF8.GetBytes(content);
response.ContentLength64 = buffer.Length;Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
}
Now, this class doesn't have any constructor dependencies (beyond a little configuration) but we do have a Setter dependency on an ILogger service - ILogger is actually a generic abstract of a logger for a logging framework (a logger being a named/scoped class supporting a number of generic message writing methods) out of the box Castle provides support for both nlog and log4net, however it also contains some lightweight loggers, such as a console logger, which means you can avoid having to write any logging configuration files... so we're going to use that this time... because the loggers are assigned by the container, there's not need to actually reference the type the logger is logging "for", because the container knows that implicitly.
Now let's have a look at the container configuration:
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
type="Castle.Facilities.Startable.StartableFacility, Castle.MicroKernel" />
type="Castle.Facilities.Logging.LoggingFacility, Castle.Facilities.Logging"
loggingApi="console" />
type="IoC.Tutorials.Part14.LameHttpFileServer, IoC.Tutorials.Part14">http://*:8089/ lame.html
As you can see we have declared two facilities, one for handling the auto-wiring of Loggers into any components with ILogger constructor parameters or setter dependencies, and secondly the startable facility, which will take care of automatically starting our LameHttpFileServer for us.
Now, notice we're referencing a file called lame.html - heres what that looks like:
Lame.html
Welcome to lame.html file
This is the contents of the lame.html file, neat huh?
Now done to business, let's look at the program that's using this server...
private static void Main(string[] args)
{
WindsorContainer container = new WindsorContainer(new XmlInterpreter());GetPage();
container.Dispose();
GetPage();
Console.Read();
}private static void GetPage()
{
Console.WriteLine("rnClient: requesting http://localhost:8089/ ...");
try
{
WebClient client = new WebClient();
string content = client.DownloadString("http://localhost:8089/");
Console.WriteLine("Client: success, content follows.rnrn {0}", content);
}
catch (WebException ex)
{
Console.WriteLine("Client: Exception occured, message: {0}", ex.Message);
}
}
As you can see what we're doing is:
- Constructing the container using the configuration.
- Calling the GetPage method, which will attempt to download the lame.html file from our server (which the container should have started automatically for us)
- Disposing of the container (any guesses what that will do?)
- And finally calling the GetPage method again, to see if our server is still running.
[Debug] 'IoC.Tutorials.Part14.LameHttpFileServer' Starting LameWebService...
[Info] 'IoC.Tutorials.Part14.LameHttpFileServer' LameWebService
Started.
Client: requesting http://localhost:8089/ ...
Client: success, content follows.
Lame.html
Welcome to
lame.html file
This is the
contents of the lame.html file, neat huh?
[Debug] 'IoC.Tutorials.Part14.LameHttpFileServer' Stopping
LameWebService...
[Info] 'IoC.Tutorials.Part14.LameHttpFileServer' Stopped
LameWebService.
Client: requesting http://localhost:8089/ ...
Client: Exception occurred, message: Unable to connect to the
remote server
As you can see before we disposed of the container our request for the file worked just fine, but after the container had been disposed, it appears the file service has been stopped, and so our client was unable to connect... what caused this?
Well the way the startable service is implemented, it actually applies a couple of lifecycle concerns - a concern is effectively a bit of code that executes when what it's concerned in occurs, in these case the startable facility adds two concerns to a startable component's model.
The first "concern" is a commission concern, which invokes the Start method upon the component entering the commission step of it's lifecycle, which is immediately after the component instance has been constructed and all it's dependencies and configuration injected.
The second concern is a "decommission" concern, which occurs upon the component is being decommissioned (ie. disposed of, released or purged from the container) this happens to all components the container is holding onto when it is disposed, or when an individual component is released - however in the case of components with a singleton lifestyle, you can't actually release the component prior to disposing of the container (because that would violate the lifestyle) ... so doing something like this:
LameHttpFileServer server = container.Resolve();
container.Release(server);
In place of disposing the container won't stop the service if it's
a singleton....
One last though - what would happen if we changed our components lifestyle to [Transient] instead of the default of singleton, let's create a simple class for testing this idea:
[Transient]
public class StartableExperiment : IStartable
{
private static int _count;
private int _number;public StartableExperiment()
{
_number = ++_count;
}public void Start()
{
Console.WriteLine("Started #{0}", _number);
}public void Stop()
{
Console.WriteLine("Stopped #{0}", _number);
}
}
Now let's write some code to test it... we'll just register the component in code, to avoid having to change our config...
Here's the program itself...
WindsorContainer container = new WindsorContainer(new XmlInterpreter());container.AddComponent("exp", typeof(StartableExperiment));
StartableExperiment exp1 = container.Resolve
();
container.Release(exp1);
StartableExperiment exp2 = container.Resolve();
container.Release(exp2);container.Dispose();
And the results:
Started #1
Started #2
Stopped #2
Started #3
Stopped #3
Stopped #1
So what's happened is that immediately upon registration a component is created... and can't be disposed (because we can't get a hold of the instance to release it) until the container is disposed... but for the couple of calls to Resolve and Release you can see the StartableExperiment instances are started & stopped during commission & decommission steps in the components life cycle as expected.
So that brings this look at the startable facility to an end... next time we might have another quick look at life cycles to see how else we can use this concept when build components, and maybe after that we'll have a look at some of other lifestyles which are available "out of the box" and how to write our own.