Dependency Injection based on request headers
Tags: dotnet, charp
Published: 2022-08-31 04:31:22

Dependency Injection (DI) helps us to change the behavior of _parts_ of our application on the fly. This is especially neat when you want to test your domain services against a mocked data-store. _But what if you need to change the behavior in your API based on a request header?_

Yesterday I had a discussion with my colleague [Robert Kranenburg](https://www.linkedin.com/in/robert-kranenburg-a7b616a7) about this subject. He showed an example of a console application changing its behavior based on an argument. I took the idea and converted it into .NET Core 3.1 code for a Web API.

2. [Let's change the message](#lets-change-the-message)
3. [(3) lifetime types](#3-lifetime-types)
4. [Accessing request headers](#accessing-request-headers)
5. [DI config](#di-config)
6. [The result](#the-result)
7. [Final thoughts](#final-thoughts)
8. [Further reading](#further-reading)

## [Let's change the message](#lets-change-the-message)

Our objective: _change the response message based on the presence of a cookie named "hidden", using dependency injection_.

First, we need to define the controller and the service interface:

```csharp
public interface IMessageService
{
    string GetMessage();
}

[ApiController]
[Route("")]
public class MyController : ControllerBase
{
    private readonly IMessageService _messageService;

    public MyController(IMessageService messageService)
    {
        _messageService = messageService;
    }

    [HttpGet]
    public string Get()
    {
        return _messageService.GetMessage();
    }
}
```

.NET Core uses _constructor injection_ and will provide an instance for each constructor parameter. It will inject an `IMessageService` instance into the controller.

Now, let's define 2 implementations of the `IMessageService`: 

* `DefaultMessageService` just returns a "Hello world!" message
* `HiddenMessageService` returns a different message. To make things more interesting, it uses a dependency to construct that message.

Let's view the code:

```csharp
public class DefaultMessageService : IMessageService
{
    public string GetMessage() => "Hello world!";
}

public class HiddenMessageService : IMessageService
{
    private readonly ISecretKey _key;

    public HiddenMessageService(ISecretKey key)
    {
            _key = key;
    }

    public string GetMessage() => 
        "The answer to life the universe and everything: " + 
        _key.GetKey();
}

public interface ISecretKey
{
    public string GetKey();
}

public class SecretKey : ISecretKey
{
    public string GetKey() => "42";
}
```

Before we set up the dependency injection, let's dive deeper into lifetimes.

## [3 lifetime types](#3-lifetime-types)

A DI service is added with a _lifetime_:

* `AddTransient`: Adding a **transient** service means that each time the service is requested, a new instance is created.
* `AddSingleton`: A **singleton** is an instance that will last the entire lifetime of the **application**. In web terms, it means that after the initial request of the service, every subsequent request will use that same instance, across all requests.
* `AddScoped`: A **scoped** service is instantiated per scope. For web this means the same instance per request, but you can actually create your own scopes.

Because we want to change the behavior based on request information, it makes sense to use a **scoped** service.

## [Accessing request headers](#accessing-request-headers)

In the olden days, we could do anything we wanted with the static `HttpContext.Current` and be done with it. In .NET Core we use the `IHttpContextAccessor` and dependency injection to interact with the `HttpContext`. We can use the `AddHttpContextAccessor` to set this up.

## [DI config](#di-config)

Let's go to the `Startup.cs` and set up the dependency injection:

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddHttpContextAccessor();

    services.AddTransient<DefaultMessageService>();

    services.AddTransient<ISecretKey, SecretKey>();
    services.AddTransient<HiddenMessageService>();

    services.AddScoped<IMessageService>(provider =>
    {
        var context = provider.GetRequiredService<IHttpContextAccessor>();
        var isHidden = context.HttpContext?.Request.Cookies.ContainsKey("hidden") == true;

        if (isHidden)
        {
            return provider.GetRequiredService<HiddenMessageService>();
        }

        return provider.GetRequiredService<DefaultMessageService>();
    });
}
```

_Why do we add `DefaultMessageService` and `HiddenMessageService` without interfaces to the service collection?_ Well, this helps us to use DI in the classes themselves. `HiddenMessageService` _needs_ this, because this is how we get an `ISecretKey` instance. Adding these classes will make our lives way easier when we need to instantiate them. If we would "new them up" ourselves, we would need to resolve all the dependencies as well, leading to a lot of unnecessary code.

The `IMessageService` is resolved via a _factory method_. This method gets an `IServiceProvider` as input to locate any services that are required. We use this provider to locate the HTTP accessor and to check if a cookie named _hidden_ is present. If so, we resolve the `IMessageService` as `HiddenMessageService`; otherwise as `DefaultMessageService`.

## [The result](#the-result)

When we add the code together we get this behavior:

[![](https://keestalkstech.com/wp-content/uploads/2020/06/hidden-1.gif)](https://keestalkstech.com/wp-content/uploads/2020/06/hidden-1.gif)

The contents of the message is changed based on the presence of a cookie named _hidden_.

## [Final thoughts](#final-thoughts)

It is easy to change de behavior of your program based on a request header or cookie. But things can get messy quite fast when you are doing dependency injection. Startup classes get large quickly when you need to inject many classes.

To make things easier, you can use an extension method like this:

```csharp
public static class HiddenCookieExtensions
{
    public static IServiceCollection AddScopedByHiddenCookie<TService, THiddenImplementation, TDefaultImplementation>(this IServiceCollection services)
        where TService: class
        where THiddenImplementation: TService
        where TDefaultImplementation: TService
    {
        services.AddScoped<TService>(provider =>
        {
            var context = provider.GetRequiredService<IHttpContextAccessor>();
            var isHidden = context?.HttpContext?.Request.Cookies.ContainsKey("hidden") == true;

            if (isHidden)
            {
                return provider.GetRequiredService<THiddenImplementation>();
            }

            return provider.GetRequiredService<TDefaultImplementation>();
        });


        return services;
    }
}
```

Which can be used like this:

```csharp
services.AddScopedByHiddenCookie<IMessageService, HiddenMessageService, DefaultMessageService>();
```

In this example, we used a cookie, but any header can be used to change the behavior of your application.

## [Further reading](#further-reading)

While working on this topic I found some excellent sources for reading:

* [Dependency injection in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1)
* [.NET Core Dependency Injection Lifetimes Explained](https://dotnetcoretutorials.com/2017/03/25/net-core-dependency-injection-lifetimes-explained/)
* [Dependency injection (with IOptions) in Console Apps in .NET Core](https://keestalkstech.com/2018/04/dependency-injection-with-ioptions-in-console-apps-in-net-core-2/)