ApplicationServer boilerplate once was a rather valuable addition to Remoting SDK for .NET. The entire server application infrastructure became available literally via a single line of code:

public static void Main(string[] args)
{
   new ApplicationServer("Server App Name").Run(args);
}

Out-of-the-box support for command-line, GUI or Windows Service/Daemon run modes, customizable server parameters, transparent access to TLS support and other features allowed developers to focus on the business logic code rather on the boring infrastructure implementation details.

As  time passed, .NET Core and .NET 5 brought us developed infrastructure like de-facto built-in Dependency Injection support and new program initialization abstractions. So inevitable at some point ApplicationServer-based code started to look slightly obsolete. Even the recent addition of Dependency Injection containers (see here for details) support did not help ApplicationServer to start feeling native to the new world of .NET Core and .NET 5. To add insult to injury, the internal ApplicationServer  infrastructure did not play well with the new BackgroundService class provided by .NET Core / .NET 5.

The Solution

Still every challenge is an opportunity to evolve. So here it is - a support for 2 different application hosting packages: Microsoft.Extensions.Hosting and Topshelf.

Microsoft.Extensions.Hosting Support

Support for the Microsoft.Extensions.Hosting.* packages family is provided via the RemObjects.SDK.Extensions.Hosting.Microsoft NuGet package.

The minimal server application startup code that uses this package looks like

static class Program
{
	public static void Main(string[] args)
	{
		var host = CreateHostBuilder(args)
			.Build();

		host.Run();
	}

	public static IHostBuilder CreateHostBuilder(string[] args)
	{
		return Host.CreateDefaultBuilder(args)
				.ConfigureServices((hostContext, services) =>
				{
				})
				.UseApplicationServer(configuration =>
				{
				})
				.UseNetworkServer(server =>
				{
				})
				.UseConsoleLifetime();
	}
}

The callback method defined in the ConfigureServices method call is the place where the dependencies used in the application should be defined.

Then two application server-related methods are called.

The UseApplicationServer method call is the place where the main server properties like its name,  RODL namespace and self-signed certificate properties (if any) are defined and the main server infrastructure is initialized. This method call is required to make the application work as a Remoting SDK or Data Abstract server. If no custom values are provided then both application name and its RODL namespace will be set to ApplicationServer.

Then the UseNetworkServer method should be called. This method sets up the network server and  allows to fine-tune its options like the port or server channel type used, or even to define custom network server implementation (see below).

These two method calls initialize the server infrastructure similar to the one provided earlier by the ApplicationServer class.

What's more important is that new methods allow to inject Remoting SDK application server lifecycle as a native part of the .NET Core / .NET 5 application lifecycle. For example this allows to use Dependency Injection container provided by Microsoft, as well as use packages that allow the app to run as Windows Service or a Systemd daemon (Microsoft.Extensions.Hosting.WindowsServices and Microsoft.Extensions.Hosting.Systemd accordingly).

For example to turn the application into a Windows Service one would need to reference the Microsoft.Extensions.Hosting.WindowsServices package and to replace the UseConsoleLifetime method call with the UseWindowsService method call.

Once the application is built it, needs to be installed as a Windows Service. Unfortunately the Microsoft.Extensions.Hosting.WindowsServices package does not provide its own installer so the standard Windows Service installer should be used:

sc create [Service Name] BinPath=[full path to the server app exe file]

Several other commands are available that allow to start, stop or delete the Windows Service:

sc start [Service Name]
sc stop [Service Name]
sc delete [Service Name]

Topshelf Support

Support for the Topshelf framework is provided via the RemObjects.SDK.Extensions.Hosting.Topshelf package.

Topshelf describe itself as such:

Topshelf is a framework for hosting services written using the .NET framework. The creation of services is simplified, allowing developers to create a simple console application that can be installed as a service using Topshelf

More info on this framework can be found on its documentation site.

The minimal application startup code that uses this package looks like this:

static void Main(string[] args)
{
	HostFactory.Run(host =>
	{
		host.ConfigureServices(services =>
		{
		});
		host.UseApplicationServer(configuration =>
		{
		});
		host.UseNetworkServer(server =>
		{
		});

		host.UseHostedService();
	});
}

The ConfigureServices, UseApplicationServer and UseNetworkServer methods have exactly the same purpose as for the RemObjects.SDK.Extensions.Hosting.Microsoft package.

The RemObjects.SDK.Extensions.Hosting.Topshelf package uses Dependency Injection container provided by the Microsoft.Extnsions.DependencyInjection package.

Topshelf framework provides a rich set of command line arguments, including commands to register the Windows Service:

NetServer.exe install -servicename:"NetService"

More commands and their options descriptions can be hound here: http://docs.topshelf-project.com/en/latest/overview/commandline.html

Extensibility: Using custom Network Server implementation

The new infrastructure described in this article allows to define a custom implementation of pretty much any component used, starting from own ICertificateGenerator implementation up to the custom IAppServerEngine application core implementation. All what is required to do this is to re-register corresponding service in the services collection.

Still the most common task is to use a custom Network Server component implementation.

