Click here to Skip to main content
15,881,687 members
Articles / Web Development / HTML

Embed a web server in a windows service

Rate me:
Please Sign up or sign in to vote.
4.90/5 (23 votes)
12 Dec 2013CPOL9 min read 67.6K   3K   48   10
Using NancyFX to provide a web-interface to a Windows service using VS 2013

Introduction

A Windows service is a long running process that sits in the background, executing when needed. Services don't interact with the desktop, and this raises many issues, including the problem of controlling the service to a finer level than simply clicking "start service" "stop service" in the services control panel. This article describes using the NancyFX framework to provide a web browser interface to your Windows service, giving you much more control on what's going on and managing the internals for the service itself.

Image 1

(The ever so lovely image on my Windows Form is taken from the NancyFX website, yes, using it can be as simple as it says!)

The Basics

To get started, and to save the bother of spinning up a service immediately, we will start off the article using a standard Windows Form project, then convert it towards the end. I am calling this starter project "NancyWinForm".

Once you have your Windows Form project created, the first thing to do is open NuGet and add the following packages:

Nancy, Nancy.ViewEngines.Razor, Nancy.Hosting.Self. Nancy is the core package, Hosting.Self is required for self hosting in a DLL, EXE etc. without the need to sit on top of another host, and we are using the Razor engine on this occasion to parse data in our HTML view pages.

Image 2

In order to get Nancy up and running, there are a few basics we need to put in place. Having linked in the NuGet packages, the next thing is to add some using clauses to the top of the main form.

C#
using Nancy;
using Nancy.Hosting.Self; 
using System.Net.Sockets; 

The next thing we need to do is set up a form level object to run Nancy.

namespace NancyWinForm{
public partial class Main : Form
    {
        NancyHost host;   // < ---- form level object
        public Main()
        {
            InitializeComponent();
        }
    }
}

Next, we initialise the Nancy host object:

C#
public Main()       {
    InitializeComponent();
    string URL = "http://localhost:8080";
    host = new NancyHost(new Uri(URL));
    host.Start();
}

We start Nancy with a constructor that gives it a simple domain "localhost" and port "8080". You should pick something that is not already running on your system. For example, if you already have IIS or another web service running, then port 80 is most likely gone. We will discuss multiple binding and ports later in the article. Once we have told dear Nancy what to bind to, we tell her to whoosht up her skirts by calling "start" .... and if we open a browser to the URL we gave her, here's what we see...

Image 3

The Nancy authors have an ethos they call the “super-duper-happy-path” ... I think we can safely say we're happy. Smile | :)

Ok, the next thing is to take that little green monster out of Nancy's skirts and get in there ourselves <ahem>..

The next step is to create a new class of type "NancyModule", and give it a default path:

C#
    public class MainMod : NancyModule    
{
    public MainMod()
    {
     Get["/"] = x =>
       {
        return "Wee hoo! - no more little green monster...";
       };

     }
}

Here's a small gotcha - in a Windows Form, if you place this code above some that requires the designer (for example, a picture container with a picture of young nance), the compiler will complain that it wants to be first in line .... to fix this, just put the module class at the end of your form file.

A "NancyModule" can be added anywhere in the application and the framework will find it. It does this by iterating through the appdomain on startup looking for any NancyModules and hooking into them when they are located. Inside a NancyModule, we add "routes" or "paths". You can see in this case I added a root path "/". To start the ball rolling, I am simply returning a simple string to the browser. When we run, the output is as expected.

Image 5

Ok, text output is great but not terribly useful. The next thing to do is to add a HTML file we can work with. For this project, I have added a folder called "views" and in there created a new HTML file "skirts.html" (get with the program, the theme must continue!!).

Image 6

Let's add some simple content to the HTML file, and adjust the module get route handler to return the file.

HTML
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Nancy's skirts</title>
</head>
<body>

Heylow der...
</body>
</html> 
HTML
public MainMod()
 {
   Get["/"] = x =>
    {
       return View["views/skirts.html"];   //<-- sending back a view page
     };

 }

Hit F5 to run, stand well back...

Image 7

Oh dear, the little green monster is back ...what's happened is that in Nancy, we need to tell it a bit more than we would IIS. In this case, we need to tell the project to include the file in its output folder:

Image 8

Now when we run the monster is gone ... perfect...

