Windows Service Hosting

Component: NServiceBus
NuGet Package NServiceBus (3.x)
Standard support for version 3.x of NServiceBus has expired. For more information see our Support Policy.

Running inside a Windows Service is the most common approach to hosting NServiceBus.

Example Windows Service Hosting

  • Create a new Console Application.
  • Reference System.ServiceProcess.dll.
  • Change the program to inherit from. ServiceBase
[DesignerCategory("Code")]
class ProgramService :
    ServiceBase
{
    IBus bus;

    static void Main()
    {
        using (var service = new ProgramService())
        {
            if (ServiceHelper.IsService())
            {
                Run(service);
                return;
            }
            service.OnStart(null);

            Console.WriteLine("Bus started. Press any key to exit");
            Console.ReadKey();

            service.OnStop();
        }
    }

    protected override void OnStart(string[] args)
    {
        var configure = Configure.With();
        configure.DefineEndpointName("EndpointName");
        bus = configure.UnicastBus()
            .CreateBus()
            .Start(
                startupAction: () =>
                {
                    configure.ForInstallationOn<Windows>().Install();
                });
    }

    protected override void OnStop()
    {
        ((IDisposable) bus)?.Dispose();
    }
}
Since Environment.UserInteractive always returns true on .NET Core, ServiceHelper.IsService() is used to provide a dual console/service experience, i.e. this process can be executed from the command line or run as a Windows service. Note that if there is no intention to target .NET Core, then Environment.UserInteractive can be used.
static class ServiceHelper
{
    public static bool IsService()
    {
        using (var process = GetParent(Process.GetCurrentProcess()))
        {
            return process != null && process.ProcessName == "services";
        }
    }

    static Process GetParent(Process child)
    {
        var parentId = 0;

        var handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

        if (handle == IntPtr.Zero)
        {
            return null;
        }

        var processInfo = new PROCESSENTRY32
        {
            dwSize = (uint) Marshal.SizeOf(typeof(PROCESSENTRY32))
        };

        if (!Process32First(handle, ref processInfo))
        {
            return null;
        }

        do
        {
            if (child.Id == processInfo.th32ProcessID)
            {
                parentId = (int) processInfo.th32ParentProcessID;
            }
        } while (parentId == 0 && Process32Next(handle, ref processInfo));

        if (parentId > 0)
        {
            return Process.GetProcessById(parentId);
        }
        return null;
    }

    static uint TH32CS_SNAPPROCESS = 2;

    [DllImport("kernel32.dll")]
    public static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);

    [DllImport("kernel32.dll")]
    public static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESSENTRY32
    {
        public uint dwSize;
        public uint cntUsage;
        public uint th32ProcessID;
        public IntPtr th32DefaultHeapID;
        public uint th32ModuleID;
        public uint cntThreads;
        public uint th32ParentProcessID;
        public int pcPriClassBase;
        public uint dwFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szExeFile;
    }
}

Bootstrapping NuGet

There is a Bootstrapping starter package on NuGet that automates most of the above code.

The NServiceBus.Bootstrap.WindowsService package will not work properly using the PackageReference in project files, for more information see nuget documentation. To get it working, use the Packages.config NuGet package manager format in Visual Studio 2017 or higher.

How to use

Create a new Console Application (.NET 4.5.2 or higher) and install the NuGet package. A minimal NServiceBus configuration will be setup along with a ProgramService.cs class that can be used as both a interactive console for development purposes and a windows service for production use.

This will also delete the default Program.cs since it is superseded by ProgramService.cs.

Single use NuGet

This is a "single use NuGet". So it after install, and adding code to the project, it will remove itself. Since it is single use there will never be any "upgrade", this is a "use and then own the code" approach.

For new self hosting applications

This NuGet helps get started on a new self hosted NServiceBus application. For existing NServiceBus projects the problems this NuGet attempts to address are most likely already solved.

Transport

The LearningTransport is selected by default.

Choose a production grade transport before deploying to production.

Persistence

No persistence is needed since the LearningTransport is used and no sagas is present. Consult the indivudal transport and Saga documentation for specific storage need.

Installation

When self-hosting a Windows service the startup code is in full control of installation. Windows supports these features though the use of the Service Control tool. For example a basic install and uninstall commands would be:

sc.exe create SalesEndpoint binpath= "c:\SalesEndpoint\SalesEndpoint.exe"
sc.exe delete SalesEndpoint

