Intercept interception - Testing bug from 1.2 # 3685

Apr 20, 2010 at 9:26 PM
Edited Apr 20, 2010 at 11:59 PM

Our service interfaces are derived so i've been unable to use interface interception when using Unity 1.2.

I'm now testing this behaviour against the 2.0 RTW(?) source code but i'm getting a strange behaviour.

In the follow code sample, my test is failing as only 2 entries are logged.  The one that is missing is  container.Resolve<IDerived>().Method1("Value1", "Value2");  This is the method on the IBase interface.  It appears that the methods on the base interface are not being intercepted, although the issue with 1.2 is now fixed as the virtual interceptor at least now compiles.  I've tried using explicit interface method signatures but the result is the same.

[EDIT : No change with Changeset 50871]

    [TestFixture]
    public class UnityInterception
    {
        /// <summary>
        /// Initialises all the unity containers used in the tests
        /// </summary>
        [Test]
        public void Setup()
        {
            IUnityContainer container = new UnityContainer()
                .AddNewExtension< Interception >();
            container
                .RegisterType< IBase, BaseImpl >()
                .Configure< Interception >()
                .SetInterceptorFor< IBase >( new InterfaceInterceptor() );
            container
                .RegisterType< IDerived, DerivedImpl >()
                .Configure< Interception >()
                .SetInterceptorFor< IDerived >( new InterfaceInterceptor() );

            container.Resolve<IBase>().Method1("Value1", "Value2");
            container.Resolve<IDerived>().Method1("Value1", "Value2");
            container.Resolve<IDerived>().Method2("Value3", "Value4");

            Assert.AreEqual( 3, InstrumentedAttribute.callLog.Count(), "Call Log did not expected number of entries" );
        }

        [Instrumented]
public interface IBase { void Method1(string param1, string param2); }         [Instrumented]
public interface IDerived : IBase { void Method2(string param1, string param2); } public class BaseImpl : IBase { #region IBase Members public void Method1(string param1, string param2) { } #endregion } public class DerivedImpl : IDerived { #region IDerived Members public void Method2(string param1, string param2) { } #endregion #region IBase Members public void Method1(string param1, string param2) { } #endregion } public class InstrumentedAttribute : HandlerAttribute { public static List<KeyValuePair<string, List<KeyValuePair<string, string>>>> callLog = new List<KeyValuePair<string, List<KeyValuePair<string, string>>>>(); public override ICallHandler CreateHandler(IUnityContainer container) { return new InstrumentedCallHandler(); } public class InstrumentedCallHandler : ICallHandler { #region ICallHandler Members public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { string methodName = input.MethodBase.Name; List< KeyValuePair< string, string > > paramsAndValues = input.MethodBase.GetParameters().Select( ( parameter, index ) => new KeyValuePair< string, string >( parameter.Name, input.Inputs[ index ].ToString() ) ).ToList(); callLog.Add( new KeyValuePair< string, List< KeyValuePair< string, string > > >( input.MethodBase.Name, paramsAndValues ) ); return getNext()( input, getNext ); } public int Order { get { return 1; } set { } } #endregion } } }
Apr 21, 2010 at 12:09 AM

If you're going to look at the unit tests for InterfaceInterceptor, you'll see a test method named "CanInterceptDerivedInterface".  This will initially tell you that your method from the base interface should have been intercepted.  But if you look at the code inside the test method, you'll see that it only tests that it doesn't throw an exception.

I think the fix was only meant to make sure that no exception will be encountered in such scenario.  The beta 2 doc still says that the InterfaceInterceptor can only intercept methods on a single interface. 

If you're interested in creating your own interceptor that meets your requirement, you can check this out - http://unity.codeplex.com/Thread/View.aspx?ThreadId=208820

 

Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@avanade.com

Apr 21, 2010 at 12:29 AM

I would still class this as a bug.  Interface inheritence is a completely different design intent than object inheritence - Contract vs Implementation. 

Is there a feature or a bug workitem created to correct this behaviour.

The solution in the link you provided corrects my situtation but I need the strongly typed Unity dlls from the installers so I don't want to alter the unity source code myself.

 

Apr 21, 2010 at 12:33 AM

Found this - http://unity.codeplex.com/WorkItem/View.aspx?WorkItemId=6911 but was already closed by Chris Tavares explicitly saying that it is out of scope for unity 2.0.

 

Sarah Urmeneta
Global Technology & Solutions
Avanade, Inc.
entlib.support@avanade.com

 

 

 

Apr 21, 2010 at 2:32 AM

I'm sorry this issue is causing you difficulty. The interception pieces are one of the biggest parts of the project, bigger than the container itself, and there are so many permutations that it's difficult to cover every scenario.

The interceptors are a plug point, so an option, if you don't want to compile the entire code yourself, you can just write an interceptor that fixes your issue for the moment and use the rest of the DLLs unchanged.

Apr 21, 2010 at 3:35 AM

Any scenarios where this would cause problems ?

        public class FullInterfaceInterceptor : IInstanceInterceptor
        {
            private InterfaceInterceptor realInterceptor = new InterfaceInterceptor();

            #region IInstanceInterceptor Members

            public IInterceptingProxy CreateProxy(Type t, object target, params Type[] additionalInterfaces)
            {
                return realInterceptor.CreateProxy( t, target, additionalInterfaces );
            }

            #endregion

            #region IInterceptor Members

            public bool CanIntercept(Type t)
            {
                return realInterceptor.CanIntercept( t );
            }

            public IEnumerable<MethodImplementationInfo> GetInterceptableMethods(Type interceptedType, Type implementationType)
            {
                IEnumerable< MethodImplementationInfo > methods = realInterceptor.GetInterceptableMethods( interceptedType, implementationType );

                foreach ( MethodImplementationInfo methodImplementationInfo in methods )
                {
                    yield return methodImplementationInfo;
                }

                foreach (var baseInterface in interceptedType.GetInterfaces())
                {
                    InterfaceMapping mapping = implementationType.GetInterfaceMap(baseInterface);

                    for (int i = 0; i < mapping.InterfaceMethods.Length; ++i)
                    {
                        yield return new MethodImplementationInfo( mapping.InterfaceMethods[ i ], mapping.TargetMethods[ i ] );
                    }
                }

            }

            #endregion
        }
Apr 21, 2010 at 8:40 PM

Have you tried that? I'm not convinced it'd actually work, since the GetInterceptableMethods stuff is only used to wire up the PIAB call handlers, they don't actually go into the proxy object.

I could very well be wrong; as I said, the interception stuff is complex and I don't have it all in short term memory. :-)

 

Apr 21, 2010 at 11:27 PM

Works on my machine :)

... well at least my unit test above now works.   I'll put the system through it's full paces after I convert the unity source test projects to nUnit. 

VSTests isn't installed with my VS2008, and VS2010 Ultimate cannot run the tests as it upgrades the test projects to .NET 4, whch causes Type load exceptions due to the changes in security.

Apr 22, 2010 at 4:05 AM

Sorry about that, the unit test framework has been a pain point for a while, but being part of DevDiv, we're kind of obligated to use our own products.

Glad to know it (seems to) work, though!