How to Create a Windows Service Using TopShelf, Ninject, OWIN and ASP.NET Web API
Recently, at work, I had to put together a Windows service application. The knowledge regarding putting all these components together is quite scattered, so I thought I'd collect my findings in a single article for future reference both for others as well as myself. I was inspired to put this together when I prepared an answer to this question on Stackoverflow.
Update (25 Mar 2015): A newer version of the code in this article is available here on https://bitbucket.org/khawaja_umar_farooq/samples
When I began work on the project at work, I already knew that TopShelf was a great framework for creating Windows service applications. The key advantages from using TopShelf are that:
- A lot of the boilerplate code to install and configure the Windows service has already been written for you, which you can access using a combination of Fluent syntax and lambda expressions.
- The resulting application is a simple console application as well as a Windows service application, which comes in very handy during debugging; debugging is a case of placing a breakpoint and hitting F5 and away you go.
So leveraging TopShelf was pretty much a given.
Further, I quite like Ninject for a dependency injection framework. I have used it often within ASP.NET MVC projects and I find the clean syntax a joy to work with. I've read some people argue that it is slower than some of the other DI frameworks available, but that aspect of performance isn't something I have had cause to worry about too much yet. It's quite popular, actively maintained and works with plenty of other frameworks, which are all good qualities in my opinion.
That added Ninject to the mix.
Again, from my previous experience with ASP.NET MVC, I knew that OWIN and ASP.NET Web API are good technologies to leverage for self-hosting a web service.
So there we have my selection of technologies for a relatively simple Windows service application:
- TopShelf
- Ninject
- OWIN
- ASP.NET Web API
Now to put them all together into an unholy mix!
I use Visual Studio and NuGet has been a brilliant addition to Visual Studio in the past few years.
So here's how I did it.
Create the project
Start by creating a Windows console application project.
Install the following packages into the project.
- Microsoft.AspNet.WebApi
- Microsoft.AspNet.WebApi.Client
- Microsoft.AspNet.WebApi.Core
- Microsoft.AspNet.WebApi.Owin
- Microsoft.AspNet.WebApi.WebHost
- Microsoft.Owin
- Microsoft.Owin.Host.HttpListener
- Microsoft.Owin.Hosting
- Newtonsoft.Json
- Ninject
- Ninject.Extensions.ContextPreservation
- Ninject.Extensions.NamedScope
- Ninject.Web.Common
- Ninject.Web.Common.OwinHost
- Ninject.Web.WebApi
- Ninject.Web.WebApi.OwinHost
- Owin
- Topshelf
- Topshelf.Ninject
Review how to add NuGet packages to a Visual Studio solution.
Create the Program class
Create the Program.cs
code file as follows.
Note the comments inline in the code for some important features of the code in this file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ninject;
using Topshelf;
using Topshelf.Ninject;
namespace BackgroundProcessor
{
using Modules;
using Services;
public class Program
{
public static int Main(string[] args)
{
var exitCode = HostFactory.Run
(
c =>
{
// load the DI bindings from the Module class
// which is a Ninject module; can take multiple
// modules
c.UseNinject(new Module());
c.Service<Service>
(
sc =>
{
// inject the objects bound from
// our Ninject module(s) above
// into our Service class
sc.ConstructUsingNinject();
sc.WhenStarted
(
(service, hostControl) =>
service.Start(hostControl)
);
sc.WhenStopped
(
(service, hostControl) =>
service.Stop(hostControl)
);
}
);
c.SetServiceName("BackgroundProcessorSvc");
c.SetDisplayName("Background Processor");
c.SetDescription("Processes things in the background");
c.EnablePauseAndContinue();
c.EnableShutdown();
c.StartAutomaticallyDelayed();
c.RunAsLocalSystem();
c.DependsOnEventLog();
c.DependsOnMsSql();
c.DependsOnIis();
}
);
return (int)exitCode;
}
}
}
Create the Service class
Create the Service.cs
code file as follows.
Note the comments inline, once again.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Owin.Hosting;
using Ninject;
using Topshelf;
namespace BackgroundProcessor
{
using Configs;
public class Service
{
private readonly IKernel kernel;
// inject the kernel into the Service class for later use
public Service(IKernel kernel)
: base()
{
this.kernel = kernel;
}
protected IKernel Kernel
{
get
{
return this.kernel;
}
}
protected IDisposable WebAppHolder
{
get;
set;
}
protected int Port
{
get
{
return 9000;
}
}
public bool Start(HostControl hostControl)
{
if (WebAppHolder == null)
{
// don't use the OwinStartupAttribute to bootstrap OWIN
//
// provide an Action<IAppBiulder> action to
// the WebApp.Start instead; this action will be
// called at start up
//
// adjust the signature of the Configure method to take
// an IKernel instance in addition to
// an IAppBuilder instance
//
// use this instance of the pre-instantiated kernel
// passed in to the Service class (and subsequently to
// the StartupConfig.Configure method) to plug in as the
// middleware
WebAppHolder = WebApp.Start
(
new StartOptions
{
Port = Port
},
appBuilder =>
{
new StartupConfig().Configure(appBuilder, Kernel);
}
);
}
return true;
}
public bool Stop(HostControl hostControl)
{
if (WebAppHolder != null)
{
WebAppHolder.Dispose();
WebAppHolder = null;
}
return true;
}
}
}
Create the StartupConfig class
Create the Configs\StartupConfig.cs
class as follows:
Note the comments in line, as before.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Http;
using Ninject;
using Ninject.Web.Common.OwinHost;
using Ninject.Web.WebApi.OwinHost;
using Owin;
namespace BackgroundProcessor
{
namespace Configs
{
public class StartupConfig
{
// add an extra parameter of type IKernel
public void Configure(IAppBuilder appBuilder, IKernel kernel)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.MapDefinedRoutes();
// plug the passed-in kernel into the Ninject middleware
appBuilder.UseNinjectMiddleware(() => kernel);
// start injecting objects into WepApi controllers
appBuilder.UseNinjectWebApi(config);
}
}
}
}
Create the rest of the classes to complete this sample application
Configs\RoutesConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace BackgroundProcessor
{
namespace Configs
{
public static class RoutesConfig
{
public static void MapDefinedRoutes(this HttpConfiguration config)
{
config.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
}
}
}
}
Modules\Module.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject.Modules;
using Ninject.Web.Common;
namespace BackgroundProcessor
{
using Contracts;
using Services;
namespace Modules
{
public class Module : NinjectModule
{
public override void Load()
{
Bind<IStringProvider>().To<DefaultStringProvider>().InRequestScope();
}
}
}
}
Contracts\IStringProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BackgroundProcessor
{
namespace Contracts
{
public interface IStringProvider
{
string HelloWorld
{
get;
}
}
}
}
Services\DefaultStringProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BackgroundProcessor
{
using Contracts;
namespace Services
{
public class DefaultStringProvider : IStringProvider
{
public static int Count = 0;
public string HelloWorld
{
get
{
return string.Format("Hello World #{0}!", ++Count);
}
}
}
}
}
And finally:
Controllers\TestController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace BackgroundProcessor
{
using Contracts;
namespace Controllers
{
[RoutePrefix("test")]
public class TestController : ApiController
{
private readonly IStringProvider stringProvider;
public TestController(IStringProvider stringProvider)
: base()
{
this.stringProvider = stringProvider;
}
protected IStringProvider StringProvider
{
get
{
return this.stringProvider;
}
}
[HttpGet]
[Route("")]
public HttpResponseMessage Index()
{
return Request.CreateResponse<string>(HttpStatusCode.OK, StringProvider.HelloWorld);
}
}
}
}
Compile, Execute & Test
Compile the solution and hit F5 to start it in debugging mode.
Navigate to http://localhost:9000:/test in your browser, and confirm that the output is as follows:
In Chrome:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Hello World #1!</string>
In Internet Explorer:
"Hello World #2!"
In Firefox:
<string>Hello World #3!</string>
NB: the number in the test string is incrementing because it is originating from a static variable, which is incremented per request effectively; this illustrates that Ninject's InRequestScope
is functioning correctly.
So there you have it, folks — how to create a Windows service using TopShelf, Ninject, OWIN and ASP.NET Web API.
Thanks for reading.