For example let's take a look at the following task:

Server should expose its services on an additional server channel.

The first step is to implement the custom Network server itself. A very straightforward custom Network Server implementation would look like

public class ExtendedNetworkServer : NetworkServer
{
	private IpHttpServerChannel _serverChannel;

	protected override void InternalStart()
	{
		base.InternalStart();

		this._serverChannel = new IpHttpServerChannel();
		this._serverChannel.Port = this.Port + 1;
		this._serverChannel.Dispatchers.AddRange(
			this.ServerMessages.Select(
               m => new MessageDispatcher(m.DefaultDispatcherName, m)));
		this._serverChannel.Open();
	}

	protected override void InternalStop()
	{
		this._serverChannel.Close();
		base.InternalStop();
	}
}

The next step is to register this class as the one that should be used as a Network Server implementation at runtime. All what is needed to this is to provide the custom Network Server class as a generic parameter in the UseNetworkServer method call:

. . .
   .UseNetworkServer<ExtendedNetworkServer>(server =>
. . .

Extensibility: Using custom App Server Engine implementation

AppServerEngine is a 'heart' of an application server. Its instance is used by Background Workers that implement the Windows Service to load the server configuration (including RODL composition), and to start or stop the NetworkServer instance.

In the very rare cases where a very deep customization is required, it is possible to use your own custom implementation of the IAppServerEngine interface.

Here is a sample implementation of a custom App Server Engine:

public class ExtendedAppServerEngine : AppServerEngine
{
	public ExtendedAppServerEngine(IServiceProvider serviceProvider, INetworkServer server,
			IAppServerConfiguration configuration, INetworkServerConfigurator networkServerConfigurator,
			ICertificateGenerator certificateGenerator)
	: base(serviceProvider, server, configuration, networkServerConfigurator, certificateGenerator)
	{
	}

	protected override void InternalStart()
	{
		System.Diagnostics.Debug.WriteLine(
            	"ExtendedAppServerEngine.InternalStart");

		base.InternalStart();
	}
}

Once defined, this class should be registered in the services collection as the implementation of the IAppServerEngine interface, replacing the default implementation registered by the UseApplicationServer method:

static void Main(string[] args)
{
	HostFactory.Run(host =>
	{
		host.ConfigureServices(services =>
		{
		});
		host.UseApplicationServer(configuration =>
		{
		});
		host.UseNetworkServer<ExtendedNetworkServer>(server =>
		{
		});

		// Register custom App Server Engine implementation
		host.ConfigureServices(services =>
		{
			services.Replace(
				ServiceDescriptor.Singleton<IAppServerEngine,
                		ExtendedAppServerEngine>());
		});

			host.UseHostedService();
	});
}

Migration to the new Hosting infrastructure

It is very easy to migrate an existing ApplicationServer-based app to to the new Hosting-based packages infrastructure.

This can be done in a few simple steps:

1. Remove all direct references to Remoting SDK or Data Abstract assemblies.

2. Reference required NuGet packages ( RemObjects.SDK.Server or RemObjects.DataAbstract.Server accordingly).

3. Reference the RemObjects.SDK.Extensions.Hosting.Microsoft or RemObjects.SDK.Extensions.Hosting.Topshelf package depending on the hosting framework you want to use.

4. Comment out existing Program.Main method

5. Reintroduce the Program.Main method:

5.1. For  RemObjects.SDK.Extensions.Hosting.Microsoft package

public static void Main(string[] args)
{
	var host = CreateHostBuilder(args)
		.Build();

	host.Run();
}

public static IHostBuilder CreateHostBuilder(string[] args)
{
	return Host.CreateDefaultBuilder(args)
			.ConfigureServices((hostContext, services) =>
			{
			})
			.UseApplicationServer(configuration =>
			{
			})
			.UseNetworkServer(server =>
			{
			})
			.UseConsoleLifetime();
}

5.2. For  RemObjects.SDK.Extensions.Hosting.Topshelf package

static void Main(string[] args)
{
	HostFactory.Run(host =>
	{
		host.ConfigureServices(services =>
		{
		});
		host.UseApplicationServer(configuration =>
		{
		});
		host.UseNetworkServer(server =>
		{
		});

		host.UseHostedService();
	});
}

6. Define necessary ApplicationServer and NetworkServer configuration options using methods UseApplicationServer and UseNetworkServer accordingly.

Conclusion

The new Hosting infrastructure provides more clean API and is easier to use than the old ApplicationServer boilerplate code. It fits better into the new approaches used by .NET Core and .NET 5 runtimes as well.

The ApplicationServer boilerplate is aimed more at the "old" .NET Framework while the new Hosting infrastructure uses benefits of the newer .NET Core and .NET 5 runtimes, like the features provided by the Microsoft.Extensions packages or native support of Dependency Injection.

While ApplicationServer boilerplate won't be removed from Remote SDK or anyhow deprecated or marked as obsolete, for new projects it is better to consider to use the new Hosting infrastructure instead of the ApplicationServer boilerplate code.

See also this topic in the documentation.