Introduction

One of the pillars of the modern software development industry are the SOLID principles. These code design principles provide a great (and solid) solution to the major software development challenge: how to cope with increasing code complexity without being buried under tons of technical debt?

Dependency Injection and Inversion of Control are a very important part of SOLID. Dependency injection containers do all the work of resolving dependencies while you concentrate on the code itself. Properly used, they improve code readability and code testability as well.

In this blog post I won't cover why or where you should you use dependency containers. Instead we will take a look at the dependency injection pattern applied to the Remoting SDK (and Data Abstract) server applications (Spoiler: a new feature coming this November is introduced in this article).

The Task

Let's assume that we have the following task:

Create a simple server application with a single-method service. This service method should be able to log its calls into either a debugger output or console.

How would we implement this in the traditional way?

A simple Code-First implementation based on the Application Server boilerplate code would look like this:

using RemObjects.SDK.Server;

namespace SampleServer
{
	static class Program
	{
		public static int Main(string[] args)
		{
			ApplicationServer server = new ApplicationServer("SampleServer");
			server.Run(args);
			return 0;
		}
	}

	// Sample Service
	[Service]
	public class FooService : Service
	{
		public FooService()
		{
		}

		[ServiceMethod]
		public string Foo(string value)
		{
            // TODO: We need to somehow call logger here
			return value.ToUpper();
		}
	}

	public interface ILogger
	{
		void Log(string message);
	}

	class ConsoleLogger : ILogger
	{
		public void Log(string message)
		{
			System.Console.WriteLine(message);
		}
	}

	class DebugLogger : ILogger
	{
		public void Log(string message)
		{
			System.Diagnostics.Debug.WriteLine(message);
		}
	}
}

And here comes the main issue - we need to somehow pass a ConsoleLogger or DebugLogger instance to the FooService instance. Note that FooService is instantiated during client request processing, so we cannot just create an instance in the Main method code. To add insult to injury, we also need to be able to use one of two possible ILogger implementations (ConsoleLogger or DebugLogger). What are our options in this case?

We cannot just define the service method code as:

		[ServiceMethod]
		public string Foo(string value)
		{
			this._logger.Log("Foo: " + value);
			return value.ToUpper();
		}

The logger instance has to be somehow provided first to the Foo method code.

To provide this value we can:

  1. Create a new ILogger instance in the service constructor each time it is called. Cons here are that change from ConsoleLogger to DebugLogger or back would require to change the service code. Also it is quite possible that the service needs to use a logger instance that is defined and configured somewhere else, so it just cannot instantiate it each time (f.e. ConnectionManager in Data Abstract).
  2. Define a static field and put an ILogger instance at application startup. Cons here are that dependency between FooService and ILogger is not clear because it is not possible to find out these dependencies without reading the service source code. Also a single shared ILogger instance might result in significant difficulties in unit testing (f.e. one test might break the internal state of the shared instance thus resulting in a failure in another completely unrelated test).
  3. Use a Service Locator pattern. Still in this case there will be the same cons as for the static field approach.

All of these difficulties might seem not that important. Yet they can and will complicate development and testing for more complex server applications. This might eventually result in unclean and messy code, unreliable application behavior and hard-to-catch bugs.

The Solution

Luckily there is a solution for the development challenges mentioned above. Dependency Injection containers (called DI containers below) can help to cope with code complexity and maintaining clean and testable code.

Take a look at this code sample:

using RemObjects.SDK.Server;

namespace SampleServer
{
	static class Program
	{
		public static int Main(string[] args)
		{
			ApplicationServer server = new ApplicationServer("SampleServer");
			
			server.DependencyResolver.RegisterSingleton(typeof(ILogger), new DebugLogger());

			server.Run(args);
			return 0;
		}
	}

	// Sample Service
	[Service]
	public class FooService : Service
	{
		private readonly ILogger _logger;

		public FooService(ILogger logger)
		{
			this._logger = logger;
		}

		[ServiceMethod]
		public string Foo(string value)
		{
			this._logger.Log("Foo: " + value);
			return value.ToUpper();
		}
	}

	public interface ILogger
	{
		void Log(string message);
	}

	class ConsoleLogger : ILogger
	{
		public void Log(string message)
		{
			System.Console.WriteLine(message);
		}
	}

	class DebugLogger : ILogger
	{
		public void Log(string message)
		{
			System.Diagnostics.Debug.WriteLine(message);
		}
	}
}

Note how an ILogger instance is passed to the service via the constructor.

Still the main magic happens in this line:

server.DependencyResolver.RegisterSingleton(typeof(ILogger), new DebugLogger());

Yes, Remoting SDK for .NET now provides built-in support for Dependency Injection containers! Remoting SDK for .NET provides a built-in Dependency Injection container as well as a simple way to integrate any 3rd-party DI container.

That said, the syntax used in the code line above should be used only in very simple cases. This is the recommended way of using the DI container:

var container = new SimpleContainer();
container.RegisterSingleton(typeof(ILogger), new DebugLogger());
container.RegisterSDK(server.NetworkServer);
container.RegisterServices();
server.DependencyResolver = container;

The code above does the following:

  • A new DI container is created.
  • A singleton of type ILogger implemented by the DebugLogger instance is registered in this container.
  • SDK-specific entities like Session Manager and Event Sink Manager are registered in the container.
  • Remoting SDK services defined in the application are registered in the container.
  • The default Remoting SDK DI container is replaced with the one just configured.

After applying these settings, Remoting SDK will be able to instantiate the service class using a parametrized constructor.

An additional bonus is that it is now possible to properly test the service method. You just need to provide a mock of the ILogger service while constructing the FooService instance. This will make the unit test of the Foo method as self-contained as it should be.

Note: This syntax will be explained in more details in the second part of this article.

Conclusion

Remoting SDK for .NET now provides built-in support for Dependency Injection containers.

Remoting SDK for .NET also provides a built-in simple Dependency Injection container, as well as a simple way to integrate any 3rd-party container like Unity, Autofac etc.