Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dependency injection in class libraries

0.00/5 (No votes)
15 Dec 2015 1  
A simple way of using dependency injection and service locator in you class library

IoC in class libraries

Developing frameworks is always great fun. Here is a quick tip of how you can use the inversion of control principle when building and publishing classes for third party developers.

Background

When designing your framework you always want to present independent interfaces and classes for the client developer. Let say you do have a class called ModelService which is a simple data access class, published from a framework called SimpleORM.

Once you had split the responsibilities and segregated your interfaces, you developed a design where your ModelService class is using (through composition) a couple of other interfaces: IConnectionStringFactory, IDTOMapper, IValidationService.

You would like to inject the dependent interfaces into your ModelService class such that you can test it appropriately. This can easily be achieved with constructor injection:

public ModelService(IConnectionStringFactory factory, IDTOMapper mapper, IValidationService validationService)
{
    this.factory = factory;
    this.mapper = mapper;
    this.validationService = validationService;
}

This type of dependency injection is used frequently when you are centering your modules around your own application, and you do not publish those as standalone class libraries. You don't worry about instantiating your ModelService class, as you will query your DI container for ModelService instance. The last one will be aware of IConnectionStringFactory, IDTOMapper, IValidationService or any other binding.

On the other hand, when you do publish your classes for third party usage, the scenario is slightly different. You don't want the caller to be able to inject whatever interface he wants into your class. Moreover, you don't want him to be worried about any interface implementation he needs to pass into the constructor. Everything, except for the ModelService class has to be hidden.

Ideally, he has to be able to get an instance of your ModelService class by just saying:

var modelService = new ModelService(); 

The above does not hold when you are letting the caller alter the behavior of your class. In case you are implementing Strategy or Decorator patterns, you will obviously define your constructor a bit differently.

Simplest approach

The easiest way of achieving testability and leaving a parameterless constructor for the framework callers is the following:

