ServiceLocator Unity External Container Bug?

Topics: Issues / bugs
Jun 27, 2012 at 2:21 PM

Hi Geert,

i'm using catel 3.2 RC1 and enterprise library 5 (unity 2.0)

if i register an external unity container and keep it synchronized with catel i get the following exception in my autocad module BUT not in my application:

System.ArgumentException: Object of type 'Microsoft.Practices.Unity.ContainerControlledLifetimeManager' cannot be converted to type 'Microsoft.Practices.Unity.LifetimeManager'.

Server stack trace:
   at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
   at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
   at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Catel.IoC.UnityHelper.RegisterInstance(Object container, Type interfaceType, Object implementingInstance) in c:\Source\Catel\src\Catel.Core.NET35\IoC\Helpers\UnityHelper.cs:line 171
   at Catel.IoC.ServiceLocator.ExportInstancesToExternalContainers() in c:\Source\Catel\src\Catel.Core.NET35\IoC\ServiceLocator.cs:line 578
   at Catel.IoC.ServiceLocator.ExportToExternalContainers() in c:\Source\Catel\src\Catel.Core.NET35\IoC\ServiceLocator.cs:line 600
   at Catel.IoC.ServiceLocator.RegisterExternalContainer(Object externalContainer) in c:\Source\Catel\src\Catel.Core.NET35\IoC\ServiceLocator.cs:line 531

************** Loaded Assemblies **************

Microsoft.Practices.Unity
    Assembly Version: 2.1.505.0
    Win32 Version: 2.1.505.0
    CodeBase: C:/XXXXX/My AutoCad Module/Microsoft.Practices.Unity.DLL
   
Microsoft.Practices.Unity
    Assembly Version: 1.1.0.0
    Win32 Version: 1.1.0.0
    CodeBase: C:/Program Files/Autodesk/AutoCAD/Microsoft.Practices.Unity.DLL

i looked at the source code for the UnityHelper class and i suppose that UnityHelper creates types from the v1 Unity assembly which are not compatible with my unity version v2. It would be helpful if UnityHelper could resolve types from the (external registered) container's assembly to avoid such conflicts...

in my application i do not get this error because there only my unity assembly is loaded.

 

 

 

Coordinator
Jun 27, 2012 at 3:33 PM

Interesting. We updated to the latest version of Unity for Catel 3.2. What you might want to try is to forward the assemblies for unity to the latest version so all use 2.0.

Jun 27, 2012 at 3:59 PM

i think the problem lies in the following code:

// Catel.IoC.UnityHelper
private object CreateDefaultLifetimeManager()
{
	string typeName = "Microsoft.Practices.Unity.ContainerControlledLifetimeManager";
	typeName += string.Format(", {0}", UnityHelper.UnityAssembly);
	Type lifetimeManagerType = Type.GetType(typeName); // <----- this resolves in my acad module to unity v1 (loaded from acad/dir)
	return Activator.CreateInstance(lifetimeManagerType);
}

the autocad unity (v1) gets precendence over my unity (v2) assembly... and the ContainerControlledLifetimeManager from v1 is not compatible with v2....

it would be helpful if catel could create the ContainerControlledLifeTimeManager object from the assembly where the external registered container resides...

currently i have disabled container sychronization and i manually register all the view services in my unity container to avoid this problem.

Coordinator
Jun 27, 2012 at 5:41 PM

I will see if I can fix it tonight. It is the last thing I do before releasing RC 2.

Coordinator
Jun 27, 2012 at 5:44 PM

Catel automatically uses the dynamically loaded unity assembly. You simply use two different versions of unity at the same time, Catel cannot be prepared for something like that.

If you want it really fixed, you can fork it and provide a fix, but we are too busy working on WinRT as well to fix such minor issues.

Maybe this sounds cruel, but we all do this for free and we really need to manage our priorities, we are already way too busy with this :)

Jun 27, 2012 at 11:51 PM

Hi Geert,

