vNext Depedency Injection Overview

I've been playing around with the latest version (beta4) of ASP.NET 5 and vNext and I have to say I'm impressed. One of my favorite features is the addition of the Dependency Injection module that will become a staple of the .NET applications. This module provides a unified DI pipeline with a simple-to-use interface.

The Dependency Injection currently only supports constructor injection. Dependencies get defined in the Constructor of a class and will be resolved by the system automatically.

The information on this topic is fairly spartan at this time. But, after some hacking I can hopefully fill in a few blanks on the new DI module.

Microsoft.Framework.DependencyInjection

The project currently lives on Github. You can review it and dig deeper if you're so inclined. Thanks Microsoft!

In order to get started with the DI module in your project, the first step is adding the module dependency to your project's project.json file. The example below is using beta4 and pulls in Microsoft.Framework.DependencyInjection.

{
    "version": "1.0.0-*",
    "description": "",
    "authors": [ "" ],
    "tags": [ "" ],
    "projectUrl": "",
    "licenseUrl": "",

    "dependencies": {
        "Microsoft.Framework.DependencyInjection": "1.0.0-beta4"
    },

    "commands": {
        "TestApp": "TestApp"
    },

    "frameworks": {
        "dnx451": { },
        "dnxcore50": {
            "dependencies": {
                "System.Console": "4.0.0-beta-22816",
                "System.Collections": "4.0.10-beta-22816",
                "System.Linq": "4.0.0-beta-22816",
                "System.Threading": "4.0.10-beta-22816",
                "Microsoft.CSharp": "4.0.0-beta-22816"
            }
        }
    }
}

You can get this module by searching in Nuget or by adding it directly to the project.json file in Visual Studio 2015.

So what does Microsoft.Framework.DependencyInjection do? This module contains a few key classes.

ServiceCollection

This is the default implementation of Microsoft.Framework.DependencyInjection.IServiceCollection which specifies the contract for a collection of service descriptions. In other words, it's the bucket that holds our dependency definitions. ServiceCollection is the default implementation of this collection.

The jist of this class is that you will be able to add service descriptors to it that can later be resolved. The methods that allow you to add/remove descriptors are defined in Microsoft.Framework.DependencyInjection.Interfaces.ServiceCollectionExtensions. A few goodies...

// Adds a pre-existing instance that will be referenced
IServiceCollection AddInstance<TService>(TService implementationInstance);

// Creates an instance once per request scope
IServiceCollection AddScoped<TService, TImplementation>();

// Creates a single instance that will be used each time the service is requested
IServiceCollection AddSingleton<TService>();

// Creates a new instance each time the service is requested
IServiceCollection AddTransient<TService>();  

These extension methods all return the instance of IServiceCollection and can be used in a fluent style... sweet.

var services = new ServiceCollection()  
    .AddTransient<Service1>()
    .AddInstance<Service2>(new Service2())
    .AddSingleton<Service3>();

These methods also define the lifecycle of dependencies. You can use the most appropriate method for your needs. The four types of lifecycles are:

  • Transient - creates a new instance each time the service is requested
  • Singleton - creates a single instance that is used each time the service requested
  • Instance - uses the supplied instance each time the service is requested
  • Scoped - creates a new instance for each container

Finally, there is another method found in Microsoft.Framework.DependencyInjection.ServiceCollectionExtensions called BuildServiceProvider. This method will generate an IServiceProvider.

ServiceProvider

Microsoft.Framework.DependencyInjection.ServiceProvider is the default implementation of System.IServiceProvider. It provides the mechanism by which you can resolve the services required by your application. Basically, it does the fetching for you.

Beyond the default interface, a few extension methods are provided by Microsoft.Framework.DependencyInjection.Interfaces.ServiceProviderExtensions to help you work more effectively.

The GetService method resolves the specified instance for you. The example below creates a collection, adds a service descriptor, creates the IServiceProvider, and retreives the service.

