Disambiguating multiple constructors in UnityContainer.Resolve

Jan 6, 2011 at 5:25 PM
Edited Jan 6, 2011 at 9:09 PM

I am just now learning Unity and, to be honest, a little lost right now. I was able to get it to inject a custom type with a single constructor, but it is unable to disambiguate if I have more than one... and I am not sure what I need to do to get around this.

(FYI, I am trying to get it to return something equeivilent to "this.impl = new EmployerMedAuth(IoC)" in the code below)

Mainline WCF REST Service Code (REST attributes removed):

public class EmployerMedAuthSvc : EmployerBase, IEmployerMedAuth
{
    private IEmployerMedAuth impl;
    private IUnityContainer IoC;

    public EmployerMedAuthSvc()
    {
        IoC = new UnityContainer().AddNewExtension<EnterpriseLibraryCoreExtension>();

        base.Logger = ioc.Resolve<LogWriter>();
        base.TraceMgr = ioc.Resolve<TraceManager>();
        base.ExceptionMgr = ioc.Resolve<ExceptionManager>();

        IoC.RegisterType<IEmployerMedAuth, EmployerMedAuth>();
        this.impl = IoC.Resolve<IEmployerMedAuth>();
    }
}

Service Implementation Code:

public class EmployerMedAuth : EmployerBase, IEmployerMedAuth
{
    private readonly IEmpMedAuthRepository repository;

    public EmployerMedAuth() : this(new UnityContainer().AddNewExtension<EnterpriseLibraryCoreExtension>()) { }

    public EmployerMedAuth(IUnityContainer ioc)
    {
        base.Logger = ioc.Resolve<LogWriter>();
        base.TraceMgr = ioc.Resolve<TraceManager>();
        base.ExceptionMgr = ioc.Resolve<ExceptionManager>();

        ioc.RegisterType<IEmpMedAuthRepository, SqlEmpMedAuthRepository>();
        this.repository = ioc.Resolve<IEmpMedAuthRepository>();
    }

    //[Inject]
    public EmployerMedAuth(IEmpMedAuthRepository repository)
    {
        this.repository = repository;
    }
}

Jan 6, 2011 at 6:36 PM

The convention Unity uses is straightforward - in the absence of other configuration, it will choose the constructor with the longest parameter list. If that is ambiguous, then it'll throw an exception. Using my psychic debugging skills (since you didn't post an exception message or the actual error) that you're trying to resolve EmployerMedAuth and hitting this problem?

Since there's an IEmployerMedAuth interface, I'm going to assume that's the type you're actually resolving. So, you've got a container.RegisterType call somewhere. That's where you'd tell the container which constructor to call:

 

container.RegisterType<IEmployerMedAuth, EmployerMedAuth>(
    new InjectionConstructor(typeof(IUnityContainer)));

 

This call tells the container not only to map the interface to the implementation, but when creating that implementation to call the constructor that takes an IUnityContainer parameter. That should get what you have working.

However, what you've got is, well, doing it wrong.


You're not doing dependency injection, you're following a pattern called service location instead. Service location loses many of the benefits of DI. Basically, you've coupled your entire system to the container, and when testing there's nothing to tell you without reading the code what should actually be in the container.

What you should do is not pass the container around and pull stuff out, but have the objects take as dependencies the things that you were pulling out of the container. So don't do this:

    public EmployerMedAuth(IUnityContainer ioc)
    {
        base.Logger = ioc.Resolve<LogWriter>();
        base.TraceMgr = ioc.Resolve<TraceManager>();
        base.ExceptionMgr = ioc.Resolve<ExceptionManager>();

        ioc.RegisterType<IEmpMedAuthRepository, SqlEmpMedAuthRepository>();
        this.repository = ioc.Resolve<IEmpMedAuthRepository>();
    }

Instead, do this:

    public EmployerMedAuth(LogWriter logger, TraceManager traceMgr, ExceptionManager exceptionMgr, IEmpMedAuthRepository repository)
    {
        base.Logger = logger;
        base.TraceMgr = traceMgr;
        base.ExceptionMgr = exceptionMgr;
        this.repository = repository;
    }

Take the dependencies directly, don't indirect through the container. It makes it much more explicit exactly which services this class is using, which makes design, maintenance, and testing easier.

