Chapter 25. Windows Services

25.1. Remarks

This is functionality that will be included after the 1.0 release. If you want to use these features please get the code from CVS http://opensource.atlassian.com/confluence/spring/display/NET/Project+Structure (instructions) or from the download section of the Spring.NET website that contains an .zip with the full CVS tree. In addition to this documentation you can refer to the example program located at examples\Spring\Spring.Examples.WindowsService to better understand the package. Please check the Spring.NET website for the latest updates to this document.

25.2. Introduction

Developers usually create Windows Services using the Visual Studio .NET wizard. While not difficult to do, this procedure is repetative and does not encourage separation between infrastructure code (windows service) and application code. This is generally considered a "bad thing" but you can certainly disagree.

As Spring.NET can provide an explicitly managed initialize/destroy lifecycle for singleton objects, there is a natural synergy with the lifecycle of a Windows service. As such, it could be very convenient to expose a Spring application context as a Windows service. Starting and stopping the service corresponds to creating and destroying an application context and its contained objects. This approach provides a high level means to declare what objects are created and destroyed when developing a Windows service.

To do that, Spring.NET requires the installation of one physical service able to run as services as many applications as you want - each a logical independent service in their own application domain. By default, the deployment and updating of the service can also be done by copying the relevant executables to a special directory.

The executable that at present provides these features is the Spring.Services.WindowsService.Process.exe assembly. It makes heavy use of classes and interfaces definde in the Spring.Services.WindowsService.Common.dll assembly. You should reference the common assembly it if you want to follow the advice on customization contained in the following sections

The benefits of this approach, a part from those given by separating infrastructure code and application code (a field where Spring.NET tries hard to succeed) is that you can think about installing a new service at client site by simply dropping a new application assembly in a remote directory[5].

25.3. The Spring.Services.WindowsService.Process.exe application

25.3.1. Installing

The installation can be done in two ways, using the .NET SDK installutil.exe tool or using the more mundane Spring.Services.WindowsService.Installer.exe; while the former is the standard, the latter is probably more flexible. It allows you to customize the name/display name of the service and has the ability to install multiple times the same assembly with different names. This can be useful in a number of scenarios, especially where you don't like, for some reasons, to run several different logical services under the same physical windows service.

Be aware of the fact that the service will be installed as running with the system account (installing with a specific user account seems a bit buggy on Windows XP)

That said, while installutil is documented on its own , the command line for Spring.Services.WindowsService.Installer.exe is as follow:

Spring.Services.WindowsService.Installer.exe

usage: 
 install service-exe-path service-display-name service-name
 uninstall service-name      [i|u] service-exe-path service-display-name service-name

for example, to install, you can invoke it with the following:

... install  Spring.Services.WindowsService.Process.exe "Spring.Service Support" spring-service

and to uninstall it:

... uninstall  spring-service

25.3.2. Configuration

The standard .NET .config file can be used to tune some parameters of Spring.Services.WindowsService.Process.exe, (including log4net settings, for which it is recomended to consult the log4net documentation).

This file also define the context run by this process; here the file in its current beauty:

<configuration>

    <configSections>
        <section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
      
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="file://~/service-process-definition.xml" />
        </context>        
    </spring>

    <system.runtime.remoting>
    <application>
        <channels>
        <channel ref="http" port="1234" />        
        </channels>
    </application>
    </system.runtime.remoting>
 
  <log4net>
    <!-- see http://logging.apache.org/log4net/release/manual/introduction.html -->
    <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d [%t] %-5p %c{1} - %m%n" />
      </layout>
      <file value="logs/Spring.Service.Process.log" />
      <appendToFile value="true" />
      <maximumFileSize value="500KB" />
      <maxSizeRollBackups value="5" />
    </appender>
    <appender name="OutputDebugString" type="log4net.Appender.OutputDebugStringAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d{HH:mm:ss,fff} %-5p %c{2}(line:%L) - %m%n" />
      </layout>
    </appender>
    <root>
      <level value="OFF" />
    </root>
      <logger name="Spring.Services">
          <level value="ALL" />
      <appender-ref ref="RollingFile" />
      <appender-ref ref="OutputDebugString" />
      </logger>      
  </log4net>

