Click here to Skip to main content
15,867,308 members
Articles / Dependency

Dependency injection in class libraries

Rate me:
Please Sign up or sign in to vote.
4.97/5 (10 votes)
14 Dec 2015CPOL4 min read 73.3K   23   4
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:

C#
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:

C#
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:

C#
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:

C#
[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:

C#
[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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Moldova (Republic of) Moldova (Republic of)
Interested in computer science, math, research, and everything that relates to innovation. Fan of agnostic programming, don't mind developing under any platform/framework if it explores interesting topics. In search of a better programming paradigm.

Comments and Discussions

 
QuestionDo you have sample source code for above explanation Pin
vishwajeetrkale2-Jul-20 21:25
professionalvishwajeetrkale2-Jul-20 21:25 
QuestionHow are you able to use T? Pin
Member 1242413020-Jul-16 3:14
Member 1242413020-Jul-16 3:14 
GeneralMy vote of 5 Pin
Simvouli16-Dec-15 3:12
Simvouli16-Dec-15 3:12 
QuestionPerfect Pin
Niraj Doshi19-Oct-15 3:40
Niraj Doshi19-Oct-15 3:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.