Recently, I was working on a system that had to use several third-party data sources with different object models. Since changing their code was out of the question, the obvious workaround was to wrap the third-party objects into a common interface in my codebase. The resulting code was dead simple, but the monotonous repetition of creating property accessors for several dozen fields left me with a sinking feeling that there ought to be a better way.
public class PersonWrapper : IPerson // REPEAT for each 3rd party object... { public PersonWrapper(CompanyA.Person person) { _person = person; } public string EmailAddress { get { return _person.Email; } set { _person.Email = value; } } // REPEAT for each Property we want to expose... CompanyA.Person _person; }
The better way snuck into my head when a colleague found an interface in the third-party library that was similar to our needs. We only needed a small handful of properties of their interface, so he asked, "Can we inherit only half of an interface, somehow?" This odd question with a seemingly obvious answer (there isn't a CLR keyword to break interface definitions) pushed me to think about the problem differently. We can't choose what parts of an interface we want to inherit, but we can choose a different interface and inherit from it dynamically. Not a trivial task but certainly possible as many frameworks have been dynamically generating classes from interfaces for years. This technique only solved half of the problem, so I discarded the notion.
Later that night, the solution to the other half seemed obvious: decorate the interface with attributes that describe the relationship to the the third-party types, and use an aspect oriented concept known as call-interception to forward calls on the interface to the mapped fields of the third-party type. If it sounds complicated, it is -- but fortunately, the tools make it easy to do, and I'll try my best to walk you through it.
AOP 101
For starters, it helps to understand that typically AOP is used to augment the behavior of existing classes by creating a special proxy around objects at runtime. For the existing code that will use those augmented types, the proxy is identical to the original -- the key difference is that each call to your object passes through a special middleman (known as an interceptor) that can perform additional operations before and after each call. The most commonly used example is logging the parameters that were passed in or the time that the method took to execute. This diagram attempts to describe what's happening:
However, a dynamic proxy for an interface is different because there's no existing class, so our interceptor is responsible for doing all the work:
Mapping Interfaces to Third Party Types
So rather than creating a concrete wrapper for each vendor's type, we annotate our interface with the mapping information. The advantage is that we can manage our interface definition and mappings in a single place, making it easy to extend to new fields and other vendors without incurring class explosion.
public interface IPerson { [MappedField("First", typeof(CompanyA.Person))] [MappedField("FName", typeof(CompanyB.Contact))] string FirstName { get; set; } [MappedField("Last", typeof(CompanyA.Person))] [MappedField("LName", typeof(CompanyB.Contact))] string LastName { get; set; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple=true, Inherited=true)] public sealed class MappedFieldAttribute : Attribute { public MappedFieldAttribute(string fieldName, Type targetType) { FieldName = fieldName; TargetType = targetType; } public string FieldName { get; private set; } public Type TargetType { get; private set; } }
Intercepting Calls
Now that our interface contains the information to map to properties in our third-party class, we need to dynamically generate a derived class that implements this interface. I'm using Castle projects' DynamicProxy, though there are several other ways to do this. We'll configure the derived class (Proxy) with a custom interceptor that will read information about the method being called, and using some Reflection goodness, it will redirect the incoming call to the third-party object.
DynamicProxy's interceptor interface is simple. The IInvocation object contains all the information about the incoming call:
public interface IInterceptor { void Intercept(IInvocation invocation); }
If we were creating a proxy for a concrete Type, we'd likely want to pass the call from the proxy onto the target object using the invocation.Proceed() method, but because we're using an interface with no target implementation, we'll have to write the implementation within the Interceptor. We can implement anything we want though the only constraint is that we must set the invocation.ReturnValue if the method has a return value.
The details of the method being called are represented in the invocation.Method. When the call is for a property, the Method is the actual accessor method, ie get_<PropertyName> and set_<PropertyName>. This represents a bit of a gotcha because our attribute definition isn't on the accessor Method, it's on the PropertyInfo, so we have a bit of work to get our custom attributes.
The FieldMapperInterceptor implementation looks like this:
public class FieldMapperInterceptor : IInterceptor { public FieldMapperInterceptor(object target) { _targetObject = target; _targetType = target.GetType(); } public void Intercept(IInvocation invocation) { if (!InterceptProperty(invocation)) { throw new NotSupportedException("This method/property is not mapped."); } } protected bool InterceptProperty(IInvocation invocation) { MethodInfo method = invocation.Method; string methodName = method.Name; if (!IsPropertyAccessor(method)) return false; string propertyName = methodName.Substring(4); bool writeOperation = methodName.StartsWith("set_"); // get attribute from the Property (not the get_/set_ method) PropertyInfo property = method.DeclaringType.GetProperty(propertyName); MappedFieldAttribute attribute = property.GetCustomAttributes(typeof(MappedFieldAttribute), true) .OfType<MappedFieldAttribute> .SingleOrDefault(mf => mf.TargetType == _targetType); if (attribute == null) return false; // locate the property on the target object PropertyInfo targetProperty = _targetType.GetProperty(attribute.FieldName); if (targetProperty == null) { throw new NotSupportedException("Field not found on the target Type."); } if (writeOperation) { object propertyValue = invocation.Arguments.Last(); object[] index = invocation.Arguments.Take(invocation.Arguments.Length -1 ).ToArray(); targetProperty.SetValue(_targetObject, propertyValue, index); } else { invocation.ReturnValue = targetProperty.GetValue(_targetObject, invocation.Arguments); } return true; } public bool IsPropertyAccessor(MethodInfo method) { string methodName = method.Name; return (methodName.StartsWith("get_") | methodName.StartsWith("set_")); } private object _targetObject; private Type _targetType; }
Caveats
The FieldMapperInterceptor will work with any interface using the MappedFieldAttribute and any third-party object, but there are a few caveats:
- The implementation is only dealing with Properties, though the approach for methods would be similar (and probably easier). Maybe I'll post a follow up if there's interest.
- There's definitely room for performance and memory improvements via caching and RuntimeTypeHandles.
- Does not support generic parameters.
- While Castle's DynamicProxy2 is very light weight, there is a cost to instantiating objects. Albeit very minor.
Putting it All Together
With this in place, dynamically graphing our custom interface to our third-party types is a snap:
[Test] public void Demo() { ProxyGenerator generator = new ProxyGenerator(); CompanyA.Person actualObject = new CompanyA.Person(); FieldMapperInterceptor interceptor = new FieldMapperInterceptor(actualObject); IPerson person = proxyGenerator.CreateInterfaceWithoutTarget<IPerson>(interceptor); person.FirstName = "Bryan"; Assert.AreEqual(actualObject.FName,person.FirstName); }
Comments welcome.
My favorite part of your post is the Linq usage to look for custom attributes. Creative idea, excellent article.
ReplyDeleteJust a note, the line
ReplyDeleteif (attribute == null) return false;
is meaningless after using Singel(), since it will throw an exception if no matching attribute is found. To return null if nothing is found you have to use SingleOrDefault()
The Linq part can actually be shortened (and corrected) to .OfType<MappedFieldAttribute>().SingleOrDefault(mf => mf.TargetType == _targetType)
ReplyDeleteThe original code will fail if there are additional attributes and the null comparison will never evaluate to true.
Linq is definitely fun! I was going for readability using the Select/Where/Single syntax, but your comments are valid and optimization here is needed. I've updated the sample.
ReplyDeleteThis bug fix provides more meaningful exception information, as we'll be returning NotSupportedException instead of a NullReferenceException. I've updated the exception to provide more meaningful detail as well.
Check out also AutoMapper (http://www.codeplex.com/AutoMapper). Eventhough its purpose is not exactly the same as what you are tryng to solve but sometimes it is only about data objects and then it is perfectly acceptable to do it in "more disconnected" way. but anyway, honestly i dont like neither solution. it is true that mapping is almost always just a very stupid boring code but you have almost always exceptions. What if field in adapter interface must be translated into two fields in target object. And this is really very simple transformation. Moreover i like when compiler do as much checking as possible for me and you are giving up this feature.
ReplyDeleteThanks for the AutoMapper recommendation.
ReplyDeleteYou're point is valid, there's no compile time checking here. Either way, I wouldn't blindly expect this to work without a few unit tests in place.
To your point about very simple mapping, or complex boundary conditions, this sample does not address those concerns. Though with some creative thinking, it's not hard to imagine forwarding complex calls to a customized interceptor.
Nice article. You could use IInterceptorSelector and use interceptor per method strategy to improve performance (not pay the cost of reflection for each call) and make the code cleaner.
ReplyDeleteKrzysztof,
ReplyDeleteThanks for the feedback. The IInterceptorSelector looks interesting -- a custom selector is associated to the ProxyGenerator as part of the ProxyGeneratorOptions -- which in turn helps the ProxyGenerator determine which methods should be intercepted up front?
I can see how this would cut down on reflecting the incoming method to determine if it should be intercepted, but the remaining reflection would still be required to map the incoming method to the 3rd party class.
I keep meaning to come back to this post and optimize for performance/memory, and the IInterceptorSelector gives me some good ideas.
Cheers.
Bryan,
ReplyDeletewell - no. IProxyGenerationHook does that.
ProxyGenerationHook statically, during proxy type creation process decides which methods should be overriden hence, which you want to allow to be intercepted.
IInterceptorSelector operates on proxy _instance_. For each instance before first call to the method it gets called to decide which interceptors should be used for that particular method.
So you can use the Hook to filter out methods you dont want to intercept, and then with the Selector put appropriate interceptors for each method you want to intercept.
I prefer interceptor-per-method approach, that is each interceptor works with only one method (or all methods in case of general use interceptors), so that interceptor can get straight to the point, without wasting time verifying it indeed is intercepting the call it is interested in.
Right on, Krzysztof. I'm working on an update for this post and realized my mistake after visiting those links the second time through.
ReplyDeleteDisappointing though -- I didn't see a GenerateProxyWithoutTarget overload that accepts a ProxyGeneratorOptions with Generics support.