Hosting in a Windows Service

Component: NServiceBus
NuGet Package NServiceBus (6.x)

Code walk-through

This sample shows how to host NServiceBus as a Windows service in process with support for streamlined debugging experience from Visual Studio.

NServiceBus comes with a host executable that abstracts much of the hosting complexity. Its features include installation, un-installation and configuring the Windows service. It provides these features though custom code and the use of libraries like TopShelf. Since the NServiceBus Host is a general solution with dependencies there are some things to keep in mind while using it.

The sample is a console application whose Main entry point detects if the application is run in interactive mode or not. When run in interactive mode, the service is manually created and invoked as any other C# class instance, otherwise the Run method is called to invoke the base ServiceBase class API.

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

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

        service.OnStop();
    }
}

The OnStart method, manually called when running in interactive mode and automatically called by the Windows service when running as a service, configures the Endpoint Instance:

protected override void OnStart(string[] args)
{
    AsyncOnStart().GetAwaiter().GetResult();
}

async Task AsyncOnStart()
{
    var endpointConfiguration = new EndpointConfiguration("Samples.WindowsServiceAndConsole");
    endpointConfiguration.SendFailedMessagesTo("error");
    endpointConfiguration.UsePersistence<LearningPersistence>();
    endpointConfiguration.UseTransport<LearningTransport>();
    endpointConfiguration.EnableInstallers();
    endpointInstance = await Endpoint.Start(endpointConfiguration)
        .ConfigureAwait(false);
    // run any startup actions on the bus
    var myMessage = new MyMessage();
    await endpointInstance.SendLocal(myMessage)
        .ConfigureAwait(false);
}

When the interactive application is shut down or the Windows service is stopped, the OnStop method is called to perform the required clean up:

protected override void OnStop()
{
    endpointInstance?.Stop().GetAwaiter().GetResult();
}

ServiceHelper

To detect if the current process is running as a service the parent process is checked.

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;
    }
}

Service Management

Management is done using the Service Control tool.

See also windows-service installation.

Installation

sc.exe create nsbSample binpath= "\"[Full Directory]\Sample.exe\""

Uninstall

sc.exe delete nsbSample

Related Articles


Last modified