i'm sorry ....you are right... it's not really a bug and catel cannot be prepared for this. I thought you would be interested in it and i did not want to offend you and your team. You are doing a really great job in providing such a cool framework for free! Please keep up the good work! My intention was only to help you make the library better and if you are still interested i created a fix for this issue:  

    internal class UnityHelper : IExternalContainerHelper
    {
        public string Name
        {
            get
            {
                return "Unity";
            }
        }

        public bool CanRegisterTypesWithoutInstantiating
        {
            get
            {
                return true;
            }
        }

        public bool IsValidContainer(object container)
        {
            Argument.IsNotNull("container", container);
            Type containerType = container.GetType();
            Type[] interfaces = containerType.GetInterfaces();
            return interfaces.Any((Type iface) => iface.FullName.Contains("IUnityContainer"));
        }

        public bool IsTypeRegistered(object container, Type interfaceType)
        {
            Argument.IsNotNull("container", container);
            Argument.IsNotNull("interfaceType", interfaceType);
            if (!this.IsValidContainer(container))
            {
                throw new NotSupportedException("Only Unity containers are supported");
            }
            Type containerType = container.GetType();
            PropertyInfo registrationsPropertyInfo = containerType.GetPropertyEx("Registrations", true, false);
            IEnumerable registrationsPropertyValue = (IEnumerable)registrationsPropertyInfo.GetValue(container, null);
            foreach (object containerRegistration in registrationsPropertyValue)
            {
                Type containerRegistrationType = containerRegistration.GetType();
                PropertyInfo registeredTypePropertyInfo = containerRegistrationType.GetPropertyEx("RegisteredType", true, false);
                Type registeredTypePropertyValue = registeredTypePropertyInfo.GetValue(containerRegistration, null) as Type;
                if (registeredTypePropertyValue == interfaceType)
                {
                    return true;
                }
            }
            return false;
        }

        public void RegisterType(object container, Type interfaceType, Type implementingType)
        {
            Argument.IsNotNull("container", container);
            Argument.IsNotNull("interfaceType", interfaceType);
            Argument.IsNotNull("implementingType", implementingType);
            if (!this.IsValidContainer(container))
            {
                throw new NotSupportedException("Only Unity containers are supported");
            }
            Type containerType = container.GetType();
            MethodInfo registerTypeMethodInfo = containerType.GetMethodEx("RegisterType", true, false);
            registerTypeMethodInfo.Invoke(container, new object[]
			{
				interfaceType,
				implementingType,
				null,
				this.CreateDefaultLifetimeManager(containerType.Assembly),
				this.CreateEmptyInjectionMemberArray(containerType.Assembly)
			});
        }

        public void RegisterInstance(object container, Type interfaceType, object implementingInstance)
        {
            Argument.IsNotNull("container", container);
            Argument.IsNotNull("interfaceType", interfaceType);
            Argument.IsNotNull("implementingInstance", implementingInstance);
            if (!this.IsValidContainer(container))
            {
                throw new NotSupportedException("Only Unity containers are supported");
            }
            Type containerType = container.GetType();
            MethodInfo registerInstanceMethodInfo = containerType.GetMethodEx("RegisterInstance", true, false);
            registerInstanceMethodInfo.Invoke(container, new object[]
			{
				interfaceType,
				null,
				implementingInstance,
				this.CreateDefaultLifetimeManager(containerType.Assembly)
			});
        }

        public object ResolveType(object container, Type interfaceType)
        {
            Argument.IsNotNull("container", container);
            Argument.IsNotNull("interfaceType", interfaceType);
            if (!this.IsValidContainer(container))
            {
                throw new NotSupportedException("Only Unity containers are supported");
            }
            if (!this.IsTypeRegistered(container, interfaceType))
            {
                throw new NotSupportedException(string.Format("Type '{0}' is not registered, so cannot be resolved", interfaceType));
            }
            Type containerType = container.GetType();
            MethodInfo resolveMethodInfo = containerType.GetMethodEx("Resolve", true, false);
            return resolveMethodInfo.Invoke(container, new object[]
			{
				interfaceType,
				null,
				this.CreateEmptyResolverOverrideArray(containerType.Assembly)
			});
        }

        private object CreateDefaultLifetimeManager(Assembly assembly)
        {
            Argument.IsNotNull("assembly", assembly);
            const string typeName = "Microsoft.Practices.Unity.ContainerControlledLifetimeManager";
            Type lifetimeManagerType = assembly.GetType(typeName);
            return Activator.CreateInstance(lifetimeManagerType);
        }

        private object CreateEmptyInjectionMemberArray(Assembly assembly)
        {
            Argument.IsNotNull("assembly", assembly);
            const string typeName = "Microsoft.Practices.Unity.InjectionConstructor";
            Type injectionMemberType = assembly.GetType(typeName);
            return Array.CreateInstance(injectionMemberType, 0);
        }

        private object CreateEmptyResolverOverrideArray(Assembly assembly)
        {
            Argument.IsNotNull("assembly", assembly);
            const string typeName = "Microsoft.Practices.Unity.ResolverOverride";
            Type resolverOverrideType = assembly.GetType(typeName);
            return Array.CreateInstance(resolverOverrideType, 0);
        }
    }

The CreateXX functions now use the assembly in which the object container type resides to resolve InjectionConstructor, ResolverOverride and ContainerControlledLifetimeManager. If you don't want to integrate it, it's no problem... i can live with the above mentioned workaround and i understand your priorities...

Coordinator
Jun 28, 2012 at 10:24 AM

Excellent. I think I can convert this into a "BaseHelper" which does this a bit better. Then we can also make it work for SL as well (because that works a bit different).

Thanks for the effort.

Jun 28, 2012 at 2:45 PM

Thanks for taking it into account....

Coordinator
Jun 28, 2012 at 6:03 PM

It's fixed. I am currently releasing a new nightly build so you can test it. If all goes well, I will create an official release on sunday.

Jun 29, 2012 at 1:09 PM

Sorry for the delay... i will test it today... many thanks! i'll post a message about the result....

Jun 29, 2012 at 10:13 PM

It works perfectly! Catel resolves now the "correct" assembly for the registered unity container. Thanks a lot for this fast fix!!!

Coordinator
Jul 2, 2012 at 6:53 PM

Great to hear :)

Thanks for helping developing this feature. If you have more feature request, keep them coming (especially if you already provide the source code :))