For completeness here are some other common usages of the Service Control tool:

Service Name

The Windows Service Name can be configured, at creation time, as follows:

sc.exe create [ServiceName] binpath= [BinaryPathName]
sc.exe create SalesEndpoint binpath= "c:\SalesEndpoint\SalesEndpoint.exe"

Display Name

Display name can be configured, at creation time, using the displayname argument:

sc.exe create [ServiceName] displayname= [Description] binpath= [BinaryPathName]
sc.exe create SalesEndpoint displayname= "Sales Endpoint" binpath= "c:\SalesEndpoint\SalesEndpoint.exe"

Description

Description can be changed, after the service has been created, using the sc description command.

sc.exe description [ServiceName] [Description]
sc.exe description SalesEndpoint "Service for hosting the Sales Endpoint"

Service Dependencies

Service dependencies can be configured after the service has been created using the sc config command.

sc.exe config [ServiceName] depend= <Dependencies(separated by / (forward slash))>
sc.exe config SalesEndpoint depend= MSMQ/MSDTC/RavenDB

Restart Recovery

Windows has a Windows Service Recovery mechanism that makes sure that a crashed service will be restarted.

The endpoint can fail when using the NServiceBus Host or when self hosting and implementing a critical error handler that exits the process in case a critical error occurs.

If Windows Service Recovery is not configured, then the message processing will halt. Therefore it's important to configure Recovery options, when hosting an NServiceBus endpoint as a Windows Service.

The Recovery options can be adjusted via Services dialog or via sc.exe. Note that the command line tool has advanced configuration options.

Configuring Service Recovery via sc.exe

The default restart duration is 1 minute when enabling recovery via the Windows Service management console, but a different restart duration may be defined for the subsequent restarts using sc.exe.

The following example will restart the service after 5 seconds the first time, after 10 seconds the second time and then every 60 seconds. The restart service count is reset after 1 hour (3600 seconds) of uninterrupted work since the last restart.

sc.exe failure [ServiceName] reset= [seconds] actions= restart/[milliseconds]/restart/[milliseconds]/restart/[milliseconds]
sc.exe failure SalesEndpoint reset= 3600 actions= restart/5000/restart/10000/restart/60000

Configuring Service Recovery via Windows Service properties

Open the services window, select the endpoint Windows service and open its properties. Then open the Recovery tab to adjust the settings:

Windows Service properties Recovery tab

Restart durations are only configurable using sc.exe.

Username and Password

Username and password can be configured, at creation time, using the obj and password parameters.

sc.exe create [ServiceName] obj= [AccountName] password= [Password] binpath= [BinaryPathName]
sc.exe create SalesEndpoint obj= MyDomain\SalesUser password= 9t6X7gkz binpath= "c:\SalesEndpoint\SalesEndpoint.exe"

Start Mode

The Windows Service start mode can be configured, at creation time, using the start parameter.

sc.exe create [ServiceName] start= {auto | demand | disabled} binpath= [BinaryPathName]
sc.exe create SalesEndpoint start= demand binpath= "c:\SalesEndpoint\SalesEndpoint.exe"

Uninstall

A service can be uninstalled using the sc delete command.

sc.exe delete [ServiceName]
sc.exe delete SalesEndpoint

Compared to NServiceBus Host

Code similarity

When using a self hosted approach, inside a windows service, this code will share many similarities with other hosting code such as send only clients and web service hosting. This similarity will result in more consistent (and hence easier to understand code and increased opportunities for code re-use.

Performance

Self host is a specific solution to a problem that can be more specialized and has less dependencies. This results in

  • Reduced memory usage
  • Faster startup/debugging time
  • Smaller deployment size

Debugging

The NServiceBus Host is a non-trivial piece of software, especially when including its dependency on TopShelf. As such the NServiceBus Host can add complexity to debugging issues. Taking full control via self hosting allows less layers of abstraction which result in a simpler debugging experience.

Controlling the entry point

When using the NServiceBus Host, the host is calling the endpoint configuration code. As such the configuration code and behaviors (such as startup and shutdown) need to plug into very specific APIs. For example IWantCustomLogging, IWantCustomInitialization, IWantToRunWhenEndpointStartsAndStops and IConfigureLogging. If the scenario is inverted, i.e. the developers code calls NServiceBus configuration, then the requirement for these APIs no longer exists.

Samples

Related Articles


Last modified