PolicyInjection/Inheritance/InterceptingRealProxy Problem

Topics: Policy Injection Application Block
Dec 31, 2007 at 9:47 PM
Hey,


I'm having trouble wrapping methods in a class that inherits those methods from another class. I wrap my instance using injector.Wrap<IFoo>(fooInstance). If the base class and the sub class are in the same project then everything works as expected. If they are in separate projects then InterceptingRealProxy doesn't seem to link the right class method to the right interface method in its AddHandlersForInterface method. So, when Invoke is called, the interface method isn't linked to any handlers.


Something like this:

In my Base project I have:

public abstract class Base
{
public void Run()
{
throw new Exception("Test");
}
}

In my Contracts project I have:

public interface IFoo
{
void Run();
}

In my Services project I have:
public class Foo : Base, IFoo
{
}

Then my test looks something like:

class Program
{
static void Main(string[] args)
{
PolicyInjectorFactory policyInjectorFactory = new PolicyInjectorFactory();

PolicyInjector injector = policyInjectorFactory.Create();

object instance = new Foo();

IFoo wrappedInstance = injector.Wrap<IFoo>(instance);

wrappedInstance.Run();
}
}


I'm expecting Run to throw a wrapped exception, based on the way everything is configured, but it doesn't. If I copy the Base class into the Services project, then everything works.


I debugged through the wrap call and it seems to be an inconsistency in the member infos dealt with in the AddHandlersForType method and the AddHandlersForInterface method. When both Base and Foo are in different projects, AddHandlersForType works with Base's Run member info and AddHandlersForInterface has IFoo.Run mapped to a member info corresponding to Foo.Run. When both Base and Foo are in the same project, AddHandlersForType works with Foo's Run member info and AddHandlersForInterface again has IFoo.Run mapped to a member info corresponding to Foo, which causes handlers to be associated with IFoo.Run.


Am I doing something wrong or can methods inherited like this not be wrapped?


Thanks




Jan 1, 2008 at 11:44 PM
Hi,

This is an interesting issue, and it looks like the .NET reflection API is causing it. When the InterfaceMapping is retrieved in method AddHandlersForInterface for a class that inherits the implementation of an interface method from a type in a different assembly, the mapping looks like a a mapping for an explicitly implemented method thus invalidating the mapping logic.

It might be possible to work around this limitation, but I'm not sure how yet. I'm logging it as a work item.

Here's the output of a repro program I put together to learn about this limitation. It is self-contained, and is not related to the PIAB.

PiabRepro4.FooSameAssembly
Interface method: PiabRepro4.IFoo, PiabRepro4.IFoo, Run
Target method: PiabRepro4.Base, PiabRepro4.FooSameAssembly, Run

PiabRepro4.FooOtherAssembly
Interface method: PiabRepro4.IFoo, PiabRepro4.IFoo, Run
Target method: PiabRepro4.FooOtherAssembly, PiabRepro4.FooOtherAssembly, PiabRepro4.IFoo.Run

PiabRepro4.FooExplicit
Interface method: PiabRepro4.IFoo, PiabRepro4.IFoo, Run
Target method: PiabRepro4.FooExplicit, PiabRepro4.FooExplicit, PiabRepro4.IFoo.Run


Where the program looks like this (square brackets need fixing)

Program.cs on console app project
class Program
{
static void Main(string[] args)
{
DumpMapping(typeof(FooSameAssembly));
DumpMapping(typeof(FooOtherAssembly));
DumpMapping(typeof(FooExplicit));
}

private static void DumpMapping(Type type)
{
Console.WriteLine(type.FullName);
InterfaceMapping mapping = type.GetInterfaceMap(typeof(IFoo));
for (int i = 0; i < mapping.TargetMethods.Length; i++)
{
Console.WriteLine("Interface method: {0}, {1}, {2}", mapping.InterfaceMethodsi.DeclaringType.FullName,
mapping.InterfaceMethodsi.ReflectedType.FullName, mapping.InterfaceMethodsi.Name);
Console.WriteLine("Target method: {0}, {1}, {2}", mapping.TargetMethodsi.DeclaringType.FullName,
mapping.TargetMethodsi.ReflectedType.FullName, mapping.TargetMethodsi.Name);
}
Console.WriteLine();
}
}

public class FooSameAssembly : Base, IFoo { }

public class FooOtherAssembly : Services.Base, IFoo { }

