2
votes

I'm using MS Unity and 'Registration by convention' (auto-registration) to register all the classes in a namespace. The code (seen below) works at it should and returns an expected result

var container = new UnityContainer();
        container.RegisterTypes( AllClasses.FromLoadedAssemblies().Where(
            t => t.Namespace == "DependencyInjectionExample.Test"), 
            WithMappings.FromMatchingInterface, 
            WithName.Default, 
            WithLifetime.ContainerControlled);

Result

Container has 4 registrations

  • IUnityContainer '[default]' Container
  • TestClass1 '[default]' ContainerControlled
  • TestClass2 '[default]' ContainerControlled
  • TestClass3 '[default]' ContainerControlled

My problem is that I can't figure out how to resolve them. I've tried with

var allRegistered = container.ResolveAll<ITestable>();

But it doesn't return anything (all the testclasses implement ITestable). When I try

var singleRegistered = container.Resolve<ITestable>();

I get a 'ResolutionFailedException' - "The type ITestable does not have an accessible constructor". I've read that it is because that the registered types are not named, but that is not possible when using auto-registration.

What should I do to resolve the registered types?

EDIT

namespace DependencyInjectionExample.Test
{
 public interface ITestable
 {
    string SaySomething();
 }
}

One of the three test classes. They all do the same thing.

namespace DependencyInjectionExample.Test
{
 public class TestClass1 : ITestable
 {
    public TestClass1() { }

    public string SaySomething()
    {
        return "TestClass1 hello";
    }
 }
}
2
If you take a closer look, you will see that only concrete classes are registered within the container. The only interface registered in the container is the default IUnityContainer reference. Are you implementing interfaces in those classes (TestClass1, etc)? Also, you can't have more than one class registered for the same interface, so if you're implementing ITestable in those three TestClasses it won't work. Post the code for the classes registered in the container so we can understand better what's going on. - Denis Brat
If I only can have one class registered for an Interface, then what is the point? I want to be able to add classes which implement an interface to the namespace and then automatically resolve them. For example by getting a list of the concrete classes. - barto90
That is why you have a method for registering each interface with a name. If you want to have 3 different instances for a single interface, then you have to register each one manually using that method. Sorry, but this is the only way you will achieve what you want. You may want to take a look on MEF. It comes with the framework and I think it supports what you need out of the box. - Denis Brat
Another way is to create an attribute that enables you to specify the name of the instance, then you just have to hook in the Unity build policies and register that specific instance with the name given in the attribute. Sounds amazingly worth to try. I may take a look at it once I get to home (in office right now). - Denis Brat
I'm a bit confused now. What is the Registration by convention used for if it can't register types implementing an interface? Thank you, I'm really looking forward to your solution. - barto90

2 Answers

9
votes

I took a closer look at RegisterTypes and it accepts a parameter of the type Func which you can use to provide the name for each instance. There was no need to create an extension to make this happen. After creating the attribute, it was quite easy to provide a specific name to each instance to be registered in the container.

First you create the attribute class:

[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class UnityNamedInstanceAttribute : Attribute
{
    private readonly string instanceName;

    public UnityNamedInstanceAttribute(string instanceName)
    {
        this.instanceName = instanceName;
    }

    public string InstanceName
    {
        get { return this.instanceName; }
    }
}

Then set up a class to help with resolving the name of the instance:

public static class UnityNamedInstance
{
    public static string AttributeName(Type type)
    {
        var namedAttribute = type
            .GetCustomAttributes(typeof(UnityNamedInstanceAttribute), true)
            .FirstOrDefault()
            as UnityNamedInstanceAttribute;
        return namedAttribute != null ? namedAttribute.InstanceName : null;
    }

    public static string AttributeNameOrDefault(Type type)
    {
        return UnityNamedInstance.AttributeName(type) ?? WithName.Default(type);
    }
}

Declare your Test Classes using the attribute we created ealier:

public interface ITest
{
    void DebugName();
}

[UnityNamedInstance("Test A")]
public class TestA : ITest
{
    #region ITest Members

    public void DebugName()
    {
        Debug.WriteLine("This is TestA");
    }

    #endregion
}
[UnityNamedInstance("Test B")]
public class TestB : ITest
{
    #region ITest Members

    public void DebugName()
    {
        Debug.WriteLine("This is TestB");
    }

    #endregion
}

[UnityNamedInstance("Test C")]
public class TestC : ITest
{
    #region ITest Members

    public void DebugName()
    {
        Debug.WriteLine("This is TestC");
    }

    #endregion
}

And change your registration by convention method to:

        container.RegisterTypes(
            AllClasses.FromLoadedAssemblies().Where(t => t.Namespace == "DependencyInjectionExample.Test"),
            WithMappings.FromAllInterfaces, // This way you have the same instance for each interface the class implements
            UnityNamedInstance.AttributeNameOrDefault, // Use our helper to solve the name of the instance
            WithLifetime.ContainerControlled);

Or you could just avoid creating the attribute and the helper and name the instance after the class name, as follows:

        container.RegisterTypes(
            AllClasses.FromLoadedAssemblies().Where(t => t.Namespace == "DependencyInjectionExample.Test"),
            WithMappings.FromAllInterfaces,
            WithName.TypeName, // Use the type name for the instances
            WithLifetime.ContainerControlled);

Now you can access each class instance using the name passed to the attribute constructor in the class declaration or the class name:

        // Access each instance using the name you gave in the attribute
        container.Resolve<ITest>("Test A").DebugName();
        container.Resolve<ITest>("Test B").DebugName();
        container.Resolve<ITest>("Test C").DebugName();

And if you want to get all the registered instances that implements a specific interface:

        foreach (var test in container.Resolve<ITest[]>()) {
            test.DebugName();
        }

OR

        foreach (var test in container.ResolveAll<ITest>()) {
            test.DebugName();
        }

I'm very interested to know if this fits your needs. I appreciate your feedback!

0
votes

Denis Brat's comment had the critical item in it that helped me solve a problem i had with this.

If you are using a Generic Repository with only constructors that take interfaces the GetInstance methods for the registered types fail with dependancy exceptions.

If you register the interfaces manually BEFORE you auto-register the concrete classes it works, i.e.

ViewModelLocator:

unityContainer.RegisterType(typeof(IGenericRepository<>), typeof(GenericRepository<>));

unityContainer.RegisterTypes(AllClasses.FromAssembliesInBasePath().Where(t => t.Namespace.StartsWith("Mynamespace.")),
            WithMappings.FromAllInterfaces,
            WithName.TypeName,
            WithLifetime.ContainerControlled,null,true);

Model:

public MainViewModel(IGenericRepository<Client> clientRepository, IExceptionManager exceptionManager )
    { whatever }

Views:

return ServiceLocator.Current.GetInstance<MainViewModel>("MainViewModel");

it will find them and return the correct instance.