Tuesday, November 25, 2008

Windsor WCF Integration

I've been playing with the Windsor WCF Integration facility today. I've been using a recent trunk build of the Castle project, and since there's been a lot of work recently on the WCF facility, the docs on the Castle site are somewhat out of date. I had to work out how to use the facility by reading the unit tests and a post by Craig Neuwirt to the Castle Project Development list.

So why would you want to integrate WCF with your IoC container? When you use an IoC container in your application it should be the repository of any service that you consume. The service consumer should not care about how the service is implemented or where it comes from. A service should also not be concerned about how it is consumed. The Windsor WCF facility allows you to use your WCF services and proxies as if they were any other service provided by the container.

Let's think about how this might help us when writing enterprise applications. My eCommerce platform, Suteki Shop, sends the customer an email confirming that they've made an order. It sends them another email when the order is dispatched. I have an interface IEmailSender that defines the contract for sending an email. Currently my Windsor configuration maps the IEmailSender service to a class called EmailSender. The OrderController expects to be given an instance of IEmailSender in its constructor. So now it gets an actual instance of EmailSender that wraps the .NET API for sending emails. When the customer clicks the 'order' button the request blocks while an email is sent. This isn't a very scalable solution, but I'm not worried about it now because the only client I have for Suteki Shop is low volume. If their shop take off and starts to get a lot more traffic, I will want to have a more scalable architecture for sending emails. With the WCF integration I could configure IEmailSender to provide a WCF proxy instead of a concrete class. I could then install EmailSender (the IEmailSender implementation) on another server and configure the local container to expose it as a WCF service. The protocol I use can be configured using WCF. So I could use TCP, SOAP, MSMQ, or whatever fitted the purpose. The key point is that by using the WCF facility I've been able to take a part of my application and move to to a new process or machine without touching a single line of code.

So how do you use the WCF facility?

Service

Let's assume we have a simple service interface:

[ServiceContract(Namespace = "Mike.WindsorWCFIntegration")]
public interface ICustomerService
{
    [OperationContract]
    Customer GetCustomer(int id);        
}

Customer is just some entity in my application. In order to serve this via WCF we have to attribute the service interface with WCF attributes: ServiceContract and OperationContract. We don't have to do anything to the existing Windsor.config file, it looks the same as before:

<component id="customerService"
   service="Mike.WindsorWCFIntegration.ICustomerService, Mike.WindsorWCFIntegration"
   type="Mike.WindsorWCFIntegration.DefaultCustomerService, Mike.WindsorWCFIntegration">
</component>

We're telling the container that when a component asks for an ICustomerService (or 'customerService' by name) they will be given DefaultCustomerService. Since I'm going to host the service as part of a web application, we can use the WCF IIS integration and create a CustomerService.svc file for the customer service that defines the service host:

<%@ ServiceHost Language="C#" Service="customerService" 
Factory="Castle.Facilities.WcfIntegration.DefaultServiceHostFactory, Castle.Facilities.WcfIntegration"  %>

Note that the Service attribute specifies the same name as the component id in the Windsor.config. The other important point is that we're asking WCF to use the Castle DefaultServiceFactory. This service factory wraps the Windsor container which it uses to resolve any service requests.

We configure WCF as normal:

<system.serviceModel>
  <services>
    <service name="customerService">
      <endpoint contract="Mike.WindsorWCFIntegration.ICustomerService" binding="basicHttpBinding" />
    </service>
  </services>
</system.serviceModel>

Finally we have to register the WCF Facility with the container on application start:

protected void Application_Start(object sender, EventArgs e)
{
    Container = new WindsorContainer()
        .AddFacility<WcfFacility>()
        .Install(Configuration.FromXmlFile("Windsor.config"));
}

The nice thing is that we haven't had to alter our Windsor.configuration or our existing service. Although we did have to attribute our service interface with WCF specific concerns which is a bit of a shame.

Client

The client side story is also pretty simple. Here's a client service that uses the customerService:

using System;
namespace Mike.WindsorWCFIntegration.Client
{
    public class DoSomethingWithCustomers : IDoSomethingWithCustomers
    {
        private readonly ICustomerService customerService;
        public DoSomethingWithCustomers(ICustomerService customerService)
        {
            this.customerService = customerService;
        }
        public void DoIt()
        {
            WriteClientDetails(customerService);
            WriteClientDetails(customerService);
        }
        private static void WriteClientDetails(ICustomerService customerService)
        {
            var customer = customerService.GetCustomer(24);
            ...
        }
    }
}

It's expecting the customerService to be injected by the container. The Windsor.config has to specify that ICustomerService is provided by WCF:

<component id="customerService"
    type="Mike.WindsorWCFIntegration.ICustomerService, Mike.WindsorWCFIntegration"
    wcfEndpointConfiguration="customerClient">
</component>

The wcfEndpointConfiguration references the WCF configuration in App.config, note that it's the same as the endpoint name, 'customerClient':

<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://localhost:2730/CustomerService.svc"
        binding="basicHttpBinding"
        contract="Mike.WindsorWCFIntegration.ICustomerService"
        name="customerClient">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

