ActivatorUtilities to create instances

The ActivatorUtilities class can be used to create instances of classes, in conjunction with DI

Home DailyDrop

Daily Knowledge Drop

The ActivatorUtilities static class can be used to create instances of classes outside of the dependency injection (DI) container, while still leveraging the DI container to create instances of the dependencies.


Examples

Consider a business logic class, which has one dependency on IConfiguration:

public class MyBusinessLogic
{
    private readonly IConfiguration _configuration;

    public MyBusinessLogic(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    public int PerformBusinessLogic()
    {
        Console.WriteLine("Performing business logic");

        return 1;
    }
}

It consists of a Constructor and a single method, PerformBusinessLogic, which performs some business logic, and returns the value 1 once completed.

In all of the below examples, the MyBusinessLogic class has NOT been registered with the dependency injection container.

The code for all the endpoints shown below is as follows:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// endpoint definition show below goes here!

app.Run();

Thats it - nothing else being registered or configured on startup.


Default DI

First, we'll try getting an instance of class directly from the DI container, two different ways:

// inject directly
app.MapGet("/direct", ([FromServices]MyBusinessLogic logic) =>
{
    return logic.PerformBusinessLogic();
});

// inject the service provider (the DI container)
// and try get the service from there
app.MapGet("/provider", (IServiceProvider provider) =>
{
    var logic = provider.GetRequiredService<MyBusinessLogic>();

    return logic.PerformBusinessLogic();
});

If you've worked with DI before, you would be unsurprised to know that the above does not work:

InvalidOperationException: No service for type 'MyBusinessLogic' has been registered.

The runtime doesn't know how to instantiate the MyBusinessLogic class. To resolve we could register MyBusinessLogic with the DI container, but for this post the assumption is this is not an option.


Manual

Next we'll look at the ways to manually get an instance of the MyBusinessLogic class:

The first way is to just manually instantiate the class directly. The constructor of MyBusinessLogic requires an instance of IConfiguration. In the below, IConfiguration is injected via DI, and then passed to the constructor of MyBusinessLogic:

app.MapGet("/manual", (IConfiguration config) =>
{
    var logic = new MyBusinessLogic(config);

    return logic.PerformBusinessLogic();
});

This will work, but if MyBusinessLogic had a long list of dependencies and it's constructor required many parameters, this can become messy. The endpoint would need to be modified to accept items from the DI container when it doesn't actual require them directly - they are only used to pass to MyBusinessLogic.

As slight improvement is to only inject the IServiceProvider implementation into the endpoint and then use that to get the required items for the MyBusinessLogic constructor:

app.MapGet("/manualprovider", (IServiceProvider provider) =>
{
    var logic = new MyBusinessLogic(provider.GetService<IConfiguration>());

    return logic.PerformBusinessLogic();
});

Again, this will work, but is still not ideal, as each type required needs to manually retrieved from the DI container.


ActivatorUtilities

Thankfully, there is a class to assist with this exact requirement - the ActivatorUtilities static class. Its usage is very simple - the CreateInstance method is called with the required class passed in as a generic parameter, along with the IServiceProvider implementation as a parameter (any other parameters which might be required, but are not part of the DI container).
ActivatorUtilities will then return an instance of the require class, using the IServiceProvider implementation to resolve any dependencies automatically - as would happen when using the DI container implicitly.

As the required IConfiguration parameter is already in the DI container, no additional parameters need to be passed in:

app.MapGet("/activatorutils", (IServiceProvider provider) =>
{
    var logic = ActivatorUtilities.CreateInstance<MyBusinessLogic>(provider);

    return logic.PerformBusinessLogic();
});

Here an instance of MyBusinessLogic is created, and all its dependencies are automatically resolved from the IServiceProvider instance, provider.


Notes

An incredibly useful method when working with dependency injection, but not all classes have been added to the DI container (for example, if in the processes of porting a legacy app, one controller at a time).

There are other ways of doing this using reflection (Activator.CreateInstance for example) not mentioned here - this post focuses on instantiating a class when the type wanted is known at compile time.


References

Activator utilities: activate anything!


Daily Drop 129: 02-08-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net activatorutilities DI