Super-duper-happy Nancy-based API… as a Windows Service

David Knaack (@AC0KG)
november 3rd, 2014

Nancy is a lightweight framework for building HTTP-based services on .NET and Mono. The goal of the framework is to stay out of the way as much as possible and provide a “super-duper-happy-path” to all interactions. This approach to sensible defaults and conventions means that it is very easy to write a stand-alone self-hosted web site or API that runs as a desktop application. In this post, I’m going to discuss the equivalent happy-path for deploying such an application as a Windows Service.

The MSDN Windows Service walkthrough details the direct approach to writing a Windows Service application. Using the Windows Service project template is a good option if you need support for features like delayed start, pausing, requesting more shutdown time, or startup dependencies.

However, every time I’ve written a Windows Service all I needed was a way to register a service so that the Service Control Manager can start and stop it automatically with Windows. The full-featured (and somewhat clunky) Windows Service project template doesn’t offer a simple, lightweight option for hosting services. To address this shortcoming, I wrote a Windows Service package: AC0KG.WindowsService.

To demonstrate the use of AC0KG.WindowsService I’ll present a very simple self-hosted Nancy-based Web API that uses AC0KG.WindowsService to register and run the application as a Windows Service.

Start off in Visual Studio 2013 with a new project using the “Nancy Empty Application with self-hosting” Nancy template (the Nancy templates can be installed from the MSDN). This will give you a console application with a NancyHost and no routes.

namespace NancyApplication1
{
using System;
using Nancy.Hosting.Self;

class Program
{
static void Main(string[] args)
{
var uri = new Uri("http://localhost:3579");

Console.WriteLine("Your application is running on " + uri);
Console.WriteLine("Press any [Enter] to close the host.");

using (var host = new NancyHost(uri))
{
host.Start();
Console.ReadLine();
}
}
}
}

Nancy uses modules to define the behavior of the application. A module is created by inheriting from the NancyModule class. In the module constructor you can define routes and actions. When the application starts Nancy will scan the application domain for modules, so no further configuration is required to enable the module.

Create a new public class, DemoApi.cs, inheriting from NancyModule. In the constructor define a route for root, "/", and assign an action that returns a sample JSON object. This route will be our demo API.

namespace NancyApplication1
{
using Nancy;

public class DemoApi : NancyModule
{
public DemoApi()
{
Get["/"] = _ => Response.AsJson(new { Property = "value"});
}
}
}

Extending the API with more useful endpoints or adding views is easy. Check out the “Defining routes” docs for detailed documentation of the route definition syntax.

The application can now be run as a console application, and will respond to requests to the "/" endpoint with a JSON object. Running as a console application, while very useful during development, isn’t practical for Windows production environments. To run as a Windows Service, the application will need a class inheriting from ServiceBase. There is a project template for service applications, but since we started with the Nancy template, we would need to bring in the support for the service class separately. Also, that template does not support self-registration, so users will need to use a separate utility to register and unregister the application. It’s not difficult, but neither is it the super-duper-happy-path.

Instead, from the package manager console, install AC0KG-WindowsService:

PM> Install-Package AC0KG-WindowsService

In Program.cs just above the Program class definition, add a new empty class Service inheriting from AC0KG.WindowsService.ServiceShell. To set the short name of the service annotate Service with the ServiceName attribute. Visual Studio recognizes ServiceShell as a visual class, but we don’t want any designer support here, so also annotate Service with the System.ComponentModel.DesignerCategory with a blank argument to tell VS not to use the designer. You will need to add using lines for AC0KG.WindowsService and System.ComponentModel:

[DesignerCategory("")]
[ServiceName("DemoApiService")]
class Service : ServiceShell { }

ServiceShell is a descendant of ServiceBase which accepts user-provided Start and Stop Actions. It takes the name provided in the ServiceName annotation and checks the command line for install/uninstall parameters.

Main() must be modified to use the Service class instead of running the NancyHost directly. The Service class provides a static method, StartService(), with two Action parameters for startup and shutdown. A third parameter is used to indicate whether the application should run as a console app or as a Service app:

static void Main(string[] args)
{
var uri = new Uri("http://localhost:3579");

Console.WriteLine("Your application is running on " + uri);
Console.WriteLine("Press any [Enter] to close the host.");

using (var host = new NancyHost(uri))
{
Service.StartService<Service>(
host.Start,
null,
Environment.UserInteractive);
}
}

In most cases the Environment.UserInteractive property is a good indicator of whether the application should be run as a console application.

Since the NancyHost Start() function happens to match the Action parameter signature that StartService uses, we can pass it directly, as shown. If the Start function requires arguments, it can be wrapped in a lambda.

This simple service doesn’t need to do anything on shutdown, so I’m passing null for the second argument. If you are using a service host with a shutdown function it would be passed there.

The application will need a service installer to handle registering the service with the Windows Service Control Manager. The InstallerShell class is a service installer, and also allows us to set the service display name and description with an annotation.

Add an Installer class inheriting from InstallerShell. Annotate it with RunInstaller and ServiceName:

[RunInstaller(true)]
[ServiceName("DemoApiService",
DisplayName = "Demo API Service",
Description = "Demonstration NancyFX API Windows Service")
]
public class Installer : InstallerShell { }

When the ServiceShell finds install or uninstall command line parameters, it will run the service installer which will look for classes annotated with the RunInstaller attribute. When the Installer class is registered, it will appear in the list of services with the display name and description provided in the ServiceName attribute.

To enable command line processing a call to Service.ProcessInstallOptions() should be made early in the application startup. If '-install' or '-uninstall' are found in the application arguments the service will be installed or uninstalled:

static void Main(string[] args)
{
if (Service.ProcessInstallOptions(args))
return;
[...]

Now when the application is run interactively, such as from Visual Studio or the desktop it will run in a console window and wait for a key press to exit. When started by the Windows Service Control Manager it will start as a service task and run until the SCM sends a stop command.

Tags: technology dotnet csharp web-services