Introduction
There is a growing number of ASP.NET MVC projects which consume WCF services. We as professionals want to keep
our controllers clean and easily testable. Here is the list of obvious options to enable ASP.NET MVC controllers to consume WCF services:
- Create a helper class which will encapsulate the WCF
ChannelFactory
: Here is a possible implementation:
public class WcfClientProvider<T> : IDisposable
{
private ChannelFactory<T> factory;
public WcfClientProvider()
{
factory= new ChannelFactory<T>(string.Empty);
Open();
}
public object GetProxy()
{
return factory.CreateChannel();
}
public void Open()
{
if (this.factory.State != CommunicationState.Opened)
{
factory.Open();
}
}
public void Dispose()
{
factory.Close();
}
This class can be used in a controller:
public class ProductController : Controller
{
public ViewResult ProductList()
{
List<Product> result = null;
using (var client = new WcfClientProvider<IProductService>())
{
result = client.GetProducts();
}
return View(result);
}
}
Howether, there are problems with this code. Firstly the controller action is not testable at all and secondly the syntax is
verbose and you have to remember to always use using
when creating the
WcfClientProvider
.
- A much better option would be to create a facade for a WCF service interface and use constructor
dependency injection in the controller, like in the example below:
public class WcfProductSerivceProxy : IProductService
{
public IList<Product> GetProducts()
{
List<Product> result = null;
using (var client = new WcfClientProvider<IProductService>())
{
result = client.GetProxy().GetProducts();
}
return result;
}
public IList<Product> GetProductsByCategory(string category)
{
List<Product> result = null;
using (var client = new WcfClientProvider<IProductService>())
{
result = client.GetProxy().GetProductsByCategory(category);
}
return result;
}
}
public class ProductController : Controller
{
private IProductService productService;
public ProductController(IProductService productService)
{
this.productService = productService;
}
public ViewResult ProductList()
{
return View(this.productService.GetProducts());
}
}
The code above looks almost fine. It is easily testable and clean, except the fact that you have to create such
a facade
for every WcfInterface
in your solution, which is really a boring code to write and clearly violates the Don't Repeat Yourself principle.
The solution
Ideally we would like to have something like in the option two example, but without
the need to write tons of facade code. This is where Castle Windsor can
help. We can create an interceptor for a WCF interface which will exactly mimic the code in
the facade classes. Here is how it is done:
public class WcfInterceptor : IInterceptor
{
public IClientFactory ClientFactory { get; set; }
public void Intercept(IInvocation invocation)
{
var clientProvider = ClientFactory.GetClientProvider(invocation.Method.DeclaringType);
using(clientProvider)
{
invocation.ReturnValue = CallClientProviderMethod(invocation, clientProvider);
}
}
private object CallClientProviderMethod(IInvocation invocation, IClientProvider clientProvider)
{
var proxy = clientProvider.GetProxy();
return invocation.Method.Invoke(proxy, invocation.Arguments);
}
}
Here the ClientFactory
returns a generic helper class, for instance WcfClientProvider<IProductService>
.
The code for the Client factory is shown below:
public interface IClientFactory
{
IClientProvider GetClientProvider(Type type);
}
public class WcfClientFactory : IClientFactory
{
public IClientProvider GetClientProvider(Type type)
{
var closedType = typeof(WcfClientProvider<>).MakeGenericType(type);
return (IClientProvider)Activator.CreateInstance(closedType);
}
}
Let me explain what is going on here. WcfInterceptor
is registered for each
WcfInterface
in the solution (later I will show how to
do that) using the WindsorCastle Dependency Injection container. It means that for any call to a
WcfInterface
, the intercept method will be called.
For example, when calling productService
from a controller action:
public ViewResult ProductList()
{
return View(this.productService.GetProducts());
}
the intercept method on WcfInterceptor
will be invoked. Then we call the intercepted method on the
WcfProxy
object:
private object CallClientProviderMethod(IInvocation invocation, IClientProvider clientProvider)
{
var proxy = clientProvider.GetProxy();
return invocation.Method.Invoke(proxy, invocation.Arguments);
}
Here is the Windsor Castle installer code:
public class Installer : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.BasedOn<IController>()
.LifestyleTransient());
container.Register(Component
.For<IInterceptor>()
.ImplementedBy<WcfInterceptor>()
.Named("wcf"));
container.Register(Types
.FromAssemblyContaining<IProductService>()
.Where(x => x.IsInterface)
.LifestyleTransient()
.Configure(x =>
{var res = x.Interceptors(InterceptorReference.ForKey("wcf")).Anywhere;} ));
container.Register(Component
.For<IClientFactory>()
.ImplementedBy<WcfClientFactory>());
}
}
With the first container.Register
call, we register all Controllers in
the current assembly. The second call registers the WcfInterceptor
. The
third call registers all WcfInterface
s from the shared library with
WcfInterceptor
. And finally WcfClientFactory
which is capable
of creating a closed WcfClientProvider<T>
.
You can find a working solution in the accompanying download.
Using the code
When running locally, the solution make sure the port in ASP.NET MVC web.config matches the port for WcfProject, otherwise ASP.NET MVC will not
be able to communicate with the WCF service project.
History
Initial version.