Changing Number of Tiers for Service Layer with Castle

Recently I had some experience with Castle open source project.  I worked on a project where we wanted to switch between two and three tiers in our web application without changing any code.  The idea is that from MVC controller I would like to either call a remove service or just execute the same code by creating an instance of the service class directly and calling the same method that I would through proxy.  Also, I wanted to do all these without code change, just via configuration.  What I would like to explore in this post is how we create an abstraction to support both scenarios.

From the client perspective, I would like to simply write something like the following:

var proxy = ProxyFactory.CreateProxy<ITestService>();
var returnValue = proxy.DoWork("testing");

Code is pretty mush self explanatory.  However, CreateProxy call should return either actual WCF proxy class or an instance of TestService.  So, now that the concept is clear, let’s talk about what Castle can do for us.  One of the pieces of functionality that Castle project has is a concept of DynamicProxy.  In my case I want to return an instance of “something” that implements my interface.  Then, I want to intercept method calls inside this DynamicProxy, make a decision of I want to call a service remotely or locally, then make this call.

At a high level, I want to have two classes – factory class to return Dynamic Proxy and interceptor class to do the work.  Here is my factory.

using System;
using System.Threading;
using Castle.DynamicProxy;
using Castle.MicroKernel.Registration;
using Castle.Windsor;

namespace CastleAbstraction.Proxies
{
    /// <summary>
    /// Proxy factory class
    /// </summary>
    public static class ProxyFactory
    {
        /// <summary>
        /// Initialize static members
        /// </summary>
        static ProxyFactory()
        {
            ProxyGenerator = new Lazy<ProxyGenerator>(
                () => new ProxyGenerator(), LazyThreadSafetyMode.PublicationOnly);
            WindsorContainer = new Lazy<WindsorContainer>(
                () => new WindsorContainer(), LazyThreadSafetyMode.PublicationOnly);
        }

        /// <summary>
        /// Proxy generator
        /// </summary>
        private static Lazy<ProxyGenerator> ProxyGenerator { get; set; }

        /// <summary>
        /// IoC container
        /// </summary>
        private static Lazy<WindsorContainer> WindsorContainer { get; set; }

        /// <summary>
        /// Use local mode is set to true, otherwise call remote service
        /// </summary>
        public static bool UseLocalExecutionMode { get; set; }

        /// <summary>
        /// Url that a service listens on
        /// </summary>
        public static string Url { get; set; }

        /// <summary>
        /// Create proxy of type T
        /// </summary>
        /// <typeparam name="T">Type of interface that a service implements</typeparam>
        /// <returns>Dynamic proxy</returns>
        public static T CreateProxy<T>()
            where T : class
        {
            if (UseLocalExecutionMode)
            {
                return WindsorContainer.Value.Resolve<T>();
            }
            return ProxyGenerator.Value.CreateInterfaceProxyWithoutTarget<T>
                (new ProxyInterceptor());
        }

 
        /// <summary>
        /// Register local services
        /// </summary>
        public static void RegisterTypes()
        {
            if (UseLocalExecutionMode)
            {
                WindsorContainer.Value.Register(Component.For<ITestService>().ImplementedBy<TestService>());
            }

        }
    }
}

You can see from my comments what I am doing.  Locally, I will use Castle Windsor IoC to resolve local services.  I also have a couple of properties – local mode flag and remote Url.  In local mode I just resolve an instance of the interface based on registration.  In remote mode I use Dynamic Proxy with the interceptor to actually call the service.  I could take it a step further and have values pulled from configuration file, and this is very easy.  This class is pretty simple, as you can see.  More interesting is the interceptor class.  They way Dynamic Proxy works, is that it will call my interceptor any time a method on Dynamic Proxy is invoked.  It will pass method info and parameters into my interceptor.

using System;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using Castle.DynamicProxy;
using System.ServiceModel.Channels;

namespace CastleAbstraction.Proxies
{

    /// <summary>
    /// Interceptor class
    /// </summary>
    [Serializable]
    public class ProxyInterceptor : IInterceptor
    {

