Anatomy of a Windows Service - Part 2
This post contains gist code samples. My experiments indicate that these do not display in Google Reader, so if you are reading this there, you may want to click through to the post.
Last time, we dissected a Windows service template project to get a handle on the different moving pieces. We identified an installer class, as well as two run-time configured installer objects, a service class, and the framework injection point for spinning up a service.
The template is a very simple project, and it gets the job done. There are some deficiencies though. From an architectural standpoint, the design doesn't lend itself well to testability, or composability. It also doesn't provide any good place to bootstrap an IoC container for the installer portion, which would be beneficial to have if we want to have any custom behavior happening at install. From a feature standpoint, there's no logging, and no worker thread to separate the service control events from the actual behavior of the app.
Let's start out by improving the composability, which will put us on a good footing for testability as well. The biggest obstacle to that in the template project was all of the direct instantiation and configuration of objects. For example, in the template project, the ProjectInstaller class's constructor, which is an entry point that is triggered directly by msiexec or installutil, was responsible for creating and initializing the sub-installers. In our application, we'll defer to an IoC container, rather than construct these objects directly.
Let's add a reference to my personal favorite IoC container, Autofac, and then create a bootstrapper that will do the registration necessary for the installer portion of the app. We'll leave it empty to start, because we haven't established what we'll need from it yet.
Now lets create the ProjectInstaller. The constructor, since it's an application entry point, will have a direct reference to the IoC container. We'll leave out the needless code we identified last time, but don't forget the crucial RunInstaller attribute. The constructor is left responsible for setting up the Installers collection. If we assume that we'll be registering all our installers with the IoC container, then Autofac makes this easy for us with the IEnumerable
Compared to the template project, we've eliminated some explicit object instantiation, but we've also lost some explicit configuration. That has to be exported to somewhere else. There are a couple of options for re-creating this functionality with our IoC container in place. One is to give these exact configuration responsibilities, i.e. the setting of properties after construction, to the IoC container. This is not a strong-point of most containers--even Autofac. It can be done, but it's not really straightforward. It results in complex registration code that doesn't communicate well what's going on.
Alternatively, we could move these responsibilities into factories. I've used this strategy before, and it can be advantageous if there are classes that will have dependencies on the factories themselves. But in this case, the only thing that would need to know about the factories is the IoC. So instead, I'd recommend deriving special-purpose sub-classes of the desired installer base classes, and putting the configuration in those constructors instead. Here's what that looks like.
Simple, straightforward, self-contained, and single responsibility. Now we just need to update the IoC registration to include them, and they'll get picked up automatically by the ProjectInstaller constructor. We could take advantage of Autofac's fluent registration API and auto-wiring to do this without referencing the individual types. But I have these two classes in separate namespaces (the service installer is in the same place as the service, because these must come in pairs), and the ProjectInstaller derives from the same base, it would actually take more code to deal with these special circumstances. So explicit registration it is.
Note also here that we've registered these as InstancePerLifetimeScope, because there's really no reason more than one of either of these should be floating around.
That covers the install portion of the service. We now have separated the three installation components into their own separate places. Changes to the configuration of any one of them are isolated in their own locations in the code. And as a bonus we've made the ProjectInstaller constructor trivially simple.
Let's shift gears and take a look at the service portion of the application. We'll set up a blank bootstrapper for this portion as well. I'll refrain from posting the code as it's essentially identical to what I showed you for the other one.
The remaining two significant pieces that need to be implemented to replicate the functionality we saw in the template project are the service class itself, and the program mainline. The mainline is going to be simple just like the ProjectInstaller. In fact it's structure mirrors the installer very closely, just with different type names in a few strategic places.
I've chosen to request an IEnumerable
Next let's look at the service class itself. This is another class that, in the template project, had a lot of noise that we identified as unnecessary. So in this iteration, it's going to be comparatively short and sweet.
There's very little to see here, and that's very much as it should be, since our service doesn't actually do anything yet. We just cut out the noise and were left with something trivially simple. So let's move on to the last piece left to put in place: the service bootstrapper.
We're just blasting through code files here, as this one is another very simple one, with just one registration. We'll add more, but for right now we have completely matched the functionality of the template project.
Before we put the project to bed for another week, though, there's a small refactoring we can make that will make a nice little illustration of the benefits of how we've organized the project. You may have noticed that we have some hard-coded strings in the constructors of the service and the service installer. And particularly, there's one that's intentionally repeated, since it has to be the same in both places. We can eliminate the hard-coded string and make sure that the values in both locations always match, by using the setting injection pattern I described a few months ago.
With just a few lines of code we can set up the necessary setting dependency.
With this class and interface in place, we can add a dependency to the constructors of the installer and the service.
And finally, in order to support the dependency injection, we just add one new registration to each of the bootstrappers.
That's the end of our work for this week. We have replicated the template project's functionality, such as it is, and run through a lot of simple little snippets of code. Structurally, the Solution Explorer doesn't look a ton different, but I think the code is simpler and more straightforward. Plus we have built a good foundation on which to add the functionality that people expect of a service. All the code for the project we worked on in this post can be found on GitHub at https://github.com/cammerman/turbulent-intellect/tree/master/HelloSvc.
Next week, we'll add logging to an event log, and we'll make it actually do some work. Finally, we'll figure out how that work needs to get done, inline, or in one or more worker threads.