Our code

Now that we have created our project, we need to talk about how we populate our program.cs file. This is where our service will be spawned from, and where Topshelf will really show its power. Here's what our fully populated program.cs file looks like. I have highlighted some areas that I want you to pay close attention to and understand:

static void Main(string[] args)
{
var builder = new ContainerBuilder();

Registering our service:

builder.RegisterType<MemoryMicroService>()
.AsImplementedInterfaces()
.AsSelf()
?.InstancePerLifetimeScope();

Registering our logger:

builder.RegisterType<Logger>().SingleInstance();
var container = builder.Build();

Watching for log4net configuration changes:

XmlConfigurator.ConfigureAndWatch(new FileInfo(@".log4net.config"));
HostFactory.Run(c =>
{

Using Autofac as our IoC:

c?.UseAutofacContainer(container);
c?.UseLog4Net();
c?.ApplyCommandLineWithDebuggerSupport();
c?.EnablePauseAndContinue();
c?.EnableShutdown();

Trapping any general exceptions:

c?.OnException(ex => { Console.WriteLine(ex.Message); });
c?.Service<MemoryMicroService>(s =>
{
s.ConstructUsingAutofacContainer<MemoryMicroService>();
s?.ConstructUsing(settings =>
{
var service = AutofacHostBuilderConfigurator.LifetimeScope.Resolve<MemoryMicroService>();
return service;
});
s?.ConstructUsing(name => new MemoryMicroService());

What to do when started, paused, continued, or shut down:

s?.WhenStarted((MemoryMicroService server, HostControl host) => server.OnStart(host));
s?.WhenPaused(server => server.OnPause());
s?.WhenContinued(server => server.OnResume());
s?.WhenStopped(server => server.OnStop());
s?.WhenShutdown(server => server.OnShutdown());
});

Running as a network service:

c?.RunAsNetworkService();

Starting automatically but giving dependencies a chance to load first:

c?.StartAutomaticallyDelayed();
c?.SetDescription(string.Intern("Memory Microservice Sample"));
c?.SetDisplayName(string.Intern("MemoryMicroservice"));
c?.SetServiceName(string.Intern("MemoryMicroService"));

If installed as a service, how to handle errors:

c?.EnableServiceRecovery(r =>
{
r?.OnCrashOnly();
r?.RestartService(1); //first
r?.RestartService(1); //second
r?.RestartService(1); //subsequents
r?.SetResetPeriod(0);
});
});

We'll take each section one by one and describe what's going on.

The first thing we are going to do is create our Autofac container by creating a builder object and registering our MemoryMicroService and Logger classes. Once we are done registering all of our types and interfaces, we can build our container:

var builder = new ContainerBuilder();
// Service itself
builder.RegisterType<MemoryMicroService>()
.AsImplementedInterfaces()
.AsSelf()
?.InstancePerLifetimeScope();
builder.RegisterType<Logger>().SingleInstance();
var container = builder.Build();

Next, we tell our XmlConfigurator object of log4net to use the log4net configuration file for configuration, and to listen for changes. Listening for changes will also update the configuration objects if changed:

XmlConfigurator.ConfigureAndWatch(new FileInfo(@".log4net.config"));

Now comes the fun part. The majority of our code is going to sit inside our HostFactory class, which has the Run method. Let's start by taking it one step at a time and breaking it down:

HostFactory.Run(c =>
{
c?.UseAutofacContainer(container);
c?.UseLog4Net();
c?.ApplyCommandLineWithDebuggerSupport();
c?.EnablePauseAndContinue();
c?.EnableShutdown();
c?.OnException(ex => { Console.WriteLine(ex.Message); });

This first part tells the program that, instead of the internal IoC container of Topshelf, we are using the Autofac container instead. Next, we will use log4net as our logger. Then, our next command applies command-line parameter support with a debugger. Next, we enable pause and continue, as well as shutdown commands, allowing our microservice to respond to and process these Windows service commands. Finally, we handle any exceptions by writing them to the console. In a production environment, you might want to log exceptions to a database.

Here's the following section:

s?.ConstructUsing(name => new MemoryMicroService());
s?.WhenStarted((MemoryMicroService server, HostControl host) => server.OnStart(host));
s?.WhenPaused(server => server.OnPause());
s?.WhenContinued(server => server.OnResume());
s?.WhenStopped(server => server.OnStop());
s?.WhenShutdown(server => server.OnShutdown());

First, we tell Topshelf we are using our constructor to create our microservice. The next five commands tell Topshelf what to do with each of the start, stop, pause, continue, and shutdown commands. For us, they call a similarly named command inside our microservice object. Remember that the base class implements the basic logic, so if all we want is to just log that the command is received and do nothing else, then just call the base object.

The following command will allow the microservice to run as a network service. Feel free to quickly change this to whatever policies your organization adheres to as far as running Windows services:

c?.RunAsNetworkService();

This equates to the service Log On properties stored for each service. Services can run as a network or Local System account:

By the way, do you remember which account does what? In case you don't have it memorized, here are the basic guidelines:

  • Local System account is a very high-privileged built-in account. It has extensive privileges on the local system and acts as the computer on the network. The actual name of the account is NT AUTHORITYSYSTEM.
  • The Local Service account is a built-in account that has the same level of access to resources and objects as members of the users group. This limited access helps safeguard the system if individual services or processes are compromised. Services that run as the Local Service account access network resources as a null session without credentials. The actual name of the account is NT AUTHORITYLOCAL SERVICE.
  • The Network Service account is a built-in account that has more access to resources and objects than members of the users group. Services that run as the Network Service account access network resources by using the credentials of the computer account. The actual name of the account is NT AUTHORITYNETWORK SERVICE.

The following command will allow the microservice to start automatically, but delayed, in case dependent services need to start first:

c?.StartAutomaticallyDelayed();

Next, we have to tell Topshelf the service name, display name, and description of our microservice. The service name is optional and, by default, uses the namespace of program.cs (the calling assembly type namespace). The service name should never contain spaces or other whitespace characters. Each service on our system must have a unique name. Also, the description and display names are optional and default to the service name:

c?.SetDescription(string.Intern("Memory Microservice Sample"));
c?.SetDisplayName(string.Intern("MemoryMicroservice"));
c?.SetServiceName(string.Intern("MemoryMicroService"));

Finally, we inform Topshelf how we want our microservice to handle recoveries in the event of a fatal execution sequence. In this case, we are only going to restart on a crash, and restart on the first, second, and subsequent attempts:

c?.EnableServiceRecovery(r =>
{
r?.OnCrashOnly();
r?.RestartService(1); //first
r?.RestartService(1); //second
r?.RestartService(1); //subsequents
r?.SetResetPeriod(0);
});

These fields map to the fields in the service control manager (service properties), as you can see in the following screenshot:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset