Skip to content

Understanding WebApi and Dependency Injection

Kaisinel edited this page May 16, 2021 · 7 revisions

What is a Dependency?

Have you ever used a plug to power some lightbulb up? I have as well. Did you know that there is a lot going on, until the electricity reaches your house?

The steps electricity takes to reach us

I did not know that!

However, that does not change the outcome- we can both turn the lights on and off knowing just the direct point of interaction- the plug.

When turning a lamp on and off, we don't need to care about a powerplant

Imagine if we didn't have this luxury and had to configure the outlet according to the power plant nearby. Or worse- make a call to it explaining our needs. It might be not a big deal if we had to do it once. But things break. When electric plug breaks- you don't contact your electric provider- you just replace the plug.

A lamp has a dependency on a plug, which has a dependency on an electric outlet, which has a dependency on electric wires, which has a dependency on electricity provider, which has a dependency on a power plant.

A dependency- a component which provides logic to another class.

If we don't conciously think about the amount of dependencies and complexity when creating new software, we might ridicule our code and make it overwhelming- just like the chain of dependencies of a lamp- if not managed.

Changes Happen

Ever-changing requirements is what makes modern software development hard. We're expected to make rapid changes happen. However, changes should not be something to be affraid of- embrace them- they will happen. In big systems, one logical unit usually depends on another, that depends from another and so on, so forth. What happens if an element at the bottom of a chain is no longer needed? -It affects the whole chain!

ToDoController depends on ToDoService, depends on ToDoRepository

In code, it looks like this:

// Delegate HTTP messages to other responsibile services.
public class ToDoController
{
    private readonly ToDoService _service;

    public ToDoController()
    {
        _service = new ToDoService();
    }

    [HttpGet]
    public IActionResult Get()
    {
        var result = _service.Get();
        return result;
    }
}

// Forward request to responsible components
public class ToDoService
{
    private readonly ToDoRepository _repository;

    public ToDoService()
    {
        _repository = new ToDoRepository();
    }

    public List<ToDo> Get()
    {
        var todos = _repository.Get();
        // do something else with retrieved ToDo item.

        return todos;
    }
}

// Persistence API of TODO.
public class ToDoRepository
{
    public List<ToDo> Get()
    {
        var todos = new List<ToDo>();
        // populate ToDo.
        
        return todos;
    }
}

If ToDoRepository breaks, the changes will rippled all the way to ToDoController. What makes matters worse, is the fact that the classes are in different packages altogether- we might not even be the owners of that code that we use. Dependency Inversion Principle helps us solve the problem. No matter how simple the WebApi is- you will be using Dependency Inversion.

How does Dependency Inversion Help? How to implement dependency inversion in WebApi? What options are there? Let's find out!

Dependency Inversion Principle

The official definition of Dependency Inversion Principle (DIP) is:

High level modules should not depend on low level modules. Both should depend on abstractions.

In other words- we should avoid creating dependencies inside classes or take concrete objects as arguments and instead abstract them away.

Do you remember the last lesson? We moved all vehicles without knowing their type. Depending on a base class (abstraction) instead of a concrete (detail) is just one of many ways of implementing the principle. We already know, that the best abstraction is an interface- it offers no implementation (details). How to apply it in Dependency Inversion?

Dependency Injection

Dependency injection is one of the ways to implement dependency inversion. There are 3 kinds of dependency injection, however there is one that dominates and thus it will be our focus. Introducing- constructor injection.

The Preparation. Constructor Dependencies

Let's start small. If we have two classes: Foo and Bar, where Foo uses Bar- we have a dependency. Let's show that in code:

class Foo
{
    private readonly Bar _bar;

    public Foo()
    {
        // Hard dependency
        _bar = new Bar();
    }

    // Do logic which involves Bar + a bit extra.
    public void FooBar()
    {
        // some logic
        _bar.Bar();
        // some more logic
    }
}

class Bar
{
    public void Bar()
    {
        // some logic.
    }
}

