ASP.NET Core: What is Dependency Injection?

ASP.NET Core: What is Dependency Injection?

Nov 19, 2020
Developer Lead at WiredViews
ASP.NET Core: What is Dependency Injection?


ASP.NET Core supports Dependency Injection — in fact, it heavily relies on it! But, what is Dependency Injection (DI) exactly? 


Microsoft's documentation defines it as:

"[a] software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies."

How wonderfully abstract! Let's break this down. As .NET developers, we know what classes are. We also probably have an idea of what 'dependencies' are — the other bits (classes, interfaces, or data) that a class depends on to operate.

What about ‘Inversion of Control’? Microsoft's documentation has some nice diagrams to explain Dependency Inversion, but I think there's a simpler way to explain it. Instead of each class managing the creation of the things it depends on (often by using the new keyword in C#), it gives up that control and instead asks for what it needs, with the expectation that something else will create and then provide each dependency.

Dependency Injection, in the ASP.NET Core world, typically means Constructor Injection where the dependencies for a class are provided in its constructor.

Let's look at a simple example:

public interface IPet

{

    string Name { get; }

}

 

public class Dog : IPet

{

    public Dog(string name) => Name = name;

 

    public string Name { get; }

}

 

public class PetOwner

{

    public PetOwner(IPet pet) => Pet = pet;

 

    public IPet Pet { get; }

}

 

// ... later in our application

 

IPet dog = new Dog("Poppy");

 

PetOwner sean = new PetOwner(dog);

 

In this example, the Name of the Dog is provided as a Constructor Dependency, just as in the case of the Pet of the PetOwner.

This is Dependency Injection by means of constructors!

What's the benefit of doing this?

Well, there is a saying that 'new is glue'. Every time we use the new keyword in our applications, we are 'gluing' ourselves to a specific implementation.

Let's look at a variation of our example code:

public class PetOwner

{

    public PetOwner() => Pet = new Dog("Poppy");

 

    public IPet Pet { get; }

}

 

The new is inside the PetOwner class, which means all PetOwner instances are 'glued' to pets named "Poppy". Not only that, but they’re always going to be dogs too! Maybe that's fine for my use case 🐕‍🦺, but what about yours?

By moving the new keyword outside of the class and supplying the Pet through the constructor of the PetOwner (which we originally did), we allow for any kind of IPet to be provided to any PetOwner. This is going to be much more useful to us!

The benefits in flexibility also impact testability! By supplying dependencies through interfaces, we can depend on classes that connect to databases, web services, and the file system when our application is running. At the same time, we can replace those with fake versions, and still make them adhere to the interface, whenever we want to run some tests.

See the benefits of using interfaces for testing in Microsoft's documentation on unit testing.

Let the Container do the dirty work

The above example shows how we can perform Dependency Injection with a couple of lines of C#. However, once an application begins to grow, we notice an increase in the number of types (e.g., interfaces and classes) as well as in the interdependence of those types.

This complexity can become hard to maintain. It can also become difficult to construct the classes we want to use, because they have dependencies, each of which will have dependencies of their own, etc. Everything then needs to be created and ready for the one class we actually want to use. Talk about Yak Shaving!

Creating and managing all of those classes can also become tricky — especially when dependencies have different lifetimes.

Some dependencies should last the lifetime of the application (Singletons), while others have a lifetime matching the current HTTP request (Scoped). The rest are created every time we need one, and destroyed once they are no longer referenced (Transient).

Fortunately, there is a common tool to help us manage our dependencies.

Dependency Injection Containers are specially designed libraries that we, the developers, give instructions to. These instructions tell the Container how to create all the types in our application, and what the lifetimes of those types should be.

By using a Container, we can register all of our classes and interfaces, and rely on the Container to do the dirty work of following the chain of what needs new'd, so that we can finally get an instance of our class.

Each time the developers started a new project in the past, they had to choose one of many DI Containers , and then configure that Container for their application. Neither ASP.NET nor the .NET Framework provided one out of the box.

With ASP.NET Core, however, much has changed!

How does Dependency Injection work in ASP.NET Core?

With ASP.NET Core, Dependency Injection has become a first class supported feature to the extent that the framework provides a built-in DI Container. The framework itself relies on this Container so that the internal framework code can use the Dependency Injection design pattern.

Developers can still integrate one of the many open source DI Container libraries, each of which has its own special features and benefits, or they can use the built-in Container if their use-case doesn't need those other features.

Dependency registration is performed in the Startup class' ConfigureServices() method. In the example below we'll register a UserService which implements the IUserService interface, and we will register it with a Scoped (1 instance per request) lifetime:

public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)

{

     services.AddScoped<IUserService, UserService>();

}

 

This one line makes instances of our UserService available to any class that has a constructor parameter of IUserService. Furthermore, it ensures that each instance created will only be shared among classes within a single request, at the end of which the UserService will be destroyed.

Integrating Kentico Xperience 13 into ASP.NET Core

Kentico Xperience 13 fully supports ASP.NET Core, and the framework's approach to Dependency Injection. If we want to integrate Xperience 13 into our ASP.NET Core projects, we only need to read the docs to see how we gain access to all its types and interfaces.

The key line is calling the .AddKentico();  extension method in our Startup.ConfigureServices();  method.

public void ConfigureServices(IServiceCollection services, IWebHostEnvironment env)

{

     services.AddKentico();

 

    // ...

}

 

It's that easy! Kentico Xperience does all the work for us. It registers all the platform’s types along with the correct lifetimes. This means that we can start using IUserInfoProvider and IPageRetriever anywhere in our classes' constructors, without having to do any additional configuration.

Of course, Kentico Xperience 13 offers many more features — i.e. content tree-based routing, the Page Builder, and flexible content retrieval. Therefore, once we've set up and begun using Dependency Injection with Kentico Xperience 13 and ASP.NET Core, there will be plenty more to learn and explore.

If you’d like to explore it for yourself, download a free trial.