Unity registration by convention with interface interceptor causes "[type] is not interceptable" exception

StackOverflow https://stackoverflow.com/questions/22869002

Question

I want to register all classes that implements a specific interface into Unity with the WithMappings.FromMatchingInterface convention. In addition, I want all of the registered objects to be intercepted using an interface interception behaviour. The problem is that Unity also registers mappings between the concrete classes, and when those classes are resolved, an exception is thrown with the message:

"[type] is not interceptable"

I realise that resolving an object using the concrete class type is not best practice but I wonder why Unity automatically adds mappings for both interface -> concrete class as well as concrete class -> concrete class when registering by convention? This means that it will never work if you add an interface interceptor and resolve using the concrete type.

My desired outcome for this would be that Unity did not add the concrete type -> concrete type mappings when registering by convention and giving it an interface interceptor, that way we could resolve the class using its concrete type if we wanted to, we would just not get interception.

I do not want to use the VirtualMethodInterceptor as I do not want to impose changes to the classes in order for interception to work, this includes inheriting from MarshalByRef. I also want to avoid individually registering all the objects.

My questions is thus, how do I register just the interface mappings when registering by convention?

Update : registering the classes individually gives the same problem, so the assumption is therefore that once an object is registered with an interfaceinterceptor then it cannot be resolved by using the concrete type.

The new registration code:

container.RegisterType<ISomeService, SomeService>(new InjectionMember[]
            {
                new Interceptor<InterfaceInterceptor>(), 
                new InterceptionBehavior<TraceInterceptor>()
            });
        container.RegisterType<ISomeRepository, SomeRepository>(new InjectionMember[]
            {
                new Interceptor<InterfaceInterceptor>(), 
                new InterceptionBehavior<TraceInterceptor>()
            });

Update 2 Adding a default interceptor for all the interfaces seems to work although this solution is rather hacky. This solution requires a little code just before the standard registration by convention, and removal of the InterfaceInterceptor in the conventions-based registration.

Pre-registration code

foreach (var type in types)
{
   container
       .Configure<Interception>()
       .SetDefaultInterceptorFor(type.GetInterface("I" + type.Name), new InterfaceInterceptor());
}

Some code that explains the dilemma :

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using System;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer();
            container.AddNewExtension<Interception>();

            var types = AllClasses.FromAssemblies(typeof(ISomeService).Assembly).Where(type => type == typeof(SomeService) || type == typeof(SomeRepository));

            container.RegisterTypes(
                types,
                WithMappings.FromMatchingInterface,
                getLifetimeManager: WithLifetime.ContainerControlled,
                getInjectionMembers: c => new InjectionMember[]
                {
                    new Interceptor<InterfaceInterceptor>(), 
                    new InterceptionBehavior<TraceInterceptor>()
                });

            // this works fine, the interceptor does what it is supposed to.
            var someService1 = container.Resolve<ISomeService>();
            someService1.SomeServiceMethod("Hello from main method");

            // should I by any chance resolve using the concrete service directly, it has a meltdown due to the interface interceptor.
            var someService2 = container.Resolve<SomeService>();
            someService2.SomeServiceMethod("This will never be shown due to a hissy fit thrown by the container about the concrete SomeService is not interceptable.");
        }
    }

    public class TraceInterceptor : IInterceptionBehavior
    {
        public System.Collections.Generic.IEnumerable<System.Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            Trace.WriteLine(string.Format("Hello from trace interception behavior for type [{0}]!", input.Target.GetType().FullName));

            return getNext().Invoke(input, getNext);
        }

        public bool WillExecute
        {
            get { return true; }
        }
    }

    public interface ISomeService
    {
        string SomeServiceMethod(string someParameter);
    }

    public class SomeService : ISomeService
    {
        private ISomeRepository _someRepository;

        public SomeService(ISomeRepository someRepository)
        {
            _someRepository = someRepository;
        }

        public string SomeServiceMethod(string someParameter)
        {
            return _someRepository.SomeRepositoryMethod(someParameter);
        }
    }

    public interface ISomeRepository
    {
        string SomeRepositoryMethod(string someParameter);
    }

    public class SomeRepository : ISomeRepository
    {
        public string SomeRepositoryMethod(string someParameter)
        {
            Trace.WriteLine(someParameter);

            return "Hello from repository!";
        }
    }
}
Was it helpful?

Solution

