Saturday, May 3, 2008

Dependency Injection in WCF Services Part 2

In the first post of these series, I explained the problems you may run into when applying Dependency Injection in your distributed service layer and some of the available solutions to get them around, mainly focusing WCF services. Let's finish off with the theory and put one of those solutions in practice, shall we? In this post I'll rework the sample that I used in my previous post to show the Service Locator pattern in order to use Dependency Injection instead. As in the project that I'm currently working on we are using Castle, I'll use WCF Castle facility to do the service injection.

Let's take then my Service Locator sample


public class CustomerService:ICustomerService
{
#region ICustomerService Members
public Customer GetCustomer(long id)
{
ICustomerDataAccess da;
da = Global.Container.Resolve<ICustomerDataAccess>();
return da.GetCustomer(id);
}

#endregion
}

and rework it to use Dependency Injection


public class CustomerService:ICustomerService
{
#region properties
private ICustomerDataAccess customerDataAccess;
public ICustomerDataAccess CustomerDataAccess
{
get { return customerDataAccess; }
set { customerDataAccess = value; }
}
#endregion
#region ICustomerService Members
public Customer GetCustomer(long id)
{

return CustomerDataAccess.GetCustomer(id);
}

#endregion
}

You can notice that with those changes our service class is no longer dependant on the DI container for resolving the CustomerDataAccess implementation like it is in the Service Locator example. Instead, the Data access dependency is a property that can be injected by an external agent, which can be whatever DI container we want to use or even a simple factory... Let's focus on this sample though!

The next step is to create a Castle configuration file called "Objects.xml" where both service and data access objects are defined.


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<components>
<component id="CustomerService"
service="DISample.Service.ICustomerService, DISample.Service"
type="DISample.Service.CustomerService, DISample.Service">
</component>
<component id="CustomerDataAccess"
service="DISample.DataAccess.ICustomerDataAccess, DISample.DataAccess"
type="DISample.DataAccess.CustomerDataAccess, DISample.DataAccess">
</component>
</components>
</configuration>

As you can see there is no need to connect the DataAccess dependency to the Service object in the configuration file; Castle automatically wires it up.

Now that the Service code is ready for injection and the objects are defined in a configuration file, it's time to use the WCF facility to do the actual service injection.
In order to install the dependency injection behavior, Castle facility uses a a custom WCF ServiceHost. Then, for a non-IIS 6.0 environment and asuming that the service and the endpoint configuration are located in an external configuration file, our WCF host creation would look something like this.


WindsorContainer container = new WindsorContainer("Objects.xml");
Uri uri = new Uri("net.tcp://localhost/DISample");
WindsorServiceHost host = new WindsorServiceHost(container.Kernel, typeof(CustomerService), uri);
host.Open();

We instantiate the Windsor container with our object configuration file so as to be used by the custom service host... and that's all for non-IIS6.0 hosts!

As you may already know, ServiceHost instatiatation model for WCF services host under II6.0 is different: We don't explictly create the ServiceHost; instead ServiceHosts are created by a ServiceHostFactory on each HTTP request.
WCF castle facility provides a custom ServiceHostFactory that can be specified for its use in IIS6 .svc service files. Let's change the default CustomerService.svc accordingly.

<%@ ServiceHost Service="CustomerService"
Factory="Castle.Facilities.WcfIntegration.WindsorServiceHostFactory, Castle.Facilities.WcfIntegration" %>

Where the value of attribute Service must match either the id of our service in Castle configuration file or the service type itself.
Are we missing anything? We haven't created the Windsor Container yet, have we?. Like I said before in II6.0 environments ServiceHosts are created by http request, but would it be wise to apply the same approach for creating the WindsorContainer? I don't think so! Don't panic though, WCF facility has already covered that. Since we are in a web environment, we can use the Global.asax Application_Start event to create our Windsor Container and then register it through an available method in the custom ServiceHostFactory.


public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
WindsorContainer container = new WindsorContainer("Objects.xml");
WindsorServiceHostFactory.RegisterContainer(container.Kernel);
}
}

We are done! What's next then? One of the things that I pointed out in the first post of this series is that no AOP features can be applied upon objects that are not handled by the DI container... That's no longer a problem for us!
In the next post of these series I'll show how we can use castle interceptors to apply cross cutting concerns upon service objects.

kick it on DotNetKicks.com

3 comments:

Sidar Ok said...

Hey man - this is really neat stuff. I haven't seen much going on in terms of loosely coupling for WCF and with Castle , this is surely a very good sample to promote re use and decoupling of cross cutting implications.

will be looking forward to your next blog post covering this issue with castle. thanks for this.

Stanislav Dvoychenko said...

Good one. Quite interesting to see that you are using Castle. That would be my choice as well (if I have one, arghhh).

Javi said...

Thanks Stan!
I have just started using Castle, but as a former Spring.NET user, it seems to me that Castle is a lighter framework and somewhat easier to use probably because it mainly tries to target Dependency Injection concerns... It's just my newbie personal opinion though!
Btw, I found very interesting the ammendments that you applied to Oran's solution, it looks to me that your behaviour extension solution is more configurable than the Custom Host approach and besides it's nice to be able to define different service instance objects of the same type and then configure the container to pick up a particular one... I might steal your code and adapt it for Castle!