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.