I believe I can shed some light on your questions. I, too, was wondering why the concrete type was registered along with a registration mapping the same concrete type to its interface.

I did a search to discover if anyone else encountered the same issue, thinking I may have done something wrong. But I ended up at this Codeplex discussion thread: Registration convention generates two registrations for each mapping. In this thread, randylevy (in the 3rd to last post) states that this is the default behavior when a LifetimeManager and/or injection members are specified as part of the convention:

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.

I believe that answers your question as to why there is a concrete type mapping in addition to the interface type mapping for a given type when using registration by convention. As the OP on that discussion thread stated, I too wish that there was some option you could set in the registration by convention class to disable the concrete type mapping.

In the end however, I'm not sure it makes a huge difference. If you're programming against contracts (e.g. using the interface type for the constructor/method/property arguments), then the container will always resolve using the interface mapping registration; and if any injection/interception is setup on that interface registration, then when resolved, the type should have the appropriate objects injected and the configured interception should occur.

At where I work, we have several different types of common registrations that need to occur, i.e. services, repositories, and other categories of classes/interfaces. For the sake of example, let's suppose I have a whole bunch of Service classes that need to be registered with their interfaces and which also have validators associated with them. The naming convention is MyService and a corresponding interface IMyService and a corresponding validator MyServiceValidator. I have created a ServiceRegistrationConvention class to accomplish this as follows:

public class ServiceRegistrationConvention : RegistrationConvention
{
    /// <summary>
    /// Gets a function to get the types that will be requested for 
    /// each type to configure.
    /// </summary>
    public override Func<Type, IEnumerable<Type>> GetFromTypes()
    {
        return WithMappings.FromMatchingInterface;
    }

    /// <summary>
    /// Gets a function to get the additional 
    /// <see cref="T:Microsoft.Practices.Unity.InjectionMember" /> 
    /// objects for the registration of each type. Defaults to no injection members.
    /// </summary>
    public override Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers()
    {
        return GetServiceValidator;
    }

    /// <summary>
    /// Gets a function to get the 
    /// <see cref="T:Microsoft.Practices.Unity.LifetimeManager" /> 
    /// for the registration of each type. Defaults to no 
    /// lifetime management (e.g. transient).
    /// </summary>
    public override Func<Type, LifetimeManager> GetLifetimeManager()
    {
        // Where I work, we use this lifetime manager for everyting.
        // I wouldn't recommend this right off the bat--I'm looking 
        // into changing this, personally, but right now, this is 
        // what we use and it works for our MVC application.
        return WithLifetime.Custom<PerRequestLifetimeManager>;
    }

    /// <summary>
    /// Gets a function to get the name to use for the registration of each type.
    /// </summary>
    public override Func<Type, string> GetName()
    {
        return WithName.Default;
    }

    /// <summary>
    /// Gets types to register.
    /// </summary>
    public override IEnumerable<Type> GetTypes()
    {
        // You may want to further restrict the search for classes to register
        // by doing some sort of namespace matching:
        //
        //     t.Namespace.StartsWith(
        //         "MyCompanyNamespacePrefix", StringComparison.Ordinal
        //     )
        //
        // for example.
        return AllClasses.FromLoadedAssemblies()
            .Where(t => t.Name.EndsWith("Service", StringComparison.Ordinal));
    }

    /// <summary>
    /// Given a type, get the type's corresponding validator, if any.
    /// </summary>
    private IEnumerable<InjectionMember> GetServiceValidator(Type type)
    {
        if (type == null)
            throw new ArgumentNullException("type");

        // In our case, the validators live in the same assembly 
        // as the class its validating...
        string matchingValidatorName = string.Concat(type.Name, "Validator");
        Type vType = AllClasses.FromAssemblies(new[] { type.Assembly })
            .FirstOrDefault(t => 
                string.Equals(t.Name, matchingValidatorName, StringComparison.Ordinal)
        );

        return (vType != null) ? 
            new List<InjectionMember> 
            {
                new Interceptor<InterfaceInterceptor>(),
                new InterceptionBehavior(vType)
            }
            :
            null;
    }
}

Again, so long as you're always resolving the type from its interface, everything should work just fine.

UPDATE: Well, unfortunately, interception is not working correctly. When I discover the issue, I'll be sure to update my answer.

UPDATE: This does work exactly as written here. I had an error with another configuration that caused the whole application to fail. Once I fixed that error, interception is occurring as expected.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top