In this example, FooBar does some logic and calls _bar.Bar(). We hard-depend on Bar, because there is no way to swap it with a different implementation. There is no way, because we create it internally (inside a constructor).

In order to make this example work, we need some extension point to supply a different implementation of Bar, if needed. Let's fix this problem, by adding an interface:

interface IBar
{
    void Bar();
}

Something so small, just allowed us to change everything dramatically:

class Foo
{
    private readonly IBar _bar;

    public Foo(IBar bar)
    {
        _bar = bar;
    }

    // Do logic which involves Bar + a bit extra.
    public void FooBar()
    {
        // some logic
        _bar.Bar();
        // some more logic
    }
}

interface IBar
{
    void Bar();
}


class Bar:IBar
{
    public void Bar()
    {
        // some logic.
    }
}

The Act of Injection. Manual Injection

Now we can supply any kind of IBar we want. For exmaple:

IBar bar = new Bar();
var foo = new Foo(bar);
foo.FooBar(); // works!

Once again, by simply exposing an interface as a dependency in the constructor- we unlocked the possibility of injecting other implementations to that class.

Internally creating dependencies does simplify things, but it is a very short term solution- we get tied with the dependency the new creates. New is glue. Avoid creating dependencies internally.

New is glue. Glue monster

Let's go back to the ToDo example and make it DI-compliant:

// Delegate HTTP messages to other responsibile services.
public class ToDoController
{
    private readonly ToDoService _service;

    public ToDoController(IToDoService service)
    {
        _service = service;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var result = _service.Get();
        return result;
    }
}

public interface IToDoService
{
    List<ToDo> Get();
}

// Forward request to responsible components
public class ToDoService
{
    private readonly ToDoRepository _repository;

    public ToDoService(IToDoRepository repository)
    {
        _repository = repository;
    }

    public List<ToDo> Get()
    {
        var todos = _repository.Get();
        // do something else with retrieved ToDo item.

        return todos;
    }
}

public interface IToDoRepository
{
    List<ToDo> Get();
}

// Persistence API of TODO.
public class ToDoRepository: IToDoRepository
{
    public List<ToDo> Get()
    {
        var todos = new List<ToDo>();
        // populate ToDo.
        
        return todos;
    }
}

Wiring it looks like this:

var repository = new ToDoRepository();
var service = new ToDoService(repository);
var controller = new ToDoController(service);
// use controller...

The dependencies pointing in the same direction:

ToDoController depends on ToDoService, depends on ToDoRepository

Now have inverted arrows, thus the name- Dependency Inversion:

Inverted arrows of dependencies

The beauty this diagram illustrates is the direction of arrows- always pointing towards an interface. If an interface in between forces the consumer and provider both point to the same place- they will never have more than one dependency- an interface.

The diagram above is called a class diagram. You can refer to this guide to learn more about it.

The Act of Injection. Inversion of Control Containers

Creating dependent objects might grow tedious and out of hand, especially in large code bases. However, there is a solution to it- a place to manage both the concrete dependencies and even their lifetimes- Inversion of Control (IoC) container.

There are 4 main containers in .NET: Unity, Autofac, Ninject and Microsoft.Extension.DependencyInjection (default). The most popular one and thus used in examples will be Microsoft.Extension.DependencyInjection (default).

Using the default container, registering dependencies is as simple as:

services.AddTransient<IAbstraction, Implementation>();

If you are interested in a comparison of the 4 containers, refer to this example.

Lifetime

Each dependency registered in a container has a lifetime. Essentially there are 3 main categories of dependency creation. Created:

  • Every time it's referenced- Transient
  • Once per request- Scoped
  • Once per whole application- Singleton

Note: different container will call those lifetimes differently.

Where?

We will have to know about the dependencies at some point. However, if we avoid creating them until we have to- we can achieve almost a complete freedom from them. The earliest place the dependencies can be created is an application start (bootstrap). Thus, when injecting dependencies, follow these rules. Dependency injection should be:

  • In a single place
  • At the startup of your application
  • Create once (recommended)