The only wrinkle is that call to RegisterType in the constructor. I'd argue that call doesn't belong there. First off, the way you're using it, you might as well just take SqlEmpMedAuthRepository directly, since you're hard wiring it, so why bother with the type mapping? Second, container configuration should really be centralized in a single spot in your application. Typically you'll have whats called a bootstrapper class that is responsible for setting up the container. With large or modular applications, there are often multiple bootstrapper classes. But the point is that the Bootstrapper is the only place that actually touches the container. Everywhere else, types are written that just take their dependencies directly. If you need different types injected for the same interface, use named registrations. It all works out.

If you follow this advice, the net result will be a simpler, easier to work with system. You're headed towards serious spaghetti right now.

 

 

Jan 6, 2011 at 10:55 PM

Excellent response. This was exactly what I was looking for. It's much cleaner now.

BTW... I agree that RegisterType should not be done in the constructor, that was only being done temporarily while trying to figure out all the moving parts in Unity.

The bootstrapper class sounds interesting... is there any sample code showing how this would be done?

Jan 7, 2011 at 4:25 AM

There's nothing special about a bootstrapper - it's just a class that contains code to initialize the container. You can be fancy about it, but the important part is simply to segregate the container initialization in a single spot in your code. That keeps things easy to follow and update later.

 

Jan 24, 2012 at 10:44 PM

Could you please teel me how to do this using the xml configuration. I have the following object

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SziCom.Degas.Abi.Core;
using SziCom.Degas.Data.SISTEMA.Tables;
using SziCom.Degas.Data.SISTEMA;

namespace SziCom.Degas.Jobs.Abi
{
    public class ParametrosAbi
    {
        public AbiShipper Shipper { get; set; }
        public Gasoducto Gasoducto { get; set; }
        public MarcaMensual Marca { get; set; }

        private event EventHandler OnGetUsuario;

        private UsuarioABI _Usuario;
        public UsuarioABI Usuario
        {
            get
            {
                if (this.OnGetUsuario != null)
                {
                    OnGetUsuario(null, null);
                }
                return _Usuario;
            }
        }

        public ParametrosAbi(UsuarioABI usuario)
        {
            OnGetUsuario += (sender, e) =>
            {
                _Usuario = usuario;
            };

        }


        public ParametrosAbi(SistemaService sistema)
        {
            OnGetUsuario += (sender, e) =>
            {
                _Usuario = new UsuarioABI() 
                {
                    Nombre = sistema.GetUsuarioABI(),
                    Password = sistema.GetPasswordABI() 
                };
            };

        }

    }
}

 

As you can see it has 2 constructor of 1 parameter each. In the xml i have the following:

      <register type="ParametrosAbi" name="ParametrosAbiCertificado25">
        <lifetime type="singleton"/>
        <constructor>
          <param name="sistema">
            <dependency />
          </param>
        </constructor>
      </register>

This is in order to use the second constructor (the one that the parameter names matches). But this does not work on unity 2.0. I get the following error:

