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 exe that abstracts much of the hosting complexity. Its many features include installation, un-installation and configuring the windows service. It provides these features though a reasonable amount of custom code and the use of some powerful libraries like TopShelf. Since the NServiceBus Host is a general solution with dependencies there are some drawback associated with 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 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 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