public ModelService() : this(new ConnectionStringFactory(), new DTOMapper(), new ValidationService()
{
   // no op
} 

internal ModelService(IConnectionStringFactory factory, IDTOMapper mapper, IValidationService validationService)
{
    this.factory = factory;
    this.mapper = mapper;
    this.validationService = validationService;
}  

Assuming you are testing you ModelService class in a separate test project, do not forget to set InternalVisibleTo attribute in your SimpleORM property file:

[assembly: InternalsVisibleTo("SimpleORM.Test")]

The advantage of the described approach is two-fold: it will allow you injecting mocks in your tests and hide the constructor with parameters from your framework's users:

[TestInitialize]
public void SetUp()
{
      var factory = new Mock<IConnectionStringFactory>();
      var dtoMapper = new Mock<IDTOMapper>();
      var validationService = new Mock<ivalidationservice>();

      modelService = new ModelService(factory.Object, dtoMapper.Object, validationService.Object);
}

Getting rid of the dependencies

The above approach has an obvious drawback: your ModelService class has a direct dependency on composite classes: ConnectionStringFactory, DTOMapper and ValidationService. This violates the loose coupling principle making your ModelService class statically dependent upon implementing services. In order to get rid of these dependencies programming experts will advise you adding a ServiceLocator that will be responsible for object instantiation:

internal interface IServiceLocator
{
    T Get<T>();
}
 
internal class ServiceLocator
{
   private static IServiceLocator serviceLocator;
   
   static ServiceLocator()
   {
        serviceLocator = new DefaultServiceLocator();
   }

   public static IServiceLocator Current
   {
      get
      {
           return serviceLocator;
      }
   }

   private class DefaultServiceLocator : IServiceLocator
   {
      private readonly IKernel kernel;  // Ninject kernel
      
      public DefaultServiceLocator()
      {
          kernel = new StandardKernel();
      }

      public T Get<T>()
      {
           return kernel.Get<T>();
      }
   }
}

I've written a typical ServiceLocator class which uses Ninject as dependency injection framework. You can use whatever DI framework you want, as it will be transparent for the callers. If performance is a concern for you, check the following nice article with interesting assessments. Also, notice that ServiceLocator class and its corresponding interface is internal.

Now replace direct initialization calls for dependent classes with a call to ServiceLocator:

public ModelService() : this(
ServiceLocator.Current.Get<IConnectionStringFactory>(), 
ServiceLocator.Current.Get<IDTOMapper>(), 
ServiceLocator.Current.Get<IValidationService>())
{
   // no op
} 

You will obviously have to define the default bindings for IConnectionStringFactory, IDTOMapper and IValidationService somewhere in your solution:

internal class ServiceLocator
{
   private static IServiceLocator serviceLocator;
   
   static ServiceLocator()
   {
        serviceLocator = new DefaultServiceLocator();
   }

   public static IServiceLocator Current
   {
      get
      {
           return serviceLocator;
      }
   }

   private sealed class DefaultServiceLocator : IServiceLocator
   {
      private readonly IKernel kernel;  // Ninject kernel
      
      public DefaultServiceLocator()
      {
          kernel = new StandardKernel();
          LoadBindings();
      }

      public T Get<T>()
      {
           return kernel.Get<T>();
      }
    
      private void LoadBindings()
      {
          kernel.Bind<IConnectionStringFactory>().To<ConnectionStringFactory>().InSingletonScope();
          kernel.Bind<IDTOMapper>().To<DTOMapper>().InSingletonScope();
          kernel.Bind<IValidationService>().To<ValidationService>().InSingletonScope();
      } 
   } 
}

Sharing dependencies across different class libraries

As you continue developing your SimpleORM framework, you will eventually end up splitting your library into different sub-modules. Let us say you want to provide an extension for a class which implements an interaction with a NoSQL database. You don't want to mess up your SimpleORM framework with unnecessary dependencies thus, you release SimpleORM.NoSQL module separately. How would you access the DI container? Moreover, how can you add additional bindings to your Ninject kernel?

Below is a simple solution for it. Define an interface IModuleLoder in your initial class library SimpleORM:

public interface IModuleLoader
{
    void LoadAssemblyBindings(IKernel kernel);
}

Instead of directly binding the interfaces to their actual implementation in your ServiceLocator class, implement IModuleLoader and call the bindings:

internal class SimpleORMModuleLoader : IModuleLoader
{
   void LoadAssemblyBindings(IKernel kernel)
   {
      kernel.Bind<IConnectionStringFactory>().To<ConnectionStringFactory>().InSingletonScope();
      kernel.Bind<IDTOMapper>().To<DTOMapper>().InSingletonScope(); 
      kernel.Bind<IValidationService>().To<ValidationService>().InSingletonScope();
   }
}

Now you are left with calling LoadAssemblyBindings from you service locator class. Instantiating these classes becomes a matter of reflection call:

internal class ServiceLocator
{
   private static IServiceLocator serviceLocator;

   static ServiceLocator()
   {
        serviceLocator = new DefaultServiceLocator();
   }

   public static IServiceLocator Current
   {
      get
      {
           return serviceLocator;
      }
   }

   private sealed class DefaultServiceLocator : IServiceLocator
   {
      private readonly IKernel kernel;  // Ninject kernel
      
      public DefaultServiceLocator()
      {
          kernel = new StandardKernel();
          LoadAllAssemblyBindings();
      }

      public T Get<T>()
      {
           return kernel.Get<T>();
      }
    
     private void LoadAllAssemblyBindings()
     {
         const string MainAssemblyName = "SimpleORM";
         var loadedAssemblies = AppDomain.CurrentDomain
                               .GetAssemblies()
                               .Where(assembly => assembly.FullName.Contains(MainAssemblyName));

        foreach (var loadedAssembly in loadedAssemblies)
        {
              var moduleLoaders = GetModuleLoaders(loadedAssembly);
              foreach (var moduleLoader in moduleLoaders)
              {
                  moduleLoader.LoadAssemblyBindings(kernel);
              }
         }
     }

     private IEnumerable<IModuleLoader> GetModuleLoaders(Assembly loadedAssembly)
     {
        var moduleLoaders = from type in loadedAssembly.GetTypes()
                                      where type.GetInterfaces().Contains(typeof(IModuleLoader))
                                      type.GetConstructor(Type.EmptyTypes) != null
                                      select Activator.CreateInstance(type) as IModuleLoader;
      return moduleLoaders;
     }
}

What this code does is the following: it queries all loaded assemblies in your AppDomain for IModuleLoader implementation. Once found, it passes your singleton kernel to their instances, making sure that the same container is used across all modules.

Your extension framework SimpleORM.NoSQL will have to implement its own IModuleLoader class so that it will be instantiated and called on the first call to ServiceLocator class. Obviously, the above code implies that your SimpleORM.NoSQL is dependent upon SimpleORM, which comes naturally with extended modules being dependent upon their parents.

Acknowledgement

The described solution is not a silver bullet. It has its own drawbacks: managing disposable resources, accidental rebinding in dependent modules, performance overhead on instance creation, etc. It has to be used cautiously with a good set of unit tests behind. If you do have comments on the above implementation you are more than welcome to leave them in the comments section.

History

  • March 2014 - First published
  • December 2015 - Reviewed

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here