I'm trying to resolve instances by key with SimpleInjector.
In my case, the keys are strings which are coming from a configuration file, and I need the factory to return the correct type based on the string.
I used a similar solution like the one described in the link above, but changed it slightly, so the instances can provide their own keys.
(there will be many classes that implement IFoo
, so I'd like to auto-register them with their keys)
Here is the complete working example (.NET Core console app):
(I kept it short for readability, so there's only one class that implements IFoo
, and I omitted the auto-register code)
using SimpleInjector;
using System;
using System.Collections.Generic;
namespace SimpleInjectorTest1
{
public interface IFoo
{
string Name { get; }
}
public class SpecificFoo : IFoo
{
public string Name { get { return "foooo"; } }
}
public interface IFooFactory
{
void Add(IFoo foo);
IFoo Create(string fooName);
}
public class FooFactory : Dictionary<string, IFoo>, IFooFactory
{
public void Add(IFoo foo)
{
// use the instance's Name property as dictionary key, so I don't
// need to hard-code it in the code which does the registration
this.Add(foo.Name, foo);
}
public IFoo Create(string fooName)
{
return this[fooName];
}
}
public class Program
{
public static void Main(string[] args)
{
var container = new Container();
// TODO: loop everything that implements IFoo, create
// an instance and add it to the factory
var factory = new FooFactory();
factory.Add(new SpecificFoo());
container.RegisterSingleton<IFooFactory>(factory);
container.Verify();
// usage
var factory2 = container.GetInstance<IFooFactory>();
IFoo foo = factory2.Create("foooo");
Console.WriteLine("Success!");
}
}
}
This worked perfectly well in the beginning, until I realized that SpecificFoo
(and the other IFoo
s as well) needs a dependency via SimpleInjector.
So when I add SpecificFoo
to the factory, I need to create the instance via SimpleInjector instead of new SpecificFoo()
.
So I changed my code as shown below:
using SimpleInjector;
using System.Collections.Generic;
namespace SimpleInjectorTest2
{
// dummy dependency
public interface IBar { }
public class Bar : IBar { }
// marker interface
public interface IFoo
{
string Name { get; }
}
public interface ISpecificFoo : IFoo
{
// empty by purpose
}
public class SpecificFoo : ISpecificFoo, IFoo
{
private readonly IBar bar;
public SpecificFoo(IBar bar) { this.bar = bar; }
public string Name { get { return "foooo"; } }
}
public interface IFooFactory
{
void Add(IFoo foo);
IFoo Create(string fooName);
}
public class FooFactory : Dictionary<string, IFoo>, IFooFactory
{
public void Add(IFoo foo)
{
// use the instance's Name property as dictionary key, so I don't
// need to hard-code it in the code which does the registration
this.Add(foo.Name, foo);
}
public IFoo Create(string fooName)
{
return this[fooName];
}
}
public class Program
{
public static void Main(string[] args)
{
var container = new Container();
container.Register<IBar, Bar>();
var factory = new FooFactory();
// TODO: loop everything that implements IFoo, create
// an instance and add it to the factory
container.Register<ISpecificFoo, SpecificFoo>();
factory.Add(container.GetInstance<ISpecificFoo>());
// The next line throws an exception because of this:
// https://simpleinjector.readthedocs.io/en/latest/decisions.html#the-container-is-locked-after-the-first-call-to-resolve
container.RegisterSingleton<IFooFactory>(factory);
}
}
}
As already said above, the registration of the factory fails because the container is locked after the GetInstance
call.
I know that I could change the factory to inherit from Dictionary<string, Func<IFoo>>
instead (like shown in Resolve instances by key in the docs), but then I need to provide the string key on registration, like shown in the example in the docs:
container.RegisterSingle<IRequestHandlerFactory>(new RequestHandlerFactory
{
{ "default", () => container.GetInstance<DefaultRequestHandler>() },
{ "orders", () => container.GetInstance<OrdersRequestHandler>() },
{ "customers", () => container.GetInstance<CustomersRequestHandler>() },
});
How can I use a factory to resolve types by key, but still let the types provide their keys themselves?
I don't want to have to add a line like above in the registration code, each time I add a new class that implements IFoo
.
I already read Registration of open generic types (and answers like this one as well), but I think it doesn't apply to my situation, because I need to resolve by string keys.