I have a use case in which I want to create repository instances using .NET Core dependency injection, but need to change one of the constructor parameters at runtime. To be precise, the parameter that should be decided at runtime is the "database connection", which will point to one or another database decided by the caller. This type, by the way, is not registered with the DI container, but all the others are.
The caller will use a repository factory type to create the repository with the desired connection.
It looks something like this:
class ARepository : IARepository
{
public ARepository(IService1 svc1, IService2 svc2, IConnection connection) { }
public IEnumerable<Data> GetData() { }
}
class RepositoryFactory : IRepositoryFactory
{
public RepositoryFactory(IServiceProvider serviceProvider) =>
_serviceProvider = serviceProvider;
public IConnection CreateAlwaysFresh<TRepository>() =>
this.Create<TRepository>(new FreshButExpensiveConnection());
public IConnection CreatePossiblyStale<TRepository>() =>
return this.Create<TRepository>(new PossiblyStaleButCheapConnection());
private IConnection Create<TRepository>(IConnection conn)
{
// Fails because TRepository will be an interface, not the actual type
// that I want to create (see code of AService below)
return ActivatorUtilities.CreateInstance<TRepository>(_serviceProvider,conn);
// Fails because IConnection is not registered, which is normal
// because I want to use the instance held in parameter conn
return _serviceProvider.GetService<TRepository>();
}
}
The following types were registered:
services.AddTransient<IARepository, ARepository>(); // Probably not needed
services.AddTransient<IService1, Service1>();
services.AddTransient<IService2, Service2>();
services.AddTransient<IRepositoryFactory, RepositoryFactory>();
And the factory would be used as such:
class AService
{
public AService(IRepositoryFactory factory)
{
_factory = factory;
}
public void ExecuteCriticalAction()
{
var repo = _factory.CreateAlwaysFresh<IARepository>();
// Gets the freshest data because repo was created using
// AlwaysFresh connection
var data = repo.GetData();
// Do something critical with data
}
public void ExecuteRegularAction()
{
var repo = _factory.CreatePossiblyStale<IARepository>();
// May get slightly stale data because repo was created using
// PossiblyStale connection
var data = repo.GetData();
// Do something which won't suffer is data is slightly stale
}
}
One of the reasons why I've kept all the code based on interfaces is, of course, for unit testing. However, as you can see from the pseudo-implementation of RepositoryFactory.Create<TRepository>
, this is also a problem because I reach a point where I need to either :
determine the concret type associated to IARepository
in the DI container to pass it to ActivatorUtilities
in order to create an instance of it using the desired value of IConnection
while resolving other constructor parameters with IServiceProvider
, or
somehow tell IServiceProvider
to use a particular instance of IConnection
when getting a particular service
Is this at all possible using .NET Core DI?
(Bonus question: Should I have used another, simpler, approach?)
Update: I edited the sample code a little to hopefully make my intentions more clear. The idea is to allow the same repository, exact same code, to use different connections (which are configured during app startup) depending on the caller's specific needs. To summarise:
Several workarounds have come up to the problem of injecting the right connection in the factory:
Resolve dependencies using IServiceProvider You can use the IServiceCollection interface to create a dependency injection container. Once the container has been created, the IServiceCollection instance is composed into an IServiceProvider instance. You can use this instance to resolve services.
The interface-based dependency injection can be achieved by creating the common interface and other classes are implements this interface to inject the dependency. In this type of DI, we can use either constructor injection or setter injection. There is a built-in support of dependency injection in ASP.net Core.
ASP.NET Core injects objects of dependency classes through constructor or method by using built-in IoC container. The built-in container is represented by IServiceProvider implementation that supports constructor injection by default.
Since the IConnection
will not be created by the DI, you could remove it from the repository constructor and have it as a property, then on your factory you can assign its value after the creation:
interface IARepository
{
IConnection Connection { set; }
}
class ARepository : IARepository
{
public IConnection Connection { private get; set; }
public ARepository(IService1 svc1, IService2 svc2)
{ /* ... */ }
}
class RepositoryFactory : IRepositoryFactory
{
/* ... */
private IConnection Create<TRepository>(IConnection conn)
where TRepository : IARepository
{
var svc = _serviceProvider.GetService<TRepository>();
svc.Connection = conn;
return svc;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With