ApplicationHost Infrastucture (.NET)
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 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 a support for 2 different application hosting packages was added: Microsoft.Extensions.Hosting
and Topshelf
.
Microsoft.Extensions.Hosting Support
Support for the Microsoft.Extensions.Hosting.*
packages family (including ...WindowsService
and ...Systemd
packages) 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 dependencies used in the application should be set up via the callback method in the ConfigureServices
method call.
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 by the ApplicationServer
-based infrastructure.
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 it is possible to use Dependency Injection container provided by Microsoft, as well as to use packages that allow the app to run as Windows Service or a Systemd daemon (Microsoft.Extensions.Hosting.WindowsServices
and Microsoft.Extensions.Hosting.Systemd
respectively).
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]
Also 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 describes 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 found here
Extensibility
Custom Network Server implementation
The new infrastructure described in this topic 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 via 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 =>
...
Custom App Server Engine implementation
AppServerEngine
is a 'heart' of an application server. It provides internal services used to load the server configuration (including RODL composition), and to start or stop the NetworkServer instance.
In the 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 needs to 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
Migration an existing ApplicationServer
-based app to the new Hosting-based packages infrastructure can be done in a few simple steps:
- Remove all direct references to Remoting SDK or Data Abstract assemblies.
- Reference required NuGet packages (
RemObjects.SDK.Server
orRemObjects.DataAbstract.Server
accordingly). - Reference the
RemObjects.SDK.Extensions.Hosting.Microsoft
orRemObjects.SDK.Extensions.Hosting.Topshelf
package depending on the hosting framework you want to use. - Comment out existing
Program.Main
method. - Reintroduce the
Program.Main
method as follows:-
-
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(); }
- 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(); }); }
-
For
- Define necessary
ApplicationServer
configuration options using methodUseApplicationServer
. - Define necessary
NetworkServer
configuration options using methodUseNetworkServer
.
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 introduced 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, for new projects it is better to consider to use the new Hosting infrastructure instead of the ApplicationServer
boilerplate code.