Image 9

Having seen how to handle a GET, we will now handle POST. For this example, let's create two further HTML files "nice.html" and "very.html" - don't forget to set them to copy to output if newer. In our original HTML file, we will add a form to post...

<form method="post" action="NiceNancy">
    How nice is nancy? - please select...
<select name="HowNice"> 
HTML
 
<option value="1">Quite nice</option>

<option value="2">Very!</option> </select> <button type="submit">Submit</button> </form>

The idea is to send back one page of the user selects "quite nice", another if they select "very". Here is the POST code added to the Nancy module:

C#
        Post["/NiceNancy"] = y =>
{
if (Request.Form["HowNice"].HasValue)
 {
 if (Request.Form["HowNice"] == "1")
  { return View["views/nice.html"]; }
 else if (Request.Form["HowNice"] == "2")
 { return View["views/very.html"]; }
 else return View["views/skirts.html"];
};
 else return View["views/skirts.html"];
}

Note that like MVC/ASP, the form data is accessible via the "Request" object.

 

Giving Nancy the Boot....

Nancy is light, and unlike something heavy like IIS, it does not have as inbuilt facilities that you might expect. One of the things it needs guidance on is things like images and paths. Nancy will automatically serve up files (JS, CSS, Images..), but needs to be told where these things are stored, relative to itself. This is handled using the "Bootstrapper" class.

I am creating a new unit to store this code, "Bootstrapper.cs" - you can place it where you wish. To this file, I am adding the following use clauses:

C#
using Nancy;
using Nancy.Session;
using Nancy.Bootstrapper;
using Nancy.Conventions;
using System.Web.Routing;
using Nancy.TinyIoc; 

I also add a project reference to "system.web".

We need to derive the new class from "DefaultNancyBootstrapper" before we can start doing anything useful. We then add an override method "Configure conventions" to hook in the location of folders that will store images, etc.

C#
public class Bootstrapper : DefaultNancyBootstrapper
    {
        protected override void ConfigureConventions(NancyConventions nancyConventions)
        {
            base.ConfigureConventions(nancyConventions);
            nancyConventions.StaticContentsConventions.Clear();
            nancyConventions.StaticContentsConventions.Add
            (StaticContentConventionBuilder.AddDirectory("css", "/content/css"));
            nancyConventions.StaticContentsConventions.Add
            (StaticContentConventionBuilder.AddDirectory("js", "/content/js"));
            nancyConventions.StaticContentsConventions.Add
            (StaticContentConventionBuilder.AddDirectory("images", "/content/img"));
            nancyConventions.StaticContentsConventions.Add
            (StaticContentConventionBuilder.AddDirectory("fonts", "/content/fonts"));
        }

    }

Image 10

On startup, Nancy is now aware that we have for example a local path "/content/img" that is referred to by the virtual path "images". Let's test it from our main HTML file by dropping in an image and referring to it:

HTML
<form method="post" action="NiceNancy">
       <img src="images/SoWhat.jpg" />
       <br />

Yea, works!

Image 11

To tell Nancy where our images and other resources were stored, we customised things using the "bootstrapper" class. Bootstrapper is useful for all kinds of things as it allows us to hook into the Nancy IoC and inject supporting objects into our application. One of the things I wanted to achieve was to be able to have an object that would effectively store global variables. To do this, I created a class to load/save data using an XML file, and allow access to the data as needed.

The following is the simple "config-manager" class which I stored in a separate file "shared" (note the addition of the XML using clauses as I am using serialisation to save/load the data quickly).

C#
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace NancyWinForm
{
    public class ConfigInfo
    {
        public String TempFolder { get; set; }
        public String Username { get; set; }
        public String DateFormat { get; set; }
    }

    public class PracticeConfigManager
    {
        public ConfigInfo config;
        string _XMLFile = AppDomain.CurrentDomain.BaseDirectory + "MyConfig.xml";

        public void LoadConfig()
        {
            if (config == null)
            {
                config = new ConfigInfo();
            }

            if (!File.Exists(_XMLFile))
            {
                config.DateFormat = "dd/mmm/yyyy";
                SaveConfig();
            }

            XmlSerializer deserializer = new XmlSerializer(typeof(ConfigInfo));
            TextReader reader = new StreamReader(_XMLFile);
            object obj = deserializer.Deserialize(reader);
            config = (ConfigInfo)obj;
            reader.Close();
        }

        public void SaveConfig()
        {
            if (config != null)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(ConfigInfo));
                using (TextWriter writer = new StreamWriter(_XMLFile))
                {
                    serializer.Serialize(writer, config);
                }
            }
        }
    }
}