In a console application this would happen in the void Main(string[] args) method, in WebApi it happens in public void ConfigureServices(IServiceCollection services) method inside Startup.cs.

Register the dependencies

It takes a few steps to register the dependencies and resolve our application:

  1. Register the dependencies
  2. Build dependencies
  3. Resolve application root

Let's use the example of a ToDoController and wire all up.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IToDoRepository, ToDoRepository>();
    services.AddTransient<IToDoService, ToDoService>();
}

Repository usually refers to something for persistence, a connection might be open and thus we don't want to reopen it per every reference, instead just per a single request until we are done. Service does not hold any unmanaged resources and so can be simply transient.

It's worth mentioning that not all DI combos are possible. You cannot register a singleton having a scoped dependency. Why? Because such a dependency needs to be created on every request, yet singleton is created just once per the whole app. A contradiction that is not possible to be implemented- and thus it fails.

Build dependencies

The resolution of services is built-in for ASP.NET Core WebApi. However, if we wanted to do the same for a console application- we would need to manually build the dependencies. Let's assume we are in a console application Main method.

Create IServiceCollection:

var services = new ServiceCollection();

Build it:

// Let's assume App is a class which provides all the logic.
services.AddSingleton<App, App>();
var serviceProvider = services.Build();

Building services returns IServiceProvider. It allows resolution of services.

Resolve application

var app = serviceProvider.Resolve<App>();
app.Run();

App is our class. Run starts the application. This could mean that it open a main window or maybe draws a start menu and asks to make a selection. All the dependencies of App will be created based on their scope, just like the Controller dependencies in ASP.NET Core.

The Benefits

Maintainability

  • With DIP, we achieve simplicity (isolate the details)
  • We get a living documentation (a contract that tells what a class can do)
  • We don’t marry any framework-specific implementation (or abstraction), so if we need a change, we can do it without rewriting
  • If you apply IoC container, you also get the benefit of controlling the dependencies in one place in a fluent way.

Reusability

  • Components (especially lower level) can be swapped
  • We can swap system as a whole or a part of it.

Testability

  • It’s easy to mock (about mocking and testing in future lessons) abstractions (so we can write unit tests for whatever consumes them)
  • If we weren’t depending on abstractions, we would not be able to create fake classes for testing.

Slicing WebApi

WebApi makes heavy use of Dependency Inection. There are two main parts which we can control.

Startup

All boot strap logic sits in Statups.cs class. When complexity grows, you might consider moving the logic elsewhere- for example /Bootstrap folder.

ConfigureServices

Dependencies are injected in public void ConfigureServices(IServiceCollection services) method. If you create a project based on a WebApi template, the only injected thing that you will see is services.AddControllers();. Controllers need to be created too after all and that's the way they are set in WebApi. In fact, they are a scoped dependency- they get created per request.

Configure

Middleware- is actions which happen before or after something in the request lifetime. You can configure middleware in public void Configure(IApplicationBuilder app, IWebHostEnvironment env) method. Default middleware in that method usually involves routing, error page, authorization. In future chapters we may cover it, but it is not something you will be working with on a daily basis.

Common Injections and the Power of Extension Methods

You can create a WebApi creating controllers in exactly the way you want and inject any kind of dependencies. However, if you injected everything inside of a ConfigureServices method, you would soon be overwhelmed by the amount of dependencies.

Did you notice how services usually does not go as an argument, but instead we always simply call something fromt it? If you hover your mouse over services.AddControllers() you will see (Extension) written next to it. What are extension methods?

Extension method- is a method which allows to extend an existing type. Extend here does not mean inheritance, but it means adding extra behavior, as if it was there all the time.

Extension methods must be inside a static class. The method itself must be static too. Therefore, ideally, you should avoid involving unrelated dependencies in extension methods- just work with one target. Extension method is like a normal method, but the target to extend needs to have a this keyword before its type and must be the first argument. Let's make our extension method!:

public static class StringExtensions
{
    // If input is "kaisinel" it return "Kaisinel".
    public static string ToName(this string text)
    {
        return text[0].ToString().ToUpper() + text.SubString(1);
    }
}

