Search code examples
c#dryioc

DryIoc - Register multiple instance with ID


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.


Solution

  • 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

    Update - the solution without Holder

    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");
        }
    }