// GetService<T> Example

// Create the service collection
IServiceCollection services = new ServiceCollection();

// Add a descriptor for ICarService
services.AddTransient<ICarService>(p => new LamborghiniService());

// Create a provider from the service collection
IServiceProvider provider = services.BuildServiceProvider();

// Resolve the ICarService
// In this case, it will resolve to LamborghiniService
ICarService carService = provider.GetService<ICarService>();  

GetService will return null if no service was added to the collection as shown below.

// GetService<T> Example

// Create the service collection
IServiceCollection services = new ServiceCollection();

// Create a provider from the service collection
IServiceProvider provider = services.BuildServiceProvider();

// Resolve the ICarService
// In this case, it will resolve to null
ICarService carService = provider.GetService<ICarService>();  

A different method is GetRequiredService. This method is similar to GetService but it will throw an InvalidOperationException if the resolve service is null as shown below.

// GetRequiredService<T> Example

// Create the service collection
IServiceCollection services = new ServiceCollection();

// Create a provider from the service collection
IServiceProvider provider = services.BuildServiceProvider();

// Throws exception since we have not defined ICarService references
ICarService carService = provider.GetRequiredService<ICarService>();  

But what if you just want to create an instance of a Type that has dependencies?

ActivatorUtilities

Microsoft.Framework.DependencyInjection.Interfaces.ActivatorUtilities is a collection of static utilities methods that can be used to create instances that have dependencies. It allows you to do the following:

  1. Create a factory for a Type
  2. Create an instance of a Type
  3. Get an instance from the service provider or create an instance of a Type

Below are the method signatures. Notice that these methods require supplying the IServiceProvider.

// Creates an instance of a Type
T CreateInstance<T>(IServiceProvider provider, params object[] parameters);

// Gets the service or creates an instance of a Type
T GetServiceOrCreateInstance<T>(IServiceProvider provider)  

Usage

Most of the examples I've seen are with the MVC stack that takes care of most of this magic for you. In this stack, you define services in Startup.cs and define dependencies in the constructor of your Controllers. When the controller is created, the depedencies are automatically resolved for you. But how do you go about using the Dependency Injection module outside of this pipeline?

If you dig around in MVC, you'll see that under the covers, the DefaultControllerActivator class is used to create controllers. This class uses the DefaultTypeActivatorCache to perform controller construction... which is using ActivatorUtilities to generate and cache type factories.

If you were using a Console Application, you could use the ActivatorUtilities to construct your classes. This would allow your classes to automatically benefit from the DI framework.

using Microsoft.Framework.DependencyInjection;  
using System;

namespace TestApp  
{
    /// <summary>
    /// Application
    /// </summary>
    public class Program
    {
        public void Main(string[] args)
        {
            // Create service collection
            IServiceCollection services = new ServiceCollection();            

            // Add service descriptor for ICarService
            services.AddTransient<ICarService>(p => new LamborghiniService());            

            // Create a provider
            IServiceProvider provider = services.BuildServiceProvider();

            // Construct an instance with Dependencies
            var helper = ActivatorUtilities.CreateInstance<RichPersonHelper>(provider);             

            Console.Read();
        }
    }

    /// <summary>
    /// Class has dependencies
    /// </summary>
    public class RichPersonHelper
    {
        public RichPersonHelper(ICarService carService)
        {
            CarService = carService;

            Console.WriteLine("Using " + carService.GetType());   
        }        

        public ICarService CarService { get; set; }
    }

    /// <summary>
    /// Dependency interface
    /// </summary>
    public interface ICarService
    { }

    /// <summary>
    /// Concrete type for depency
    /// </summary>
    public class LamborghiniService : ICarService
    { }   
}

Hopefully you now have a better understanding of how DI works in beta4. Watch for future updates as new beta versions are released as there have already been some changes that will impact what you've learned here.

comments powered by Disqus