Services, Dependency Injection and Composition

Wisej.NET 3.1 added full support for Services registration (IServiceProvider) and Dependency Injection (DI) in a simple and yet powerful implementation typical of Wisej.NET. In this blog entry we clarify some of the concepts and show how to integrate Microsoft’s Managed Extensibility Framework (MEF) and Wisej.NET’s Dependency Injection through composition.

What are Services?

In this context, a service is simply an object that implements a particular functionality, a “service” to the rest of the application. Not to be confused with Windows Services, which are processes running in the background.

Using the service pattern we have the service provider and a consumer. The consumer is whatever code obtains the service object, the provider is the service object. For example, suppose your app needs to list files from a file system regardless of the type of file system. Could be the local system, could be OneDrive, could be GoogleDrive, or could be a database table. The point is that the app expects an object that implements IMyCoolFileSystem.EnumFile(path). The actual implementation is transparent to the app.

From the consumer point of view (the code using the service), all it needs to do is “ask” for an implementation of IMyCoolFileSystem. Which implementation it will receive depends on something else: could be a configuration file, a rule somewhere, etc. In a simple scenario like this one, we’d register a service at startup like this:

Application.Services.AddService<IMyCoolFileSystem, MyOneDriveFileSystem>();

From this moment on, the “consumer” code for the IMyCoolFileSystem services will receive an instance of MyOneDriveFileSystem.

var fileSystem = Application.Services.GetService<IMyCoolFileSystem>();

So far so good. But, which implementation? Is it always the same instance (singleton), or a new instance is created every time the service is requested? This is the lifetime of the service. Wisej.NET supports several lifetimes: See Services Lifetime for more information.

Dependency Injection

Using the service pattern decouples the code that uses a service from the actual implementation. In our simple sample, the application may register MyGoogleDriveFileSystem without changing anything else. Code that uses the service only expects an implementation of IMyCoolFileSystem.

However, we can do more and create a dependency between a class and a service, then “inject” the implementation into the instance of that class. We do that in two ways: using the Inject attribute, or add a parameter to the constructor (only for service classes).

public class MyMainView : Page {
[Inject]
protected IMyCoolFileSystem FileSystem {get; set;}
}

As soon as MyMainView is created, Wisej.NET retrieves the IMyCoolFileSystem service and assigns it to the FileSystem property. Now the code in MyMainView can use this.FileSystem and doesn’t need to even know where the implementation is coming from.

But we still need to load the implementation class. Which means that MyOneDriveFileSystem and MyGoogleDriveFileSystem must be in referenced assemblies or in the same project as the application. However, in a modular system we may want to be able to discover automatically where to load the service implementation from. With .NET it’s easy to do this by using Assembly.Load() and Assembly.GetType() in conjunction with a service creation factory callback.

Application.Services.AddService<IMyCoolFileSystem>((t) => {
return (IMyCoolFileSystem)
Assembly.Load("MyOneDriverAssembly").GetType("MyOneDriveFileSystem");
});

In this case the assembly is not referenced directly. However, the name of the assembly and the implementation class name are explicitly set in the code.

But what if I want to be able to use any assembly that exports a class, regardless of its name, that implements the IMyCoolFileSystem interface? You could load all the assemblies in a given location and check all the types, etc. A much better option is to use MEF together with the service factory option.

Composition

Microsoft’s Managed Extensibility Framework (introduced with .NET 4 and available in .NET Core as well) was designed to build “lightweight and extensible applications.” Using MEF you dynamically discover any exported implementation of your service from any assembly.

The factory callback example above becomes:

Application.Services.AddService<IMyCoolFileSystem>((t) => {
return _container.GetExportedValue<IMyCoolFileSystem>();
});

But what is _container? It is the composition container created using a composition dialog.

var _container = new CompositionContainer(
new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory));

Application.Services.AddService<IMyCoolFileSystem>((t) => {
return _container.GetExportedValue<IMyCoolFileSystem>();
});

In this very simple code we are using a directory catalog capable of discovering exported classes from a directory. There are several catalog types available, you can aggregate multiple catalogs, and you can write your own.

On the implementation side, all you need is:

[Export(typeof(IMyCoolFileSystem)]
public class Class1 : IMyCoolFileSystem {
...
}

The application and the consumer code don’t need to know the class name of the implementation, they don’t even need to know in which assembly it’s located, and don’t even need to know where the assembly is!