Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it a good practice to create a new instance of ContainerBuilder?

The following sample code basically call multiple dummy services, and combine the result. (In real application, they will be web services.)

Question: Is it a good practice to create a new instance of ContainerBuilder? Please see a couple of ***** in the Main method.

If not, could you point me to a right direction? I'm open to any suggestion.

Note: Build() or Update() method can only be called once on a ContainerBuilder.

Output

enter image description here

Main

private static void Main(string[] args)
{
    var builder = new ContainerBuilder();
    var container = builder.Build();

    // ***** Create a new instance of ContainerBuilder *****
    builder = new ContainerBuilder(); 
    builder.RegisterType<AlfaService>().As<IService>().Named<IService>("a");
    builder.RegisterType<BravoService>().As<IService>().Named<IService>("b");
    builder.Update(container);

    // ***** Create a new instance of ContainerBuilder *****
    builder = new ContainerBuilder(); 
    var services = new Dictionary<string, IService>
    {
        {"a", container.ResolveNamed<IService>("a")},
        {"b", container.ResolveNamed<IService>("b")}
    };
    builder.RegisterType<CompositeService>().As<ICompositeService>()
        .WithParameter("services", services);
    builder.Update(container);


    // The following is for testing purpose only.
    // In real application, I'll inject ICompositeService to MVC controller.
    using (ILifetimeScope scope = container.BeginLifetimeScope())
    {
        IList<int> ids = scope.Resolve<ICompositeService>().GetIdsBySource("a");
        Console.WriteLine("AlfaService: " + string.Join(", ", ids));

        ids = scope.Resolve<ICompositeService>().GetAllIds();
        Console.WriteLine("All Services: " + string.Join(", ", ids));
    }
    Console.ReadLine();
}

Interfaces

public interface ICompositeService
{
    IList<int> GetIdsBySource(string source);

    IList<int> GetAllIds();
}

public interface IService
{
    IList<int> GetIds();
}

Services

public class CompositeService : ICompositeService
{
    private readonly Dictionary<string, IService> _services;

    public CompositeService(Dictionary<string, IService> services)
    {
        _services = services;
    }

    public IList<int> GetIdsBySource(string source)
    {
        return _services.Where(x => x.Key == source)
            .Select(x => x.Value).First().GetIds();
    }

    public IList<int> GetAllIds()
    {
        return _services.SelectMany(x => x.Value.GetIds()).ToList();
    }
}

public class AlfaService : IService
{
    public IList<int> GetIds() { return new List<int> {1, 2, 3}; }
}

public class BravoService : IService
{
    public IList<int> GetIds() { return new List<int> {4, 5, 6}; }
}
like image 922
Win Avatar asked Sep 06 '25 03:09

Win


1 Answers

To resolve something in your parameter, you don't have to build the container. Building a container takes time and should be avoided if possible.

The simplest way to do resolve something inside your parameter is to use the WithParameter method like this :

builder.RegisterType<CompositeService>()
        .As<ICompositeService>()
        .WithParameter((pi, c) => pi.Name == "services", (pi, c) =>
        {
            return new Dictionary<string, IService> {
                { "a", c.ResolveNamed<IService>("a") }, 
                { "b", c.ResolveNamed<IService>("b") } 
            };
        });

Another solution would be to create your own Parameter

public class ServiceParameter : Parameter
{
    public override Boolean CanSupplyValue(ParameterInfo pi,
        IComponentContext context, out Func<Object> valueProvider)
    {
        valueProvider = null;
        if (pi.Name == "services" 
            && pi.ParameterType == typeof(Dictionary<String, IService>))
        {
            valueProvider = () =>
            {
                return new Dictionary<string, IService> {
                    { "a", context.ResolveNamed<IService>("a") }, 
                    { "b", context.ResolveNamed<IService>("b") } 
                };
            };
        }
        return valueProvider != null;
    }
}

And register it this way :

builder.RegisterType<CompositeService>()
        .As<ICompositeService>()
        .WithParameter(new ServiceParameter());

In your case, instead of Named service you may want to use Metadata

builder.RegisterType<AlfaService>().As<IService>().WithMetadata("Key", "a");
builder.RegisterType<BravoService>().As<IService>().WithMetadata("Key", "b");

In CompositeService you will have a dependency on IEnumerable<Meta<IService>>

public CompositeService(IEnumerable<Meta<IService>> services)
{
    _services = services.ToDictionary(m => (String)m.Metadata["Key"], m => m.Value);
}

If you don't want to introduce dependency on Meta on your CompositeService you can use a custom Parameter which will translate your IEnumerable<Meta<IService>> to IDictionary<String, IService>

public class ServiceParameter : Parameter
{
    public override Boolean CanSupplyValue(ParameterInfo pi,
        IComponentContext context, out Func<Object> valueProvider)
    {
        valueProvider = null;
        if (pi.Name == "services" 
            && pi.ParameterType == typeof(Dictionary<String, IService>))
        {
            valueProvider = () =>
            {
                IEnumerable<Meta<IService>> services = 
                    context.Resolve<IEnumerable<Meta<IService>>>(); 
                return services.ToDictionary(m => (String)m.Metadata["Key"], 
                                             m => m.Value);
            };
        }
        return valueProvider != null;
    }
}
like image 157
Cyril Durand Avatar answered Sep 07 '25 21:09

Cyril Durand