I want to register multiple instances of a class in a container. Each instance is identified by its ID property.
public interface MyInterface
{
string Id { get; }
}
public class MyImpl : MyInterface
{
public string Id { get; }
public MyImpl(string id) => Id = id;
}
And, I want to receive this instance in the constructor of another class.
using DryIoc;
public class User
{
public MyInterface Obj { get; }
public User(string id, Func<string, MyInterface> getObj)
=> Obj = getObj(id);
}
class Program
{
static void Main()
{
Container container = new();
container.RegisterInstance<MyInterface>(new MyImpl("A"), serviceKey: "A");
container.RegisterInstance<MyInterface>(new MyImpl("B"), serviceKey: "B");
container.Register<User>();
var user = container.Resolve<Func<string, User>>()("A");
}
}
This code throws ContainerException.
DryIoc.ContainerException: code: Error.UnableToResolveFromRegisteredServices;
message: Unable to resolve MyInterface with passed arguments [String@0] (IsWrappedInFunc, IsDirectlyWrappedInFunc)
in wrapper Func<String, MyInterface> as parameter "getObj" WrapperExpressionFactoryWithSetup FactoryId=158 with passed arguments [String@0] (IsWrappedInFunc)
in User FactoryId=146 with passed arguments [String@0] (IsWrappedInFunc, IsDirectlyWrappedInFunc)
in resolution root wrapper System.Func`2[System.String,User] WrapperExpressionFactoryWithSetup FactoryId=158 with passed arguments [String@0]
from container without scope
with normal and dynamic registrations:
("B", {FactoryID=145, ImplType=MyImpl, Reuse=Singleton {Lifespan=1000}}) ("A", {FactoryID=144, ImplType=MyImpl, Reuse=Singleton {Lifespan=1000}})
The desired instance can be obtained by specifying the serviceKey in Container.Resolve(). However, since the User class is constructor-injected into another instance, we do not want to call Contaienr's methods directly.
I think that I can specify made appropriately in the argument of Register(). How should I specify it?
Edit
For now, create an object holder and work around the problem.
public static class Holder<T> where T : class
{
private static readonly Dictionary<string, T> _dictionary = new();
public static void Add(string id, T obj) => _dictionary.Add(id, obj);
public static void Remove(string id) => _dictionary.Remove(id);
public static T Get(string id) => _dictionary[id];
}
interface IService
{
string Id { get; }
}
class Service : IService
{
public string Id { get; }
public Service(string id) => Id = id;
}
interface IServiceUser
{
IService Service { get; }
}
class ServiceUser : IServiceUser
{
public IService Service { get; }
public ServiceUser(IService service) => Service = service;
}
class ViewModel
{
public ViewModel(IServiceUser serviceUser)
{
Console.WriteLine(serviceUser.Service.Id);
}
}
static class Program
{
static void Main()
{
Holder<IService>.Add("A", new Service("A"));
Holder<IService>.Add("B", new Service("B"));
Container container = new();
container.Register(made: Made.Of(() => Holder<IService>.Get(Arg.Of<string>())));
container.Register<IServiceUser, ServiceUser>();
container.Register<ViewModel>();
var createVm = container.Resolve<Func<string, ViewModel>>();
var vmA = createVm("A");
var vmB = createVm("B");
}
}
But since container can hold objects, I am thinking that if I can transfer the value passed in Func to serviceKey, there is no need for Holder class.
You may replace the Func<string, MyInterface> getObj
with the IEnumerable<KeyValuePair<<string, MyInterface>>> getObj
so it will serve as a dictionary to get your instance (DryIoc currently does not support Dictionary
directly, but may be in future).
Here is the full example (https://dotnetfiddle.net/kxZCck):
using System;
using System.Collections.Generic;
using System.Linq;
using DryIoc;
public interface MyInterface
{
string Id { get; }
}
public class MyImpl : MyInterface
{
public string Id { get; }
public MyImpl(string id) => Id = id;
}
public class User
{
public MyInterface Obj { get; }
public User(string id, IEnumerable<KeyValuePair<string, MyInterface>> getObj)
=> Obj = getObj.First(kv => kv.Key == id).Value;
}
class Program
{
static void Main()
{
Container container = new();
container.RegisterInstance<MyInterface>(new MyImpl("A"), serviceKey: "A");
container.RegisterInstance<MyInterface>(new MyImpl("B"), serviceKey: "B");
container.Register<User>();
var user = container.Resolve<Func<string, User>>()("A");
Console.WriteLine(((MyImpl)user.Obj).Id);
}
}
Here is Dictionary feature issue to track: https://github.com/dadhi/DryIoc/issues/504
Here is the solution where you don't need Holder
and don't need to pollute your service constructors with dictionaries (https://dotnetfiddle.net/IAhGTg):
using System;
using System.Collections.Generic;
using DryIoc;
interface IService
{
string Id { get; }
}
class Service : IService
{
public string Id { get; }
public Service(string id) => Id = id;
}
interface IServiceUser
{
IService Service { get; }
}
class ServiceUser : IServiceUser
{
public IService Service { get; }
public ServiceUser(IService service) => Service = service;
}
class ViewModel
{
public ViewModel(IServiceUser serviceUser)
{
Console.WriteLine(serviceUser.Service.Id);
}
}
static class Program
{
static void Main()
{
var container = new Container();
container.RegisterInstance<IService>(new Service("A"), serviceKey: "A");
container.RegisterInstance<IService>(new Service("B"), serviceKey: "B");
// Register a delegate returning a service without key selecting the result
// from the collection of keyed services with their keys and using the key provided by Func<string,...>
container.RegisterDelegate<KeyValuePair<string, IService>[], string, IService>(
(services, serviceKey) => Array.Find(services, s => s.Key == serviceKey).Value
);
container.Register<IServiceUser, ServiceUser>();
container.Register<ViewModel>();
var createVm = container.Resolve<Func<string, ViewModel>>();
var vmA = createVm("A");
var vmB = createVm("B");
}
}