Using a Custom Proxy For Interception

I've been working on a system that required intercepting the method calls in an object model; basically I was trying to use Aspect Oriented Programming to weave in some logging and verification code.  My first attempt was to simply add an object sink to a ContextBoundObject as discussed in the following msdn article: https://msdn.microsoft.com/msdnmag/issues/03/03/ContextsinNET/default.aspx.

While this was relatively simple to implement, I quickly ran into a problem: I needed to reference the context bound objects from a client in a different context.  Unfortunately, the context bound objects contained private fields (which cannot be marshalled across context boundaries).  As a result, I would get exceptions in the transparent proxy whenever I tried to reference these objects from a client in a different context.

The solution to this seemed to be clear.  The only reason for proxying objects at all was for interception, so I could solve my problem by handing out the real objects (instead of a proxy) from the interceptor.  However, as I started digging into this, it appeared that obtaining the real object might be a problem.  While I could get the RealProxy, I couldn't figure out how to get at the underlying object.  RealProxy.GetUnwrappedServer was clearly the method I needed to call, but it was protected.  So it became clear that the solution would be to implement a custom proxy. 

The problem with that is that there is a dearth of documentation on the subject.  Aside from the following short article (https://msdn2.microsoft.com/en-us/library/scx1w94y(VS.71).aspx) and a host of samples that don't demonstrate how to actually proxy a call to the real object, MSDN had very little to say on the subject.  A few folks have blogged about this but the few examples out there were all explicitly creating the proxy.  I wanted my interception to be completely seamless such that object activation would return a proxy.  While it was clear that ProxyAttribute would allow this, there were no examples of how use the ProxyAttribute to do anything more than intercept activation.  So, in this blog post, I'm going to put the pieces together and demonstrate how to implement a completely transparent (no pun intended) interception system using a custom proxy.

The first step is to create the custom proxy itself.  The custom proxy is simply a class that derives from RealProxy.  The class should provide an implementation of the parameterized constructor which does nothing more than delegate to the base.  The interesting code is in the Invoke method (which is the only required override).  Here is an implementation:

    public class InterceptorProxy : RealProxy

    {

        private MarshalByRefObject m_realObject;

        public InterceptorProxy()

        {

        }

        public InterceptorProxy(Type classToProxy) : base(classToProxy)

        {

        }

        public override IMessage Invoke(IMessage msg)

        {

            IMessage returnMessage = null;

            if (msg is IConstructionCallMessage)

            {

                IConstructionCallMessage ctorCallMessage = msg as IConstructionCallMessage;

                returnMessage = InitializeServerObject(ctorCallMessage);

                m_realObject = GetUnwrappedServer();

                SetStubData(this, m_realObject);

            }

            else if (msg is IMethodCallMessage)

            {

                IMethodCallMessage methodCallMessage = msg as IMethodCallMessage;

                Preprocess(methodCallMessage);

                IMethodReturnMessage rawReturnMessage = RemotingServices.ExecuteMessage(m_realObject, methodCallMessage);

                returnMessage = PostProcess(methodCallMessage, rawReturnMessage);

            }

            else

            {

                throw new NotSupportedException();

            }

            return returnMessage;

        }

private void Preprocess(IMessage msg)

{

// Do interception work here.

}

private IMessage PostProcess(IMessage msg, IMessage msgReturn)

{

// Do interception work here.

}

}

There are two pieces to the Invoke implementation. The first is how the IConstructionCall message is handled--we'll go through each call as it happens. The first call is to RealProxy.InitializeServerObject. This call simply instantiates the real object that we are proxying. The next call is to RealProxy.GetUnwrappedServer. This call actually returns a reference to the object that we just created. We need this so we can marshal calls (among other things). Finally, we call RealProxy.SetStubData. This last call performs the magic that ensures that calls to the Transparent Proxy are routed to the RealProxy. If SetStubData is not called, no further messages will be routed to the RealProxy.

The second piece of the Invoke implementation deals with handling IMethodCallMessages. Assuming you hooked up your proxy correctly, you will get IMethodCallMessages whenever a method is invoked on your proxied object. This implementation provides hooks before and after the method invocation to allow whatever pre and postprocessing you might require. In my case I do some logging and verification actions (not shown). The interesting call is RemotingServices.ExecuteMessage. This call performs the execution of a message on the real object. 

In this case, we simply delegate what we were passed in since we aren't interested in modifying the message. However, if we needed to, we could provide our own message in lieu of the one we received to do anything we liked. For example, we could create a custom proxy implementation that would provide a thunking layer to allow existing code to call what would otherwise be an incompatible API. Another thing to point out is that Invoke's return IMessage can also be modified. For example, the interceptor can eat exceptions by simply replacing the IMethodReturnMessage it receives from the method call with one that does not contain any exceptions.

 Now that we've got our custom proxy created, we need to hook into activation. What we want to accomplish is to ensure that when we "new" up our object we always get a transparent proxy instead. In other words, we want to make sure that our interceptor is installed automatically and without any special code required.

To do this, we need to create a ProxyAttribute.  It looks like this:

    [AttributeUsage(AttributeTargets.Class)]

    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]

    public class InterceptorProxyAttribute : ProxyAttribute

    {

        public InterceptorProxyAttribute()

        {

        }

        public override MarshalByRefObject CreateInstance(Type serverType)

        {

            RealProxy proxy = new InterceptorProxy(serverType);

            MarshalByRefObject transparentProxy = (MarshalByRefObject)proxy.GetTransparentProxy();

            return transparentProxy;

        }

    }

The interesting method is the only override we need: ProxyAttribute.CreateInstance. CreateInstance gets called during non-remote activation. Normally, in that case you would get the real object back. However, we want to return a TransparentProxy that delegates to our custom interceptor proxy. So instead of activating our real object, we activate our InterceptorProxy. We then call RealProxy.GetTransparentProxy to provide a TransparentProxy that gets returned in lieu of the real object. 

Since CreateInstance doesn't actually create the real object, you might wonder where that happens. If you recall, the actual object gets created when RealProxy.Invoke handles the IConstructionCallMessage. RealProxy.InitializeServerObject instantiates the real object that is being backed by the proxy.

That's it. The only remaining step is to apply the InterceptorProxyAttribute to any class which we want to set up interception on. Once this is done, activation of that class will result in our interceptor being automatically installed. Note that in order for this technique to work, the class must derive from ContextBoundObject--otherwise it cannot be proxied.

As a final note, the following blog entry from Chris Brumme provides some good background on proxies and contexts: https://blogs.msdn.com/cbrumme/archive/2003/07/14/51495.aspx