        /// <summary>
        /// Intercepts the specified invocation.
        /// </summary>
        /// <param name="invocation">The invocation.</param>
        public void Intercept(IInvocation invocation)
        {

            // get constructor for ChannelFactory<T>
            var createFactoryMethod = typeof(ChannelFactory<>)
                .MakeGenericType(new[] { invocation.Method.ReflectedType })
                .GetConstructor(new[] { typeof(Binding), typeof(string) });
            Debug.Assert(createFactoryMethod != null, "createFactoryMethod != null");
            // Create instance of ChannelFactory
            using (var factory = (ChannelFactory)createFactoryMethod.Invoke(
                new object[]
                        {
                            new WebHttpBinding(), 
                            ProxyFactory.Url
                        }))
            {
                factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
                // Call remote service
                invocation.ReturnValue = Execute(invocation, factory);
            }

        }

        /// <summary>
        /// Executes the specified method on a service.
        /// </summary>
        /// <param name="invocation">The invocation.</param>
        /// <param name="factory">The factory.</param>
        /// <returns></returns>
        private object Execute(IInvocation invocation, ChannelFactory factory)
        {
            try
            {
                // Get method to create channel
                var channel = factory.GetType()
                                     .GetMethod("CreateChannel", new Type[] { })
                                     .Invoke(factory, new object[] { });
                Debug.Assert(channel != null, "channel != null");
                object returnValue;
                //Create scope - this is optional and needed if you use cookies on services
                using (var scope = new OperationContextScope((IContextChannel)channel))
                {
                    // may need to add null checking
                    // FInd remote service method to invoke
                    var methodToInvoke = channel.GetType().GetMethod(
                        invocation.Method.Name,
                        invocation.Arguments.Select(argument => argument.GetType()).ToArray());
                    // call the method
                    returnValue = methodToInvoke.Invoke(channel, invocation.Arguments);
                    // close factory
                    factory.Close();
                }

                return returnValue;
            }
            catch
            {
                // abort factory in case of an error to release resources.
                if (factory != null) factory.Abort();
                throw;
            }
        }
    }

}

I tried hard to document what I am doing.  Read the comments to see what I am doing.  You could shore up the code that finds the method to invoke based on parameter types.  Possible other enhancement would involve caching of reflection calls to speed up the process.  Because I do not know service type in advance, I have to create channel factory using reflection, the same is the case with the channel. If you are supporting a single service interface, you can remove most of the reflection code.

 

Here is my test service interface and implementation

using System.ServiceModel;
using System.ServiceModel.Web;

namespace CastleAbstraction
{
    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        [WebGet(UriTemplate = "DoWork?input={input}")]
        string DoWork(string input);
    }
}
namespace CastleAbstraction
{
    public class TestService : ITestService
    {
        public string DoWork(string input)
        {
            return string.Format("Message {0} was received by service", input);
        }
    }
}

 

Finally, I am creating a small project with two console apps.  One will host my service, the other will be the client.  Here is the service one

using System.ServiceModel.Description;
using System.ServiceModel.Web;
using CastleAbstraction;
using System;
using System.ServiceModel;

namespace ServiceHost
{
    class Program
    {
        static void Main(string[] args)
        {
            var host = new WebServiceHost(typeof(TestService), new Uri("http://localhost:3333/"));
            ServiceEndpoint ep = host.AddServiceEndpoint(typeof (ITestService), new WebHttpBinding(), "");
            host.Open();
            Console.WriteLine("Press any key");
            Console.ReadKey();
        }
    }
}

 

And here is the client:

using CastleAbstraction.Proxies;
using System;

namespace CastleAbstraction
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Wait for service to start up and press any key");
            Console.ReadKey();
            ProxyFactory.UseLocalExecutionMode = false;
            ProxyFactory.Url = "http://localhost:3333/";
            ProxyFactory.RegisterTypes();
            var proxy = ProxyFactory.CreateProxy<ITestService>();
            var returnValue = proxy.DoWork("testing");
            Console.WriteLine("Service returned:...... " + returnValue);
            Console.WriteLine("Press any key to quit");
            Console.ReadKey();
        }


    }
}

You can test both configurations by changing UseLocalExecutionMode flag.  You can download full project here.

Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *