Using RealProxy to extend the WCF proxy

Posted on January 31, 2011 by

1


In this post, I’m going to demonstrate how to use RealProxy in order to extend the generated client side WCF proxy.

RealProxy is defined in the System.Runtime.Remoting.Proxies namespace and is not directly related to WCF, though it is used by WCF internally when generating a client proxy.

The WCF client proxy is very extendable via behaviors and extension points (http://wcfpro.wordpress.com/wcf-extensions/), but not all logic can be applied by using these extensions. In this situations the RealProxy can be your friend.

Even in the occasions in which you can implement your logic by using extensions, sometimes, you might prefer that all your logic will be located in one place (and not spread all over with different extensions). RealProxy can provide you a single location to implement all your logic which will affect all invoked operations on the client side. This is very much like the IOperationInvoker (http://wcfpro.wordpress.com/2010/12/15/ioperationinvoker/) on the server side where you can interfere before and after the operation was invoked.

You can implement all kinds of complex logic by using RealProxy (see http://wcfpro.wordpress.com/2010/12/19/extended-wcf-preview/) . For example:

  1. Writing logs before and after the operation is invoked
  2. Adding routing/load-balancing capabilities
  3. Adding channel life time management mechanisms, such as disposing faulted channels  and creating new ones instead
  4. Adding retries mechanisms on failed operations

When using RealProxy, you implement all your logic in a class deriving from it, and during runtime, you request from the RealProxy to provide a TransparentProxy. The TransparentProxy is a generated wrapper for the interface you are trying to extend. Each call on this generated TransparentProxy will be redirected to a single Invoke() method of the RealProxy derived class. It is important to mention, that you extend only the RealProxy class and you can not extend the TransparentProxy, as it is a sealed class (generated during runtime).

WCF uses internally the same mechanism to generate during runtime your client proxy. When your client performs a WCF call, it arrives to the TransparentProxy , which calls the RealProxy Invoke() and forwards the calls made on it to the real object using the remoting infrastructure.

Your WCF client –> TransparentProxy –>  RealProxy – - – - – - – - – > Remote server

Creating a Custom WCF RealProxy

Creating a custom RealProxy involves the following steps:

1. Inherit from the RealProxy class, and override RealProxy.Invoke(IMessage msg) method.

The IMessage object, provided to this method, contains a metadata on the real source method. You can access this information by using the provided Properties dictionary, but the preferred way is to cast the message into IMethodMessage or to the more explicit types IMethodCallMessage or IMethodReturnMethod (the message can be either IMethodCallMessage or IMethodReturnMethod  but not both). The IMethodMessage interfaces provide a strongly typed way to access the required information.

for more details on IMessage, IMethodMessage, IMethodCallMessage and IMethodReturnMethod  see:

2. In the constructor of your custom RealProxy, first call the base RealProxy with the actual type of the proxy (in our example its the WCF interface). This type is used when generating a TransparentProxy object.

3. Create a custom ChannelFactory, and override CreateChannel(EndpointAddress address, Uri via) method. In this method, instead of creating a WCF channel from the ChannelFactory class create your custom RealProxy object (the one which inherits from RealProxy), and return (T)customRealProxy.GetTransparentProxy().

 

Custom RealProxy Sample

The following sample is very simplified, and its purpose is just to give a general idea of how you can extend WCF using a custom RealProxy.

In this sample, when the client creates the custom ExtendedChannelFactory<T> it passes  an integer to the constructor. The integer indicates how many WCF channels should be created internally. The ExtendedChannelFactory<T> then creates an ExtendedProxy<T> and provides it a list of channels to work with. The ExtendedProxy<T> performs round robin between these channels on each request. In addition, each request is being logged to our log system before and after each operation, while indicating success or failure.

    /// <summary>
    /// Creates an extended channel
    /// </summary>
    /// <typeparam name="T">The channel's type</typeparam>
    class ExtendedChannelFactory<T> : ChannelFactory<T>
    {
        #region Fields

        private ILogger m_logger;

        /// <summary>
        /// Saves the number of channels which should be
        /// created per each endpoint
        /// </summary>
        private int m_numOfChannels;

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public ExtendedChannelFactory(ServiceEndpoint ep, int numOfChannels)
            : base(ep)
        {
            m_logger = new ConsoleLogger();
            m_numOfChannels = numOfChannels;
        }

        #endregion

        #region ChannelFactory<T>

        public override T CreateChannel(EndpointAddress address, Uri via)
        {
            IList<T> channels = new List<T>();
            for (int i = 0; i < m_numOfChannels; i++)
            {
                channels.Add(base.CreateChannel(address, via));
            }
            var extendedProxy =
                new ExtendeProxy<T>(typeof(T), m_logger, channels.ToArray());
            T proxy = (T)extendedProxy.GetTransparentProxy();
            return proxy;
        }

        #endregion
    }

    /// <summary>
    /// Custom proxy
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [SecurityCritical(SecurityCriticalScope.Everything)]
    class ExtendeProxy<T> : RealProxy
    {
        #region fields

        private Type m_proxyType;
        private ILogger m_logger;
        private T[] m_channels;
        private int m_index;

        #endregion

        #region constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public ExtendeProxy(
            Type proxyType,
            ILogger logger,
            T[] channels)
            : base(proxyType)
        {
            m_logger = logger;
            m_proxyType = proxyType;
            m_channels = channels;
            m_index = 0;
        }

        #endregion

        /// <summary>
        /// See <see cref="RealProxy.Invoke"/>
        /// </summary>
        public override IMessage Invoke(IMessage message)
        {
            var methodCall = message as IMethodCallMessage;
            var methodInfo = methodCall.MethodBase as MethodInfo;

            var innerProxy = m_channels[m_index++ % m_channels.Count()];
            Debug.Assert(innerProxy != null);

            m_logger.WriteLine(severity.Info,
                "Operation '{0}' is about to be invoked", methodCall.MethodName);

            try
            {
                var result = methodInfo.Invoke(innerProxy, methodCall.InArgs);
                m_logger.WriteLine(severity.Info,
                   "Operation '{0}' was ended", methodCall.MethodName);

                return new ReturnMessage(
                      result, // Operation result
                      null, // Out arguments
                      0, // Out arguments count
                      methodCall.LogicalCallContext, // Call context
                      methodCall); // Original message
            }
            catch (Exception e)
            {
                m_logger.WriteLine(severity.Error,
                    "Operation '{0}' ended with exception '{1}'",
                    methodCall.MethodName, e);
                return new ReturnMessage(e, methodCall);
            }
        }
    }

    /// <summary>
    /// The severity of the log
    /// </summary>
    public enum severity
    {
        Info,
        Warning,
        Error
    }

    /// <summary>
    /// Logger
    /// </summary>
    public interface ILogger
    {
        /// <summary>
        /// Write the message to the log system
        /// </summary>
        void WriteLine(severity severity,
            string message,
            params object[] args);
    }

    /// <summary>
    /// Logger which output the messages to console
    /// </summary>
    public class ConsoleLogger : ILogger
    {
        #region ILogger Members

        /// <summary>
        /// See <see cref="ILogger.WriteLine"/>
        /// </summary>
        public void WriteLine(severity severity,
            string message,
            params object[] args)
        {
            Console.WriteLine("{0}: {1}", severity.ToString(), string.Format(message, args));
        }
        #endregion
    }

Useful links

http://msdn.microsoft.com/en-us/library/scx1w94y(VS.71).aspx

About these ads
Posted in: Extended WCF