Having setup the class to save/load the config data, we now need to inject this into Nancy. We do this using the bootstrapper. In our bootstrap class, we override the method "Application Startup", and in here, register the ConfigManager class in the IoC container as follows:

C#
public class Bootstrapper : DefaultNancyBootstrapper
{

    protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
    {
        base.ApplicationStartup(container, pipelines);
        ConfigManager mgr;
        mgr = new ConfigManager();
        mgr.LoadConfig();
        container.Register<configmanager>(mgr);
    }</configmanager>

Having registered it, we now need to be able to access it whenever we get a GET/POST HTTP request. We do this by changing the signature of our main NancyModule:

From:

C#
public MainMod()

To:

C#
public MainMod(ConfigManager mgr)

As our ConfigManager is created and initialised at Nancy startup, we can therefore use it directly now in the NancyModule:

C#
Get["/config"] = x =>
{
   return mgr.config.DateFormat;
};

and the result...

Image 12

Ok, all looking good. The bootstrapper class is an extremely useful part of NancyFX and a place I find myself dipping into often.

When developing for the web, I prefer to steer clear these days of WebForms and use MVC almost exclusively - I like the clean separation of concerns and the ability to be "close to the metal" as it were. Nancy allows us to use Razor syntax in a familiar way to MVC. Let's say we had some values persisted that we wished to use on the web-page. We work with razor in Nancy the same way we do in MVC...

  1. Create a page "config.html" and add set properties to "add if newer"
    HTML
    <body>
    
        Model test page<br />
        <hr>
        <form method="post" action="SaveConfig">
    
            <input id="dateFormat" value="@Model.DateFormat" />
            <input id="username" value="@Model.Username" />
            <input id="tempFolder" value="@Model.TempFolder" />
    
            <button type="submit">Submit</button>
    
        </form>
    </body>
  2. Add new controller code, passing in the ConfigManager as the model:
    C#
    Get["/config"] = x =>
    {
       //return mgr.config.DateFormat;
        return View["views/config.html",mgr.config];
    };
    

Image 13

So we can see that the model data (the date format) has been rendered correctly into the HTML.

Binding to the IP Stack

Sometimes when setting up a HTTP server to listen for traffic, you want to specify a particular IP address, but sometimes you want to bind to all available IPs, in other words a wildcard binding. I found some useful code here that helps this happen. You send the method the port to bind to, it sends back an array of available bindings.

C#
private Uri[] GetUriParams(int port)
{
    var uriParams = new List<uri>();
    string hostName = Dns.GetHostName();

    // Host name URI
    string hostNameUri = string.Format("http://{0}:{1}", Dns.GetHostName(), port);
    uriParams.Add(new Uri(hostNameUri));

    // Host address URI(s)
    var hostEntry = Dns.GetHostEntry(hostName);
    foreach (var ipAddress in hostEntry.AddressList)
    {
        if (ipAddress.AddressFamily == AddressFamily.InterNetwork)  // IPv4 addresses only
        {
            var addrBytes = ipAddress.GetAddressBytes();
            string hostAddressUri = string.Format("http://{0}.{1}.{2}.{3}:{4}",
            addrBytes[0], addrBytes[1], addrBytes[2], addrBytes[3], port);
            uriParams.Add(new Uri(hostAddressUri));
        }
    }

    // Localhost URI
    uriParams.Add(new Uri(string.Format("http://localhost:{0}", port)));
    return uriParams.ToArray();
}

To implement, we need to start Nancy off slightly differently... the effect of this is to this force ACL to create network rules for new ports if they do not already exist.

C#
public Main()
{
    InitializeComponent();
    int port = 8080;
    var hostConfiguration = new HostConfiguration
    {
        UrlReservations = new UrlReservations() { CreateAutomatically = true }
    };
   host = new NancyHost(hostConfiguration, GetUriParams(port));
    host.Start();
}

There is a "gotcha" to be aware of ... Windows does not like anyone but admin running on things on anything but default ports - the solution is to open up the ports manually - you can also handle this manually outside in an installation process, like this:

netsh http add urlacl url=http://+:8888/app user=domain\user
+ means bind to all available IPs

Nancy Goes into Service...

To facilitate the quick testing of the project, we put it together in a Windows form application. The objective however was to use this very cool little micro web server as a gateway to access and interact more meaningfully with a Windows service. We have already put together the main code previously in the article, so the only thing I am going to do here is show you the setup of the service - it is available for download if you wish to see it in operation or hopefully, use the code yourself!

The normal template codebase for a service program starts like this:

C#
static class Program
{
    static void Main()
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new Service1()
        };
        ServiceBase.Run(ServicesToRun);
    }
}