When there are too many dependencies, yet they are logically related, we should move them to another static class with an extension method and use that instead of listing all the abstraction and implementations. It's not a detail we want to know all the time, instead, it's more convenient to know what modules are injected. If we want to know more, we will look at the injected module.

For example, this:

public void ConfigureServices(IServiceCollection services)
{
    // ModuleA
    services.AddTransient<IDependencyA1, DependencyA1>();
    services.AddTransient<IDependencyA2, DependencyA2>();
    services.AddTransient<IDependencyA3, DependencyA3>();

    // ModuleB
    services.AddTransient<IDependencyB1, DependencyB1>();
    services.AddTransient<IDependencyB2, DependencyB2>();
}

Can be refactored to this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddModuleA();
    services.AddModuleB();
}

where AddModuleA and AddModuleB are extension methods to IServiceCollection inside ModuleASetup and ModuleBSetup classes within /BootStrap folder:

public static class ModuleASetup
{
    public static void AddModuleA(this IServiceCollection services)
    {
        services.AddTransient<IDependencyA1, DependencyA1>();
        services.AddTransient<IDependencyA2, DependencyA2>();
        services.AddTransient<IDependencyA3, DependencyA3>();
    }
}
public static class ModuleBSetup
{
    public static void AddModuleA(this IServiceCollection services)
    {
        services.AddTransient<IDependencyB1, DependencyB1>();
        services.AddTransient<IDependencyB2, DependencyB2>();
    }
}

Summary

Dependency Inversion is a principle which suggests to depend on abstractions, rather than implementations. It matters, because otherwise our code becomes a coupled mess, making changes will become hard. Dependency injection is a design pattern which implements Dependency Inversion Principle. Usually, we expose dependencies as abstractions in a constructor and inject them once, during the bootstrap of an application. Inversion of Control container helps to manage the lifetimes of dependencies, through 3 scopes: transient, scoped and singleton. In WebApi ConfigureServices is the method in which DI happens through IServiceCollection. We usually don't pass IServiceCollection to other methods- instead, we use extension methods in order to inject the features we want.

Don't blindly apply an interface to every single class. Remember- abstractions are supposed to hide implementation details. If you are making an interface on top of a model- that is usually a bad idea- it does not really hide anything. Also, simple static methods don't need an abstraction as well. DIP through DI is a powerful tool, but with great power comes great responsibility. Use it wisely.

Homework

This time, you will have to simulate a WebService which keeps track of your electric bill.

  1. Create a PowerPlant class. It has properties: Location, ElectricityPrice, ProducedPowerPerDay, Name.
  2. Create an ElectricityProvider class with a property Name and 3 methods:
    1. void Subscribe(PowerPlant plant)- subscribes to a single power plant. If powerplant is already subscribed, throws an exception.
    2. void Unsubscribe(PowerPlant plant)- unsubscribes from a subscribed power plant. If no powerplant is subscribed- does nothing.
    3. decimal CalculatePrice(Address address)- use some arbitrary formula to calculate the price the provider will charge for elecrticity. Location should be a factor.
  3. Create a Location class with properties: X, Y, Z.
  4. Create an Address class with properties: Country, City, Street, HouseNumber Location.
  5. Create an ElectricProviderPicker. It has:
    1. A list of ElectricProviders
    2. A method FindCheapest which returns the best (cheapest) ElectricProvider for an address.
  6. Have a controller endpoint to get the name and price of the best ElectricityProvider name and price.

A super villain- Electro

Did you understand the topic?

  • What is a dependency?
  • Why should we avoid depending on an implementation?
  • Where does the word "inversion" in dependency inversion come from?
  • How to design classes so that dependencies are loose?
  • How to inject a dependency?
  • What is an IoC container?
  • Where does DI happen in WebApi?
  • What is the difference between: Singleton, Transient and Scoped dependencies?
  • How to use IServiceCollection to get a dependency?
  • What is an extension method?

Clone this wiki locally