public class FooExplicit : IFoo
{
void IFoo.Run() { }
}

public interface IFoo
{
void Run();
}

public class Base
{
public void Run() { }
}


CS file on class library project referenced by previous project

public class Base
{
public void Run()
{
}
}
Jan 1, 2008 at 11:46 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Jan 2, 2008 at 10:46 AM
Some additional information.

Reflector does show an explicit implementation of the IFoo.Run method on class FooOtherAssembly that is probably created by the compiler, so the reflection API is really returning the right information.

The compiler seems to be adding this explicit implementation behind the covers; I couldn't find a specification for this behavior yet.

Fernando
Jan 2, 2008 at 2:31 PM
Hey,

Thanks for the quick replies.

Looking at what you've said it seems that the problem is explicit interface implementations. In AddHandlersForType, classToProxy.GetMethods() won't return any explicit method implementations since they're not public.

I tested this with IFoo and Foo in the same assembly with IFoo implemented explicitly, and the handlers for IFoo.Run in Foo don't get stored in the AddHandlersForType method, so the policy injection doesn't work.

Just as a quick test I tried changing the call to GetMethods() to GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static) and everything works. Some sort of combination of BindingFlags that return public members + explicit interface implementation member would work nicely, if one exists.

Another source code change that makes things work is changing AddHandlersForInterface to set up the handlers for target methods that don't exist:

private void AddHandlersForInterface(Type targetType, Type itf, PolicySet policies)
{
InterfaceMapping itfMapping = targetType.GetInterfaceMap(itf);
int numMethods = itfMapping.InterfaceMethods.Length;
for( int i = 0; i < numMethods; ++i)
{
if (memberHandlers.ContainsKey(itfMapping.TargetMethodsi))
{
memberHandlersitfMapping.InterfaceMethods[i] =
memberHandlersitfMapping.TargetMethods[i];
}
else
{
//Explicit implementation.
IEnumerable<ICallHandler> handlers = policies.GetHandlersFor(itfMapping.TargetMethodsi);
HandlerPipeline pipeline = new HandlerPipeline(handlers);
memberHandlersitfMapping.TargetMethods[i] = pipeline;

memberHandlersitfMapping.InterfaceMethods[i] = pipeline;
}
}
}

Do either of these two changes seem reasonable to use?


Thanks

Jan 2, 2008 at 3:18 PM
Hi,

Indeed, explicitly implemented interfaces are not supported by the current implementation. This, combined with the code generated by the compiler where the explicit implementation is added causes the issue you posted in the first place.

Regarding your work arounds, I don't think including all methods in the reflection query is the way to go since they wouldn't be invoked through the proxy. Instead, adding support for explicitly implemented methods in AddHandlersForInterface looks like the right approach.

Fernando
Jan 2, 2008 at 3:35 PM
Hey,

Could this work around be included in the official code? If so, when are new releases available?

Thanks
Jan 2, 2008 at 4:00 PM
Hi,

I've posted a work item for this. Regarding the upcoming releases, you can get up to date information from http://msdn2.microsoft.com/en-us/practices/bb232643.aspx.

Regards,
Fernando
Jan 2, 2008 at 4:53 PM
Hey,

Sorry about this, but I have one more question: would the next release be the only way to get this fix or are intermediate builds available somewhere?

Thanks again for the help, it is greatly appreciated.
Jan 2, 2008 at 5:07 PM
Hi,

Interim CTP releases have been published in the past, but I can't confirm whether it will be the case for this release (and if it is, they may or may not include a fix for this issue).
Just for clarification: are you looking for a blessed fix that could be applied to the source code, or is it that only the binaries signed by MS will work in your scenario?

Regards,
Fernando
Jan 2, 2008 at 5:28 PM
Hey,

I'm currently using the signed dlls, but I could use the unsigned ones from a local build. I just wanted to make sure that it was short term necessity before having to modify the source code and shifting my projects/config files to use the unsigned dlls.


Thanks
Jan 2, 2008 at 5:44 PM
Hi,

That's fair, although you will still need to update the configuration files if there's a new drop.

Do you own all the classes involved? If you change the base class' Run() method to virtual, the interface mapping will correctly reference the inherited method and the current implementation will work. Of course this only deals with your specific situation, but it's certainly cheaper.

Fernando
Jan 2, 2008 at 6:09 PM
Hey,

Using virtual makes everything work perfectly and is much easier than the code change and dll switch.

Thanks again for all of your help.