</configuration>

As you see, the context is defined in another file: let's review the objects it defines.

Firstly, it is worth notice that in order to 'localize' the service (i.e. to know where it is installed to use that directory as base for the deploy dir as in the above file) you should define an object like this: the name is not very important, it is important that it is an IObjectFactoryPostProcessor and so will be automatically applied to this application context:

<!-- provides access to the ${spring.services.process.base.dir} property -->
<object 
    name="localizer"
    type="Spring.Services.WindowsService.Common.Localizer+ForProcess, Spring.Services.WindowsService.Common">
    <!-- change this to access the property with another prefix, for example ${foo.process.base.dir}
    <property name="prefix" value="foo"/>
    -->
</object>

In that object definition you can customize the prefix for the following string

public static readonly string SpringServicesProcessBaseDirFormat = "{0}.process.base.dir";

but you usually won't need it; the default value is

public static readonly string DefaultPrefix = "spring.services";

The sole important object defined by this context, i.e. the main object run by the service. The thing you can (and should) configure is the path to the folder you will use as the deploy location; the current definition, to avoid the need for a fully qualified path (e.g.: c:\spring\services) uses the properties made available by the localizer above:

<object 
    name="service"
    type="Spring.Services.WindowsService.Common.DefaultService, Spring.Services.WindowsService.Common"
    init-method="Start"
    destroy-method="Stop">
    <property name="DeployPath" value="${spring.services.process.base.dir}/deploy"/>
</object>

The above object is then easily remoted using spring remoting utilities (please notice you should tune the remoting configuration listed in the standard .NET .config file, listed above):

<object name="remoted.service" type="Spring.Remoting.SaoExporter, Spring.Services">
    <property name="TargetName" value="service"/>
    <property name="ServiceName" value="SpringWindowsService.rem"/>
</object>

25.4. Running an application context as a windows service

If you package an application using the layout and conventions described here, you'll be able to run an application context as a Windows Service. The conventions used are modeled after those used by ASP.NET and are very easy to follow.

As already said, you'll have a Spring.NET application context running in a dedicated AppDomain hosted in a process running as a windows service: that process is able to run many application contexts simultaneously.

A complete application runable as service consists of a directory containing:

  • The .NET configuration file service.config: this file should define your application context. Moreover this files will be used by the CLR to configure the application domain your application will run in, exactly as you expect. This file has the same role of ASP.NET Web.config file.

  • Optional: an xml context file (watcher.xml) defining the watcher for your application.

    The watcher controls the automatic redeployment of the service and is discussed more in the following section.

  • Recomended: along the lines of ASP.NET convention, a bin subdirectory containing all the assemblies your application needs; you can of course put your assemblies in the same directory where you put service.config but this is not encouraged ...

25.4.1. service.config

This is the standard .NET configuration file for the AppDomain that will host your application. It is semantically equivalent to the ASP.NET Web.config file. [6]

This file should also define your application context. When the service is started and stopped, the corresponding lifecycle methods are called on all the singletons defined. Of course, singletons are automatically instantiated by the application context when the service starts. For more information on lifecycles in Spring.NET see Section 4.5.1, “Lifecycle interfaces” Here an example taken from the tests:

<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
      
    <appSettings>
        <add key="port" value="10"/>
    </appSettings>  

    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="file://~/service.xml" />
        </context>
    </spring>
 
</configuration>

