Registration convention generates two registrations for each mapping

Jul 5, 2013 at 4:50 PM
I created some registration conventions and in the GetFromTypes() method I used the WithMappings.FromAllInterfaces(t) with some filters.

but when I see the registration result, there are two registrations, one for the correct interface and one for the same type i'm registering.

Is that the correct behavior?
Will I have some performance issues if I have thousands of registrations?
if so in this case I could reduce my registrations in 50%.
Jul 8, 2013 at 3:18 AM
Can you be more specific with what you are seeing...perhaps with a short example?

I personally haven't run any performance tests on a large number of registrations but I would guess that any potential time would be spent during assembly scanning and creating the registrations. Once registered, I would think they would be accessed relatively inexpensively. Memory would be higher with a larger number of registrations.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jul 8, 2013 at 2:08 PM
Let me give an example:

I have an registration convention with this methods
        public override System.Collections.Generic.IEnumerable<System.Type> GetTypes()
        {
            var retorno = Microsoft.Practices.Unity.AllClasses.FromLoadedAssemblies()
                .Where(
                    a => a.Assembly.GetReferencedAssemblies()
                        .Any(n => n.FullName == this.GetType().Assembly.FullName));

            retorno = retorno.Where(t=>t.GetCustomAttributes(typeof(AutoLoadAttribute),false).Count()>0);
            return retorno;
        }

        public override System.Func<System.Type, System.Collections.Generic.IEnumerable<System.Type>> GetFromTypes()
        {
            return t => GetInterfaces(t);
        }

        private IEnumerable<Type> GetInterfaces(Type t)
        {
            var retorno =  WithMappings.FromAllInterfaces(t);
            retorno = retorno.Where(i => GetInterface(i));
            retorno = retorno.Select(i => i.IsGenericType && i.FullName == null ? i.GetGenericTypeDefinition() : i);
            return retorno;
        }

        private bool GetInterface(Type t)
        {
            var att = t.GetCustomAttributes(false);
            foreach (var item in att)
            {
                if (item is AutoLoadInterfaceAttribute)
                {
                    return true;
                }
            }
            return false;
        }
So, to complete the example , if I have an interface IService<TAction> that is decorated with the AutoLoadInterfaceAttribute from the GetInterface method, and a Service decorated with the AutoLoadAttribute, I should have one line of registration, where IService Maps to Service.


But when I see the registrations I have one IService mapping to service, and one Service mapping to Service again, why is this second registration?
Did I make some mistake or is this the correct way of using the registrations?

I think that if I want to register the implementation to itself, I would explicitly register in the GetFromTypes method.

Thanks about the performance clarification.

Do you have production projects with about how many registrations?
Jul 8, 2013 at 3:16 PM
How are you registering the classes? I assume that you want to map IService<T> to Service<T> you can do this using:
IUnityContainer container = new UnityContainer();

container.RegisterTypes(
    GetTypes(), // your method to return classes marked with AutoLoadAttribute
    WithMappings.FromMatchingInterface, // Add "I" in front of type name to create "From" mapping
    WithName.Default);

This will create one registration for IService<T>.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jul 8, 2013 at 5:44 PM
In this point you're right, but i'm not using that way, but it helped me reproduce it.

I wasn't using this method, I created some classes that extended RegistrationConvention class, and registered those classes in the unity.config file, so in the startup I used the container.resolveAll<Registrationconvention> and then container.RegisterTypes(RegistrationConvention).


But when I used your method it worked fine, so that's my method that work fine
            container.RegisterTypes(new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetTypes(),
                new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetFromTypes()
                , new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetName()); 
as you can see I'm using the same method in my previous class, but it's not complete anymore, so, when I changed it to
            container.RegisterTypes(new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetTypes(),
                new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetFromTypes()
                , new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetName(),
                new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetLifetimeManager(), 
                getInjectionMembers: new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetInjectionMembers(),
                overwriteExistingMappings: false); 
It started duplicating again, and after some tests, I realized that it was because of the GetLifetimemanager Method, so this is the final example:

this one duplicates the registrations
            container.RegisterTypes(new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetTypes(),
                new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetFromTypes()
                , new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetName(),
                t => new Microsoft.Practices.Unity.TransientLifetimeManager(), 
                getInjectionMembers: new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetInjectionMembers(),
                overwriteExistingMappings: false); 
this one doesn't duplicate the registrations
            container.RegisterTypes(new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetTypes(),
                new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetFromTypes()
                , new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetName(),
                t => null, 
                getInjectionMembers: new Unity.AutoRegistration.Attributes.AttributeEmptyRegistrationConvention().GetInjectionMembers(),
                overwriteExistingMappings: false); 
now I'm not sure anymore if I'm doing something wrong or if it's a bug.
Jul 10, 2013 at 4:54 AM
It's hard to tell. Can you post a full (yet relatively simple) example that demonstrates the behavior?

Thanks,

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jul 10, 2013 at 1:44 PM
I created a new project, got unity from nuget and typed this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Practices.Unity;

