WCF Service Dependency Injection
Whoops, I accidentally got some link love from Phil Haack, but my blog has been dormant for 8 months! Time to start posting all those half-finished Windows Live Writer drafts! Here goes...
Say you've implemented a WCF service that uses a business logic layer which in turn uses a data access layer. Rather than hard-code these dependencies into your service's constructor, you would like to use dependency injection to enable unit testing of each layer using a mock object framework such as Rhino Mocks.
For our purposes we'll do constructor injection using Spring.NET, although I've also heard good things about the Castle containers, and ObjectBuilder from Microsoft patterns & practices looks promising.
So we have a few simple classes and interfaces that we'd like to automagically hook up:
[ServiceContract]
public interface IServiceContract
{
[OperationContract]
...
}
public class ServiceLayer : IServiceContract
{
IBusinessLogic _businessLogic;
public ServiceLayer(IBusinessLogic businessLogic)
{
_businessLogic = businessLogic;
}
...
}
public interface IBusinessLogic
{
...
}
public class BusinessLogic : IBusinessLogic
{
IDataAccess _dataAccess;
public BusinessLogic(IDataAccess dataAccess)
{
_dataAccess = dataAccess;
}
...
}
public interface IDataAccess
{
...
}
public class DataAccess : IDataAccess
{
...
}
We'd like to accomplish the equivalent of the following code whenever our service is created:
return new ServiceLayer(new BusinessLogic(new DataAccess()));
What does the Spring.NET equivalent look like?
IApplicationContext ctx = ContextRegistry.GetContext();
return (ServiceLayer)ctx.GetObject("ServiceLayer");
How does Spring.NET know how to wire up the dependencies? It uses reflection and config hints to determine who depends on whom:
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects"/>
</context>
<objects xmlns="http://www.springframework.net">
<object name="DataAccess" type="NamespaceA.DataAccess, AssemblyA" />
<object name="BusinessLogic" type="NamespaceB.BusinessLogic, AssemblyB" autowire="constructor" />
<object name="ServiceLayer" type="NamespaceC.ServiceLayer, AssemblyC" autowire="constructor" />
</objects>
</spring>
That's great, but how do we get WCF to do this for us at the right place at the right time? By using WCF's "behavior injection" functionality of course!
We'll need to implement an IInstanceProvider that allows us to serve up instances of our service each time WCF needs a new instance.
public class DependencyInjectionInstanceProvider : IInstanceProvider
{
private Type _serviceType;
public DependencyInjectionInstanceProvider(Type serviceType)
{
_serviceType = serviceType;
}
public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}
public object GetInstance(InstanceContext instanceContext, Message message)
{
object result = null;
IApplicationContext context = ContextRegistry.GetContext();
string[] objectNames = context.GetObjectNamesForType(_serviceType);
if (objectNames.Length != 1)
{
throw new YourOwnException(
string.Format(
CultureInfo.InvariantCulture,
"There must exist exactly one <object> definition for the {0} service in the Spring configuration",
_serviceType.Name)
);
}
return context.GetObject(objectNames[0]);
}
public void ReleaseInstance(System.ServiceModel.InstanceContext instanceContext, object instance) { }
}
Then we'll need an IServiceBehavior that plugs in our IInstanceProvider at the right place at the right time. Remember, WCF could choose to activate our service per call, per private session, per shared session, or as a singleton.
public class DependencyInjectionServiceBehavior : IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ed.DispatchRuntime.InstanceProvider =
new DependencyInjectionInstanceProvider(serviceDescription.ServiceType);
}
}
}
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) {}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {}
}
Now we want to actually use this behavior. You could implement classes to apply this behavior using custom attributes, via config, or programmatically via a custom ServiceHost. I chose to go the ServiceHost route.
public class MyServiceHost : ServiceHost
{
public MyServiceHost() : base() { }
public MyServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses) { }
protected override void OnOpening()
{
this.Description.Behaviors.Add(new DependencyInjectionServiceBehavior());
base.OnOpening();
}
}
And of course a custom ServiceHost needs a custom ServiceHostFactory if you want IIS to host it (you can use your custom ServiceHost directly when self-hosting):
public class MyServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new MyServiceHost(serviceType, baseAddresses);
}
}
This factory can then be referenced from your .svc files:
<%@ ServiceHost
Service="NamespaceC.ServiceLayer, AssemblyC"
Factory="NamespaceD.MyServiceHostFactory, AssemblyD"
%>
Steve Maine has a great series of posts with more details on how service activation and hosting work:
32 comments:
Excellent stuff. We've been using this in our project for a while now with great sucess. However, one thing we did find is that the destroy-method on the Spring object never gets called. We got around the problem by implementing the ReleaseInstance method as follows:
public void ReleaseInstance( InstanceContext instanceContext, object instance )
{
if ( instance is IDisposable )
{
if ( _log.IsDebugEnabled )
{
_log.DebugFormat( "Disposing of {0}", instance.GetType().ToString() );
}
( ( IDisposable ) instance ).Dispose();
}
}
Of course this only works for IDisposable objects. Any idea on how to call the spring destroy-method method?
Regards,
Christian M
Hi Christian, I'm glad to hear it was useful to you! I currently only use Spring for its constructor-based dependency injection feature, but from digging around in the Spring documentation it looks like you should be able to extend your ReleaseInstance implementation to do something like this:
instance.GetType().GetMethod(context.GetObjectDefinition(objectNames[0]).DestroyMethodName).Invoke(instance, null);
I haven't tested it but the GetObjectDefinition/DestroyMethodName stuff looks pretty close to what you'll need.
Hello,
Could you supply the full code in a zipped file?
Thanks
Yaz
Where did you get IApplicationContext from?
It is a .net?
Thanks
Yaz
Great stuff. I'm adding you to my rss reader.
I will try this using ObjectBuilder.
Thanks
Matias
Matias,
I look forward to seeing an example using the object builder.
Yaz
Hi,
Great blog entry, great to see this and plan to get functionality like this into Spring.NET (after 1.1 RTM). WCF exporters, similar to the WebServiceExporter, are also planned.
It is also worth noting that you can take advantage of the AOP features of Spring.NET and apply 'aspects/advice' to your services exposed in this manner.
I've put up a link to this on the Spring.NET homepage.
Cheers,
Mark
Hi,
Great stuff !
I've did some WCF integration recently and commited the code to CVS.
I took another approach and only commited the ServiceHostFactory for now, but the ServiceHost will work in the same way.
The code is in Spring.Services project under ServiceModel/Activation directory :
http://fisheye1.cenqua.com/browse/~raw,r=1.1/springnet/Spring.Net/src/Spring/Spring.Services/ServiceModel/Activation/ServiceHostFactory.cs
To use it, we need to change the .svc file :
<%@ ServiceHost Factory="Spring.ServiceModel.Activation.ServiceHostFactory" Service="ContextName:ObjectName" %>
There is still some work to do as Mark said, like removing the need for the .svc file or adding a ServiceHost implementation for Spring.
Cheers,
Bruno
Hi Alex, you can self-host the service by putting something like this in your Main method:
using (MyServiceHost myHost = new MyServiceHost(typeof(ServiceLayer), new Uri("http://localhost:8080/myservice/")))
{
myHost.Open();
Console.WriteLine("Service started. Press Enter to exit.");
Console.ReadLine();
}
MyServiceHost is a drop-in replacement for the ServiceHost you would normally use for self-hosting.
MyServiceHost hooks up Spring dependencies for the services it hosts, but is not itself a recipient of the Spring dependency injection process.
Hi Orand, I'm getting an error here:
public MyServiceHost(Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses) { }
that reads:
System.InvalidOperationException was unhandled
Message="The service type provided could not be loaded as a service because it does not have a default (parameter-less) constructor. To fix the problem, add a default constructor to the type, or pass an instance of the type to the host."
Source="System.ServiceModel"
StackTrace:
at System.ServiceModel.Description.ServiceDescription.CreateImplementation(Type serviceType)
at System.ServiceModel.Description.ServiceDescription.SetupSingleton(ServiceDescription serviceDescription, Object implementation, Boolean isWellKnown)
at System.ServiceModel.Description.ServiceDescription.GetService(Type serviceType)
at System.ServiceModel.ServiceHost.CreateDescription(IDictionary`2& implementedContracts)
at System.ServiceModel.ServiceHostBase.InitializeDescription(UriSchemeKeyedCollection baseAddresses)
at System.ServiceModel.ServiceHost..ctor(Type serviceType, Uri[] baseAddresses)
If I give it a default constructor, it works, but then I lose my dependency injection. It looks like you had a constructor dependency as well. Any idea why I'm seeing this?
Thanks
Hi Orand,
I just wanted to let you know that I got everything working great! I've never used Spring.NET, so this is pretty exciting!
Thanks again!
Jeff: interesting bug! After poking around with Reflector, it turns out that in the special case where our service is configured to run as a singleton by putting [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] on our service, WCF disregards our instance provider in ServiceDescription.CreateImplementation and is hard-coded to look for a parameterless constructor.
So if I add another constructor to the class:
private ServiceLayer() {}
this appears to make WCF happy, it calls the parameterless constructor once, and tucks it away in a private field called hiddenSingleton in a ServiceBehaviorAttribute object.
When calls come in, ServiceBehaviorAttribute.ApplyInstancing grabs the cached version of our service from hiddenSingleton instead of using our instance provider version.
The fix appears to be setting DispatchRuntime.SingletonInstanceContext in our DependencyInjectionServiceBehavior class. So at the beginning of ApplyDispatchBehavior, do this:
ServiceBehaviorAttribute serviceBehavior = serviceDescription.Behaviors.Find<ServiceBehaviorAttribute>();
and then inside the foreach(EndpointDispatcher ed in cd.Endpoints) add this:
if(serviceBehavior != null && serviceBehavior.InstanceContextMode == InstanceContextMode.Single)
{
ed.DispatchRuntime.SingletonInstanceContext = new InstanceContext(serviceHostBase);
}
leaving the pre-existing InstanceProvider stuff alone.
Now we get a Spring-loaded singleton version of our service, although WCF still hits the private parameterless constructor first.
I'm not entirely comfortable with this solution since I don't feel that I fully understand why it works the way it does and I've only played around with it in the debugger, but hopefully this is somewhat helpful.
Alex, glad you got it working! This was my first introduction to Spring as well and I agree, it's pretty cool.
Very useful. I've been using it in my project and it's working fine.
But now I'd like to integrate logging via AOP into my service class. Do you have any idea how to realize this? I think it's necessary to "wrap" my service object as Target of a ProxyFactoryObject. But how do we have to "connect" with the requirements of the WCF (definition of endpoints etc.)?
Currently my implementation looks like this:
1. App.config:
<spring>
..
<objects xmlns="http://www.springframework.net">
<object id="loggingAroundAdvice" type="MyProject.Spring.LoggingAroundAdvice, MyProject">
<property name="Logger" value="MyService" />
</object>
<object id="myService" type="Spring.Aop.Framework.ProxyFactoryObject">
<property name="Target"><object type="MyProject.MyService, MyProject"/></property>
<property name="InterceptorNames">
<list><value>loggingAroundAdvice</value></list>
</property>
</object>
</objects>
</spring>
..
<system.serviceModel>
<services>
<service name="MyProject.MyService">
<endpoint address="MyServiceBasic" binding="basicHttpBinding" contract="MyProject.IMyService" />
</service>
</services>
</system.serviceModel>
2. Creation of ServiceHost:
IMyService myService = (IMyService)new SpringInstanceProvider(typeof(MyService)).GetInstance(null);
using (ServiceHost host = new ServiceHost(myService, baseAdresses))
{
host.Open();
..
}
By this way no endpoints can be found by the host because the service object is not of the desired type but a proxy object ..
Wunderbar!
I have implemented using Castle's Windsor and using a custom attribute. This is great stuff!
I was gonna see if the Castle Contrib folks wanna take a look at this...giving you massive credit, of course. That cool?
Ruprict: go for it! The Spring.NET folks have already done so, and I'd really like to see what the Castle implementation looks like. I probably would have gone with Castle originally, but back when I wrote this it seemed easier to get started with Spring based on the documentation at the time.
HA! Ayende beat me to it
Windsor WCF Integration
Hi Oran Dennison,
Is that possible to integrate Windows WorkFLow and Spring.Net?
Actually I am proposing the architecture of an application based on Spring.Net but the application is heavily based on Work Flows. So I am thinking to integrate the two.
I'll be thankful if you could provide same example for WF and Spring.Net integration.
Regards,
Asif Tasleem
asiftasleem at gmail dot com
Asif, I'm curious, what do you see as the benefits of integrating Workflow and Spring.NET? If you are interested in doing this in the context of WCF, you may want to check out the new durable services feature in .NET 3.5 that further integrates WCF and WF. Creating Workflow Services and Durable Services
I am curious as to why any custom code is needed at all. If the service defines interface based properties, isn't spring able to do rule based configuration of the service.
For example, if my service is defined by the interface (attributes omitted).
Public Interface IMyService
property X as IDAO
End Interface
Can't a Spring configuration be created that does essentially:
"For objects of type IService X=SpringObjectName"
Where SpringObjectName is a spring object definition/configuration which implements interface IDAO?
Thanks
Rich
Hi Rich, what "custom code" are you referring to? My code is basically glue to wire WCF and Spring together.
If your question is "can't Spring just do this out-of-the box?" then the answer is yes, the Spring Framework folks incorporated the code from this blog post into the Spring Framework so that it is no longer necessary to write this yourself. But my code came first. :-)
Hi
great post.
I saw here that is better to use IEndpointBehavior instead of IServiceBehavior.
What do you think?
Oran,
First - excellent stuff, on a subject that is complex and not very well documented.
Wanted to ask you if you have an updated (.net 3.5?) version of your code you could post. It seems that in Spring 1.3 the support for your approach is not there (was there in 1.2). Spring's solution clearly does not work for "InstanceContextMode.Single" service behaviors and the Spring forums are suspiciously quiet, so you I thought I ask you
Cheers
Excellent Post. Very helpful.
Thank you very much.
Just saved me alot of plumbing work, excellent information!
so is .svc only available if you are hosting in IIS? or is it the files created when using the mex endpoint for metadata?
I am hosting in Windows Services/Console for release/test.
Also I have the mex endpoint turned off and am passing the dll for use since it is all in house.
so then how do I use the customhostfactory? with the activation call?
Thanks
When/why would "cd != null" evaluate to false?
cd is null if the variable cdb of type ChannelDispatcherBase can't be cast to type ChannelDispatcher. See the documentation for C#'s "as" keyword for details.
Orand,
I understand the functioning of the "as" operator. I should have asked if you knew of a case where ChannelDispatcherBase wouldn't cast to ChannelDispatcher.
If you do know of such a case, what would you do to handle it?
Peter Provost has documented the equivalent dependency injection technique for ASP.NET Web API services.
http://www.peterprovost.org//blog/2012/06/19/adding-ninject-to-web-api/
Post a Comment