Dependency Injection containers API (.NET)
Introduction
This article will discuss APIs provided by Dependency Injection containers support code in Remoting SDK, its performance and other technical details.
Backwards Compatibility
The first question that might arise is "What if I do not need all this stuff? My application is very simple so I just want to use the approaches I am used to."
Well, the answer is simple - just do not do anything. The DI support is designed to be as unobtrusive and transparent as possible so all existing code will work without any changes. Nothing will break. Also there are no performance hits or broken code in the case you won't touch DI container in your code.
Under the hood, Remoting SDK will still do all the work for you. In fact, all these years SDK already did use a code that did act like a DI container, just a very simple one.
Container API
Let's take a closer look at the initialization code from the end of the main article:
var container = new SimpleContainer();
container.RegisterSingleton(typeof(ILogger), new DebugLogger());
container.RegisterSDK(server.NetworkServer);
container.RegisterServices();
server.DependencyResolver = container;
Here an instance of Remoting SDK built-in container is created.
Then a singleton registration call is performed. The container will register an instance of the DebugLogger
class as implementing the ILogger
interface. After this registration, that instance will be used as a parameter value while instantiating any types registered in the DI container.
The RegisterSDK
method is an extension method that registers SessionManager
and EventSinkManager
instances in the container. Note that passing a INetworkServer
instance as a parameter of this method ensures that Application Server infrastructure initializes its NetworkServer
instance and its SessionManager
and EventSinkManager
instances before they are registered in the container.
Then a RegisterServices
call is issued. This extension method finds all non-abstract SDK services in the server application and registers them in the container.
The SimpleContainer
can register SDK services on the fly, so this call can be omitted. Still it is highly recommended to call the RegisterServices
method anyway. In this case SimpleContainer
will validate all services registrations, so any container misconfiguration (like missing dependencies) will be detected immediately at server application startup.
For example let's take a look at the code snippet above. Commenting out both .RegisterSingleton
and .RegisterServices
calls will allow the server application to start. Unfortunately any FooService
call will fail because its constructor requires an ILogger
instance while there is none provided. The main issue here is that an exception will be raised only when the service is called.
However, uncommenting the .RegisterServices
call will result in the server application failing immediately at startup, allowing you to easily detect and fix any misconfiguration.
Containers Without ApplicationServer
Older server applications (ones that do not use the Application Server boilerplate infrastructure) can also benefit from container support.
The server.DependencyResolver
property used in the code earlier is just an alias for the RemObjects.SDK.Server.Engine.ObjectActivator
static property. So this code will allow to configure a DI container as well:
var container = new SimpleContainer();
container.RegisterSingleton(typeof(ILogger), new DebugLogger());
container.RegisterSDK(server.NetworkServer);
container.RegisterServices();
RemObjects.SDK.Server.Engine.ObjectActivator = container;
Note: There is no need to add any code to your existing apps. All existing apps will continue to work without any code changes required.
Registration API
Remoting SDK and Data Abstract provide a number of methods that simplify the task of registering SDK services and infrastructure in the DI container. These methods are declared as extension methods, so it is possible to easily switch from the Remoting SDK container to any 3rd party DI container like Unity with as less code changes as possible.
Methods:
-
RemObjects.SDK.Server
namespaceRegisterSDK(INetworkServer)
- registersSessionManager
andEventSinkManager
instances used by the providedINetworkServer
instanceRegisterServices
- register all non-abstract SDK services defined in the server application
-
RemObjects.DataAbstract.Server
namespaceRegisterDataAbstract
- registers ConnectionManager instance. Also registers anILocalServiceActivator
instance (see below).
The ILocalServiceActivator
interface provides a set of helper methods that should simplify acquiring local Data Services. At this moment the default implementation of this interface provides 2 methods that are aliases of the corresponding RemObjects.DataAbstract.Server.Engine
methods: AcquireService
and ReleaseService
Simple Container, Described
The built-in DI container exposes the following methods:
void Register(Type serviceType);
void Register(Type serviceType, Type implementationType);
void Register(Type serviceType, Func<Object> factory);
void RegisterSingleton(Type serviceType);
void RegisterSingleton(Type serviceType, Type implementationType);
void RegisterSingleton(Type serviceType, Object instance);
void Register<TService>();
void Register<TService, TImplementation>();
void Register<TService>(Func<Object> factory);
void RegisterSingleton<TService>();
void RegisterSingleton<TService, TImplementation>();
void RegisterSingleton<TService>(TService instance);
The Register
methods allow you to register types that should be instantiated anew on every request (like SDK services):
Register(Type serviceType)
- registersserviceType
in the containerRegister(Type serviceType, Type implementationType)
- registersimplementationType
in the container as a type implementing theimplementationType
, Func<Object> factory)- registers the
serviceType` in the container and sets a custom method that should be used to instantiate it. Can be used if the class being registered requires special actions to be instantiated (f.e. a type should be instantiated via a factory method call)
The RegisterSingleton
methods allow you to register types that should be instantiated only once:
RegisterSingleton(Type serviceType)
- registers singleton of typeserviceType
RegisterSingleton(Type serviceType, Type implementationType)
- registers singleton ofserviceType
as the type implementing theimplementationType
. Can be used to register the concrete implementation of abstract base class.RegisterSingleton(Type serviceType, Object instance)
- registers the concrete instance as theserviceType
implementation.
Generic methods are used as aliases for their non-generic versions.
Integration of 3rd-party Containers
It is possible to use almost any 3rd part Dependency Injection container instead of the built-in Remoting SDK container.
The recommended approach is to create a wrapper class around the container implementing one of the IDependencyContainer
or IObjectActivator
interfaces. Then this wrapper instance should be assigned to either the server.DependencyResolver
property (for IDependencyContainer
implementation) or directly to the RemObjects.SDK.Server.Engine.ObjectActivator
property.
The IObjectActivator
exposes a single GetInstance
method. This method accepts a service type and returns an instance of this type.
The IDependencyContainer
interface provides 2 additional methods:
Register(Type serviceType)
- registers theserviceType
in the container.RegisterSingleton(Type serviceType, Object instance)
- registers the concrete instance as theserviceType
implementation.
In most cases it is recommended to implement the IDependencyContainer
interface as it will allow you to use the Registration API methods, saving you from manually writing the code required to register SDK services and SDK/DA infrastructure.
Sample Wrappers: LightInject
This is a sample wrapper for the LightInject
(https://www.lightinject.net/) DI container:
public sealed class LightInjectContainer : IDependencyContainer
{
private readonly ServiceContainer _container;
public LightInjectContainer()
{
this._container = new ServiceContainer(ContainerOptions.Default);
}
public object GetInstance(Type objectType)
{
return this._container.GetInstance(objectType);
}
public void Register(Type serviceType)
{
this._container.Register(serviceType);
}
public void RegisterSingleton(Type serviceType, object instance)
{
this._container.RegisterInstance(serviceType, instance);
}
}
Once defined this container wrapper can be used as:
var container = new LightInjectContainer();
container.RegisterSingleton(typeof(ILogger), new DebugLogger());
container.RegisterSDK(server.NetworkServer);
container.RegisterServices();
server.DependencyResolver = container;
Note how easy it is to replace the DI container used.
Sample Wrappers: Autofac
As another example let's look at Autofac (https://autofac.org/):
A wrapper class for this DI container would look like this:
public sealed class AutofacContainer : IDependencyContainer
{
private IContainer _container;
private ContainerBuilder _builder;
public AutofacContainer()
{
this._builder = new ContainerBuilder();
}
public void Build()
{
this._container = this._builder.Build();
this._builder = null;
}
public object GetInstance(Type objectType)
{
return this._container.Resolve(objectType);
}
public void Register(Type serviceType)
{
this._builder.RegisterType(serviceType);
}
public void RegisterSingleton(Type serviceType, object instance)
{
this._builder.Register(_ => instance).As(serviceType).SingleInstance();
}
}
This wrapper requires an additional container-specific step to be used:
var container = new AutofacContainer();
container.RegisterSingleton(typeof(ILogger), new DebugLogger());
container.RegisterSDK(server.NetworkServer);
container.RegisterServices();
container.Build(); // This call performs actual Autofac container initialization
server.DependencyResolver = container;
Still most of the code is the same for any DI container used.
Conclusion
We hope that you will find your use for this feature and that Dependency Injection containers support will help you to write even more reliable and clean code.