FullName : Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
AppDomainName : SziCom.Degas.Quartz.exe
ThreadIdentity : 
WindowsIdentity : PC-PABLO\Administrador
	Inner Exception
	---------------
	Type : System.InvalidOperationException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
	Message : The type ParametrosAbi has multiple constructors of length 1. Unable to disambiguate.
	Source : Microsoft.Practices.Unity
	Help link : 
	Data : System.Collections.ListDictionaryInternal
	TargetSite : System.Reflection.ConstructorInfo FindLongestConstructor(System.Type)
	Stack Trace :    en Microsoft.Practices.ObjectBuilder2.ConstructorSelectorPolicyBase`1.FindLongestConstructor(Type typeToConstruct) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\Creation\ConstructorSelectorPolicyBase.cs:línea 113
	   en Microsoft.Practices.ObjectBuilder2.ConstructorSelectorPolicyBase`1.SelectConstructor(IBuilderContext context, IPolicyList resolverPolicyDestination) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\Creation\ConstructorSelectorPolicyBase.cs:línea 38
	   en Microsoft.Practices.ObjectBuilder2.DynamicMethodConstructorStrategy.PreBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\DynamicMethod\Creation\DynamicMethodConstructorStrategy.cs:línea 69
	   en Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\StrategyChain.cs:línea 110
	   en Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlanCreatorPolicy.CreatePlan(IBuilderContext context, NamedTypeBuildKey buildKey) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\DynamicMethod\DynamicMethodBuildPlanCreatorPolicy.cs:línea 48
	   en Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\BuildPlanStrategy.cs:línea 37
	   en Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\StrategyChain.cs:línea 110
	   en Microsoft.Practices.ObjectBuilder2.BuilderContext.NewBuildUp(NamedTypeBuildKey newBuildKey) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\BuilderContext.cs:línea 215
	   en Microsoft.Practices.Unity.ObjectBuilder.NamedTypeDependencyResolverPolicy.Resolve(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilderCustomization\NamedTypeDependencyResolverPolicy.cs:línea 51
	   en BuildUp_SziCom.Degas.Jobs.Abi.BaseDescargaAbiJob(IBuilderContext )
	   en Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\DynamicMethod\DynamicMethodBuildPlan.cs:línea 37
	   en Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\BuildPlanStrategy.cs:línea 43
	   en Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\StrategyChain.cs:línea 110
	   en Microsoft.Practices.ObjectBuilder2.BuilderContext.NewBuildUp(NamedTypeBuildKey newBuildKey) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\BuilderContext.cs:línea 215
	   en Microsoft.Practices.Unity.ObjectBuilder.NamedTypeDependencyResolverPolicy.Resolve(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilderCustomization\NamedTypeDependencyResolverPolicy.cs:línea 51
	   en Microsoft.Practices.Unity.ResolvedArrayWithElementsResolverPolicy.DoResolve[T](IBuilderContext context, IDependencyResolverPolicy[] elementPolicies) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilderCustomization\ResolvedArrayWithElementsResolverPolicy.cs:línea 73
	   en Microsoft.Practices.Unity.ResolvedArrayWithElementsResolverPolicy.Resolve(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilderCustomization\ResolvedArrayWithElementsResolverPolicy.cs:línea 64
	   en BuildUp_SziCom.Degas.Quartz.QuartzService(IBuilderContext )
	   en Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\DynamicMethod\DynamicMethodBuildPlan.cs:línea 37
	   en Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\BuildPlan\BuildPlanStrategy.cs:línea 43
	   en Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\ObjectBuilder\Strategies\StrategyChain.cs:línea 110
	   en Microsoft.Practices.Unity.UnityContainer.DoBuildUp(Type t, Object existing, String name, IEnumerable`1 resolverOverrides) en e:\Builds\Unity\UnityTemp\Compile\Unity\Unity\Src\UnityContainer.cs:línea 511

If i set the attribute [Microsoft.Practices.Unity.InjectionConstructor] it works ok. I think unity should be able to resolve this based on the name of the parameter in the constructor or else using the type. I also try that in the dependecy element with the same result.

Is this even possible???

Jan 25, 2012 at 12:10 AM

The configuration looks good.  The only thing is that the type ParametrosAbi is registered by name in the configuration file so to retrieve the instance from the container the name must be used:

var parametrosAbi = container.Resolve<ParametrosAbi>("ParametrosAbiCertificado25");

With the name the correct constructor is used.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com 

Jan 25, 2012 at 3:09 PM

I found my error. The code posted before is only a part. I am actually using the xml config to generate all of my object. The problem is that i use a lot of registry objects that have dependency on other objectc referenced by name.

 <register type="DegasQuartzJob" mapTo="BaseDescargaAbiJob" name="ProcesosDescargaEstadoLinepack">
        <constructor>
          <param name="Nombre" value="Descarga Estado Linepack"></param>
          <param name="ProcesoAbi" dependencyName="EstadoLinepack"></param>
          <param name="ParametrosAbi" dependencyName="ParametrosAbi"></param>
          <param name="ProcesoDegasAbi" dependencyName="EstadoLinepackDegasAbi"></param>
          <param name="Momento" dependencyName="Diario">
          </param>
        </constructor>

The problem is that if the dependencyName does not exist, Unity tries to create that object. Since the class has 2 constructor with the same ammount of paramters, i get the error  The type ParametrosAbi has multiple constructors of length 1. Unable to disambiguate. The error message could be that Unity did not found an object of that type named [The name ob the dependecyName], instead of trying to create the object. This is because i am passing a dependencyName. In the cases that this parameter is not defined, it should try to create the object.

This is only an opinion.

Thanks for answering