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 IFoos 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.