namespace RegistrationConvention
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Registration without lifetimeManager");
            using (var container = new UnityContainer())
            {
                container.RegisterTypes(Microsoft.Practices.Unity.AllClasses.FromAssembliesInBasePath(), t => t.GetInterfaces(),
                    WithName.Default,null,null,false);

                foreach (var item in container.Registrations)
                {
                    Console.WriteLine(item.RegisteredType + " - " + item.MappedToType + " - " + item.Name);
                }
                
            }

            Console.WriteLine("\n\n\nRegistration with lifetimeManager");

            using (var container = new UnityContainer())
            {
                container.RegisterTypes(Microsoft.Practices.Unity.AllClasses.FromAssembliesInBasePath(), t=>t.GetInterfaces(),
                    WithName.Default, f=>new TransientLifetimeManager(), null, false);

                foreach (var item in container.Registrations)
                {
                    Console.WriteLine(item.RegisteredType + " - " + item.MappedToType + " - " + item.Name);
                }
            }

            Console.ReadLine();
        }
    }

    public interface IInterface
    {
        void Method();
    }

    public class Class1 : IInterface
    {

        public void Method()
        {
            Console.WriteLine("X");
        }
    }
}
and got this result


Registration without lifetimeManager
Microsoft.Practices.Unity.IUnityContainer - Microsoft.Practices.Unity.IUnityContainer -
RegistrationConvention.IInterface - RegistrationConvention.Class1 -



Registration with lifetimeManager
Microsoft.Practices.Unity.IUnityContainer - Microsoft.Practices.Unity.IUnityContainer -
RegistrationConvention.Program - RegistrationConvention.Program -
RegistrationConvention.IInterface - RegistrationConvention.Class1 -
RegistrationConvention.Class1 - RegistrationConvention.Class1 -



do you want the zip from the project?
Jul 11, 2013 at 5:58 AM
Thanks for providing the example. I was hitting some runtime errors with system types and duplicate keys so I tweaked the code to the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Practices.Unity;

namespace RegistrationConvention
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Registration without lifetimeManager");
            using (var container = new UnityContainer())
            {
                container.RegisterTypes(AllClasses.FromLoadedAssemblies(false, false, false, false), 
                    t => t.GetInterfaces(),
                    WithName.Default, null, null, true);

                foreach (var item in container.Registrations)
                {
                    Console.WriteLine(item.RegisteredType + " - " + item.MappedToType + " - " + item.Name);
                }
            }

            Console.WriteLine("\n\n\nRegistration with lifetimeManager");

            using (var container = new UnityContainer())
            {
                container.RegisterTypes(AllClasses.FromLoadedAssemblies(false, false, false, false),
                    t => t.GetInterfaces(),
                    WithName.Default, t => new TransientLifetimeManager(), null, true);

                foreach (var item in container.Registrations)
                {
                    Console.WriteLine(item.RegisteredType + " - " + item.MappedToType + " - " + item.Name);
                }
            }

            Console.ReadLine();
        }
    }

    public interface IInterface
    {
        void Method();
    }

    public class Class1 : IInterface
    {

        public void Method()
        {
            Console.WriteLine("X");
        }
    }
}

In terms of the behavior you are seeing, I believe this is by design. If you specify a LifetimeManager or injectionMembers then Unity will register any types passed in with the supplied values. This makes sense in general because the user has specified the various configuration they desire for the types passed in.

For example you can also use registration by convention to just register types:
container.RegisterTypes(
    AllClasses.FromLoadedAssemblies(false, false, false, false),
    WithMappings.None,
    WithName.Default, 
    t => new ContainerControlledLifetimeManager(), 
    null, 
    true);
So in this case all classes (lets say Class1) will be registered as singletons (ContainerControlledLifetimeManager) with no interface mapping (WithMappings.None). If the lifetime manager was not specified then Class1 would not have been registered. But because the lifetime manager is specified the registration needs to be setup to use the correct user specified lifetime manager.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jul 11, 2013 at 1:19 PM
I understand your argument that this is by design, but I don't agree with it.

For two reasons:


1) If i'm using a registration convention and i'm not looking in the current registrations, I'll have a different result than the expected, I mean, I explicitly said that I want to register the class to the interfaces using the lifetime manager, so I expect that registration and this could result in some error.

2) I can get the actual result explicitly, but I can't get the result with only the interfaces in the actual behavior, so this way you have the two features.


I think that this is a great tool for developers, and it's better for a developer to have the actual result that the convention was supposed to give, in the end of the project, when all the deadlines are in the head, you simply can't remember why there are two registrations and you have some information duplicated.
Jul 12, 2013 at 6:50 AM
The worst case scenario is that you can modify the Unity source code to do whatever you think is right. Registration by convention is implemented using extension methods so I would think changes would not impact the core functionality.

Let me just say that there is a difference between a Registration and the internal build plan used by Unity. Consider:
container.RegisterType<IInterface, Class1>(new ContainerControlledLifetimeManager());

This will result in one registration. One mapping between IInterface to Class1 using a ContainerControlledLifetimeManager. However, internally Unity is creating and caching a build plan for Class1 (as well as the Class1 instance itself since it is a singleton). So, there is no visible registration but Class1 is still in some sense "registered".

The only real issue I see is if you are relying on the container registrations (IsRegistered) to perform some sort of application logic but in general that approach is not recommended.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to