Once again we have to make sure that the WCF Facility is registered with the container (you can do this in the XML configuration as well):

var container = new WindsorContainer()
 .AddFacility<WcfFacility>()
 .Install(Configuration.FromXmlFile("Windsor.config"));

I hope I've been able to show that using WCF integration with Windsor can be a big win in extending component oriented application design to include serving and consuming remote services. Note that in none of the code above did I have to change the implementation of any of my services in order to make them work with WCF. You can retain your Dependency Injected, testable components and simply have them served onto the web. WCF allows you to configure the wire format (SOAP, REST, POX, binary) and transport (HTTP, named pipes, TCP-IP) independently of your component design. It's very easy to use and very nifty.

You can download the complete demo solution here:

http://static.mikehadlow.com/Mike.WindsorWCFIntegration.zip

12 comments:

Colin Jack said...

The fluent interface is good and I'd recommend looking at the tests with the WCF facility, they show how to use it in a variety of situations and I found them very useful.

emq3 said...

I've ben playing with the WCF Facility recently too. I wanted to be able to specify a bunch of dependencies to have a perWcfOperation lifestyle, similar to the perWebRequest lifestyle, so that when my service is instantiated, all it's dependencies will be cleaned up after the operation exits. I'm surprised that this doesn't exist already, actually. It took some doing but I finally managed it using a combination of ICallContextInitializer and AbstractLifestyleManager.

The tricky part though, was realising that this means that my service class needs to explicitly request it's dependencies becuase it is itself created before the ICallContextInitializer.BeforeInvoke method is called - ie it is created *outside* the context of a WCF operation.

So, Mike, I wondered if you'd encountered this problem during your own playtime?

Neil Mosafi said...

emq3, I would have thought that setting the lifestyle on your service implementation to Transient (and all required dependencies) would have done that for you?

Mike Hadlow said...

Colin,

Yes, I like the fluent interface. I orginally wrote this post using it. I then thought it would be nicer to show how you could serve/consume a component via WCF with simple XML configuration changes. Of course you have to decorate the WCF contract with Contract and Operation attributes, so it's never that simple. I wanted to someone who's familiar with WCF and Windsor how little work you need to do to use the facility.

Mike Hadlow said...

emq3

Neil is correct. I've just modified the solution so that the DefaultCustomerService implements IDisposable, I've also marked DefaultCustomerService as transient. I added a Debug.WriteLine() call to both the constructor and Dispose() method. Dispose was called immediately after the web service completed.

I've uploaded the new solution. If you get it again you'll see the changes.

Anonymous said...

Fantastic!

But, let's see - a build from a trunk on a completely undocument version for a 'enterprise system' :)

Whew, I'm struggling to get that.

Castle stack is tremendous. I love it. But it's downfall is that the community isn't given enough control over the document imo, OR isn't contributing enough to the document to make it more than what it is.

Something needs to be done, telling my client/boss that 'hey, it's not document but we have the trunk code to run our multi-billion dollar company gets people a bit nervous!

Thanks for your article, all the help we can get from guys like you really helps show off this great open source stack!

Mike Hadlow said...

Anonymous,

Thanks for a great comment.

True, the Castle documentation is not great. But to be fair it's mostly aimed at the release candidate, not the trunk. If you compile and use the Castle trunk in your software then you are playing with a work in progress. Of course it's not as reassuring as telling your boss that you are using a mature product from a recognised vendor.

I'm sure Hamilton Verissimo would love to have you, or anyone else, contribute to the Castle documentation, so to say that the community isn't given enough control over it is unfair.

I approach open source software from a slightly different angle. I ask myself, "would I like Ayende and Craig to work on my enterprise system?" Well of course I would. They are far far better programmers than I am and they are giving away code. I could try and integrate the Windsor IoC (a very mature and stable container) and WCF (one of Microsoft's core technologies) myself, but would I do as good a job?

I find it strange that developers are always ready to re-invent the wheel or copy and paste some code they find on a blog, but are reluctant to build and use open source frameworks. You have the code and the guy who wrote it is only an email away. You can judge for yourself if you think it's fit for purpose. Your boss hired you to write code from scratch, surely he should trust your judgment in choosing what code to include from 3rd parties?

Anonymous said...

Fair enough :)

How about in the trunk WCF Facility section of the Castle website, you go post a link to this blog post so that the next guy, like you, can find the info?


Thanks!

Daniel Jin said...

Is the facility using a special proxy that will handle reset after faulting? Or is this just the default WCF proxy generated by the ChannelFactory?

Mike Hadlow said...

Daniel Jim,

I don't know. I'll be the first to admit that I'm no WCF expert. Is this quote from Craig Neuwirt relevant?

"Automatic detection of unhanded channel faults and re-acquisition of new channel proxy

You can continue to use client proxy even after an exception occurs. By default WCF makes all faulted channels unavailable."

Daniel Jin said...

Yeah, that answers my question. Thanks.

dbones said...

Many thanks for the simple intro.