With Microsoft.Extensions.DependencyInjection
, can I resolve the type and construct an instance while providing extra constructor parameters, in one go?
What I'd like to do is easy to illustrate by example. Below, in CreateSomethingWithContext
I need to use ActivatorUtilities.CreateInstance
to call a parameterized constructor, but I don't know the concrete type for ISomething
in advance.
I could use serviceProvider.GetRequiredService
to resolve the type and create a default instance, but then I couldn't pass context
parameter.
I could use a factory version of AddTransient
, but that would break the code which already uses GetRequiredService
to create default instances.
Below, a possible kludge (which I dislike) is to create a redundant default instance of Something
only to figure out its type and pass it to ActivatorUtilities.CreateInstance
, along with the context
parameter.
Is there a better way of doing this? To clarify, I have no control over the actual library that implements ISomething
/Something
.
using System;
using Microsoft.Extensions.DependencyInjection;
namespace App
{
public interface ISomething
{
void DoSomething() => Console.WriteLine(nameof(DoSomething));
}
public class Something: ISomething
{
public Something() =>
Console.WriteLine($"{this.GetType().Name} created");
public Something(object context) =>
Console.WriteLine($"{this.GetType().Name} created with {context}");
}
static class Program
{
private static IServiceProvider BuildServices() => new ServiceCollection()
.AddTransient<ISomething, Something>()
.BuildServiceProvider();
static ISomething CreateSomething(IServiceProvider serviceProvider) =>
serviceProvider.GetRequiredService<ISomething>();
static ISomething CreateSomethingWithContext(IServiceProvider serviceProvider, object context)
{
// first I need an instance of ISomething, only to learn its concrete type for
var something = serviceProvider.GetRequiredService<ISomething>();
var type = something.GetType();
// now that I have the type, I can use ActivatorUtilities.CreateInstance
var something2 = (ISomething)ActivatorUtilities.CreateInstance(
serviceProvider, type, context);
return something2;
}
static void Main()
{
var serviceProvider = BuildServices();
CreateSomething(serviceProvider);
CreateSomethingWithContext(serviceProvider, Guid.NewGuid());
}
}
}
CodePudding user response:
Personally, I agree with you about not wanting to do this with ActivatorUtilities.CreateInstance - although it will work, if ever the constructor's signature changes you'd end up getting a runtime error, and I'm sure a compile time error would be preferable.
Why not register a completely separate factory for constructing your object with the context - Leave the AddTransient() registration untouched so that all calling code that relies on the default constructor would still work, and use newly created factory where you need to insert your context
?
CodePudding user response:
The thing you are asking for is something that goes fundamentally against the idea of the dependency inversion principle, which is the basis for dependency injection. So it is unlikely that you will find support for something like this in common dependency injection containers.
To understand this, let’s think about why you are actually registering your concrete implementation Something
as its interface type ISomething
. By doing that, you are allowing components to depend on the abstraction instead of a concrete type. This reduces coupling as it allows you to easily swap the implementation with something else that satisfies the interface specification.
The idea is that you don’t actually want to know what implementation you get when you depend on ISomething
as long as it is compatible to the interface. That also means that you do not want to know what dependencies that concrete implementation might have itself. Instead, the dependent component gives up the control to an outside system, usually the DI container, and just expects the system to give you the thing you need.
Now, when comparing this idea with ActivatorUtilities.CreateInstance
, one might observe that this utility method is actually just syntactic sugar around the service locator pattern, which is usually in direct contrast to dependency injection: Instead of relying on the system to give you whatever you depend on, you can now actively request your dependencies from the container itself. ActivatorUtiltilies.CreateInstance
just does that while also allowing you to pass values that should not be resolved from the container.
Let’s assume that a method existed that allowed you to do CreateInstance<ISomething>(additionalValues)
. You are now asking the container to provide you with an implementation for ISomething
but apparently you know so much about the concrete implementation of that, that you are able to tell what parameters you would want to pass in order to resolve it. And that contradicts what I explained above, that you usually (deliberately) don’t want to know what the concrete implementation is.
Instead, I would suggest you to create a proper factory for this, something that is made to create that Something
for you, and offers a properly typed way to add that “context” data:
public class SomethingFactory : ISomethingFactory
{
private readonly SomeDependency _someDependency;
public SomethingFactory(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public ISomething Create()
=> new Something(_someDependency);
public ISomething CreateContext(object context)
=> new Something(_someDependency, context);
}
That factory is then made for the Something
implementation, so it is perfectly fine that it knows what kind of parameters it would need to pass to the Something
constructor, and since it has an abstraction ISomethingFactory
itself, you are still able to do this properly using dependency injection instead of using the service locator pattern.