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.
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].
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
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>
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 ...
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>
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>
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
.
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!
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:
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