In this case the context is (again!) defined in another file (author's personal taste...) and the only 'service' is the echo object (there is also a PropertyPlaceholderConfigurer just to make the example more realistic):

<objects xmlns="http://www.springframework.net" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd">
    
    <object name="echo" 
      type="Spring.Services.WindowsService.Samples.Echo, Spring.Services.WindowsService.Tests" 
      init-method="Start" destroy-method="Stop">
      <property name="port"><value>${port}</value></property>
    </object>

    <object id="configurer" type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core">
      <property name="locations">
         <list>
            <value>file://~/service.config</value>
         </list>
      </property>
      <property name="configSections">
         <list>
            <value>appSettings</value>
         </list>
      </property>
    </object>

</objects>

25.4.1.1. Let the application know where it is

There are some properties you may need at runtime, when your services will run, and you cannot know in advance. Hopefully, your xml definition file will allow to find the information it needs using some predefined variables you can use inside the service definition file with the standard NAnt style ${property name} syntax.

These properies are:

  • spring.services.application.fullpath that will be replaced with the full path of the application's AppDomain.BaseDirectory, i.e., where your application has been deployed;

  • spring.services.application.name that will be replaced with the name of the subdirectory where the application has been deployed. Each application is deployed in its own directory, of course;

These properties are accessible only if one defines a localizer in the context like this (the localizer is a special IObjectFactoryPostProcessor:

<!-- provides access to the ${spring.services.application.*} properties -->
<object 
    name="localizer"
    type="Spring.Services.WindowsService.Common.Localizer+ForApplication, Spring.Services.WindowsService.Common">
    <!-- change this to access the property with another prefix, for example ${foo.application.base.dir}
    -->
    <property name="prefix" value="myPrefix"/>
</object>

As you can see above, one can easily change the prefix used by that localizer and then write someting like:

<object name="simple" 
  type="Spring.Services.WindowsService.Samples.Simple, Spring.Services.WindowsService.Tests" 
  init-method="Start" destroy-method="Stop">
  <constructor-arg index="0" value="${myPrefix.application.name},${myPrefix.application.fullPath}"/>
  <property name="AppName">
    <value>${myPrefix.application.name}</value>
  </property>
  <property name="AppFullPath">
    <value>${myPrefix.application.fullpath}</value>
  </property>
</object>

25.4.2. watcher.xml - optional

This file allows you to optionally define a watcher for your application that can automatically redeploy it when needed.

The important thing to notice is that you can define your own application watcher, named watcher. Here it is used a watcher that listen for changes on the filesystem, configured to listen for some changes and to ignore others.

You can provide your own implementation defining an object named watcher that implements Spring.Services.WindowsService.Common.Deploy.IApplicationWatcher:

/// <summary>
Interface defining the contract for an application watcher.
<p>An application watcher is responsible to dispatch an 
<see cref="IApplicationWatcherFactory">event</see> whenever it thinks the 
application has been updated.</p>
<p>Usually it should not raise other kind of events
as they are usually raised by the <see cref="FileSystemApplicationWatcher"/> 
that creates the watcher itself</p>
</summary>
<remarks>Usually instances of this interface need to be disposed</remarks>
<seealso cref="DeployEventArgs"/>
<seealso cref="IDeployLocation"/>
<seealso cref="DeployEventType.ApplicationUpdated"/>
<seealso cref="DeployEventAggregator"/>
<seealso cref="IDeployLocation"/>
<seealso cref="DeployEventType"/>
public interface IApplicationWatcher : IDisposable
{
    /// <summary>
    /// The watched application
    /// </summary>
    IApplication Application {get; }

    /// <summary>
    /// Start to watch the application, using the given dispatcher to 
    /// dispatch deply events
    /// </summary>
    /// <param name="dispatcher">the dispatcher used to raise deploy events</param>
    void StartWatching (IDeployEventDispatcher dispatcher);

    /// <summary>
    /// Stop to watch the application.
    /// </summary>
    void StopWatching ();

    /// <summary>
    /// If physical events watched by this watcher should be filtered, this methods
    /// will allow to set filters that allows and disallows the event to be raised
    /// by the watcher.
    /// </summary>
    /// <param name="allows">the list of allowing filters</param>
    /// <param name="disallows">the list of disallowing filters</param>
    /// <seealso cref="FilteringSupport"/>
    /// <seealso cref="RegularExpressionFilter"/>
    void SetFilters (IList allows, IList disallows);
}

Please notice that this interface is currently a movable target and will probably change before the first official release (this will probably affect also the way a watcher will know about the application it should monitor, as shown in a few lines).

A tipical example of this file is give here:

<objects>

    <object name='watcher' 
      type='Spring.Services.WindowsService.Common.Deploy.FileSystem.FileSystemApplicationWatcher'>
      <!-- 
      we can get access to the IApplication we are asked to monitor 
      using a reference like the following
      -->
      <constructor-arg ref='.injected.application'/>

      <!-- sometimes the windows OS will decide to not give you the same case you see in explorer:
           in fact one should consider this OS case-insensitive with regard to file names ...
           The following property, true by default can however be tuned
      <property name="ignoreCase" value="false"/>
      -->

      <property name="includes">
        <list>
          <value>wwwroot/bin/*.*</value> 
          <value>service.config</value> 
          <value>service.xml</value> 
        </list>
      </property>      
      
      <!--
      <property name="excludes">
        <list>
          <value>Db/**/*.*</value> 
          <value>Jobs</value>
          <value>Jobs</value>
          <value>**/*.log</value>
        </list>
      </property>
      -->
      
    </object>
    
</objects>

As you can see, if you need it, you can reference the Spring.Services.WindowsService.Common.IApplication object that your watcher should watch using the name .injected.application.

25.4.3. bin directory - optional

This is, by default, the folder where your assemblies are placed in the same way they are in an ASP.NET application.

Putting assemblies there is more a convention and maybe a good practice (they are isolated from other artifacts, but maybe you will prefer to use another directory (modify the service.config file accordingly) or the application directory directly (= bin parent).

Be aware of the fact that the process in which your application will run will have its own PATH environmental variable. As such don't expect to be successfull using dlls imported with [DllImport] if they are not in the system PATH of the hosting machine: while it is well known that the CLR fusion algorithm will not consider the PATH variable, you may be biten by assemblies using non-system dlls (SQLite and Firebird ADO.NET providers are good examples).

Reiterating, one can put assemblies in another directory under the application directory tree, and write the .NET configuration file (service.config) accordingly: .NET probing algorithm is always in place.

Please notice that it is not required that your application uses or include any of the Spring.NET assemblies: any object in any assembly, given it has lifecycle methods, can be run as a service: non invasive infrastructure support courtesy of Spring.NET!

25.5. Customizing or extending

It should be said that support for windows service has been initially developed with a clear but limited set of 'extension points' in mind, mainly related to the way you can deploy your services: deploy location (filesystem, zip archives, mailbox, urls, ...), (auto-)updating features, and so on.

To better understand the following discussion, the following figure depicts some of the inner details of Spring.Services.WindowsService.Process.exe at run-time:

Spring.Services.WindowsService.Process.exe run-time details

25.5.1. The .config file

The executable Spring.Services.WindowsService.Process.exe is somewhat configured by the corresponding .config file. Please notice that this file is the most important extension point for windows service support, and it will probably be made more powerful and flexible in the future.

For applications deployed in the standard way (i.e. on the filesystem as explained above) the updating features are configured by the watcher.xml file, if present, as already seen.

There should be however, other ways to deploy your applications, maybe just as zip files dropped somewhere on the web or sent via e-mail.

For these scenarios, your deploy location will be something that implements Spring.Services.WindowsService.Common.Deploy.IDeployLocation.

Please notice that, while questionable, it actually entends IDisposable [7]:

/// <summary>
/// Interface defining how a deploy location should look like
/// </summary>
public interface IDeployLocation : IDeployEventSource, IDisposable
{
    /// <summary>
    /// The list of applications deployed at this location
    /// Usually non-valid applications are not listed
    /// </summary>
    /// <seealso cref="Application"/>
    IList Applications { get; }
}

/// <summary>
/// Interface defining the contract for an object acting as the source of
/// deploy events (application added, removed, updated)
/// </summary>
/// <seealso cref="DeployEventArgs"/>
/// <seealso cref="DeployEventHandler"/>
public interface IDeployEventSource
{
    /// <summary>
    /// The multicaster for deploy events
    /// </summary>
    event DeployEventHandler DeployEvent;        
}



[6] log4net users please notice that (as of 1.2 beta 9) file appenders, when dealing with a relative file name, assume it is relative to the application domain code base. If you use log4net, it is very handy with the mechanics used by Spring Windows Service as every log file you will specify will be relative the directory containing the service application.

[7] this has been done as it is possible that a deploy location holds resources that should be released, for example network connections, lock files or the like