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