We will made two adjustments - first, we will alter the code so that if you start it from the Visual Studio development environment, it will start and allow you to debug, finally, we will add the startup Nancy code itself.

C#
static class Program
{
    static void Main()
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new Service1()
        };
        ServiceBase.Run(ServicesToRun);
    }
}

changes to:

C#
static void Main()
{
    NancyHost host;
    string URL = "http://localhost:8080";
    host = new NancyHost(new Uri(URL));
    host.Start();

    //Debug code
    if (!Environment.UserInteractive)
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
    {
        new Service1()
    };
        ServiceBase.Run(ServicesToRun);
    }
    else
    {
        Service1 service = new Service1();
        // forces debug to keep VS running while we debug the service
        System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
    }
}

To install the service, we need to use the command line:

"C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe" "[Path]NancyService.exe"
(where [Path] is the location of your compiled service executable).

Le Voila, the finished product....

Image 14

That's it, a useful exercise and something to consider the next time you write a Windows service.

An Aside...

Using Nancy is of particular interest to me as many moons ago in a far and distant planet, I was heavily involved in http://www.indyproject.org/index.en.aspx. Indy was/is an open source sockets library heavily used in the Borland Delphi community and as part of this I wrote many demos, including a self hosted HTTP server ... how the tide turns.

History

  • 10/12/13 - Version 1 submitted
  • 11/12/13 - Content added
  • 21/12/13 - Final content added

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer SocialVoice.AI
Ireland Ireland
Allen is CTO of SocialVoice (https://www.socialvoice.ai), where his company analyses video data at scale and gives Global Brands Knowledge, Insights and Actions never seen before! Allen is a chartered engineer, a Fellow of the British Computing Society, a Microsoft mvp and Regional Director, and C-Sharp Corner Community Adviser and MVP. His core technology interests are BigData, IoT and Machine Learning.

When not chained to his desk he can be found fixing broken things, playing music very badly or trying to shape things out of wood. He currently completing a PhD in AI and is also a ball throwing slave for his dogs.

Comments and Discussions

 
QuestionOnly for static web page? Pin
Member 1291119921-Jun-18 19:39
Member 1291119921-Jun-18 19:39 
QuestionHow you actually controlling the service? Pin
peerd16-Dec-15 9:07
peerd16-Dec-15 9:07 
AnswerRe: How you actually controlling the service? Pin
DataBytzAI16-Dec-15 9:08
professionalDataBytzAI16-Dec-15 9:08 
QuestionAny particular reason for the innuendo? Pin
CHill6011-Dec-13 0:05
mveCHill6011-Dec-13 0:05 
AnswerRe: Any particular reason for the innuendo? Pin
DataBytzAI11-Dec-13 0:10
professionalDataBytzAI11-Dec-13 0:10 
GeneralRe: Any particular reason for the innuendo? Pin
ednrg11-Dec-13 3:27
ednrg11-Dec-13 3:27 
Good work. It's not offensive. People have to stop crying about every little thing. Jeez.
AnswerRe: Any particular reason for the innuendo? Pin
ednrg11-Dec-13 3:26
ednrg11-Dec-13 3:26 
AnswerRe: Any particular reason for the innuendo? Pin
enhzflep11-Dec-13 4:21
enhzflep11-Dec-13 4:21 
QuestionPretty basic simple Nancy Blog Pin
Nosfheratu10-Dec-13 19:43
Nosfheratu10-Dec-13 19:43 
AnswerRe: Pretty basic simple Nancy Blog Pin
DataBytzAI10-Dec-13 23:42
professionalDataBytzAI10-Dec-13 23:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.