Download the code for this post.
A while ago I blogged about using Ninject for dependency injection with WCF Services. The advantage of using DI is that it allows you to achieve loose coupling in your application architecture, so that you’re not tightly bound to a particular infrastructure implementation, such as data access or logging. The problem with WCF services is that by default they are required to have a parameterless constructor, which does not play nice with DI containers, such as Ninject, which support injection of dependencies via constructor parameters.
I had the need recently to set up a REST-style WCF Services project and wanted to use Ninject for DI with it. Luckily, the Ninject WCF Extension project had been updated to support REST, so I updated the Nuget package and discovered the project would not compile. I found out that the static KernelContainer class had been deprecated, so I had to refactor my code to remove references to it. I also noticed there was no longer any need to derive the Global.asax code file from NinjectWcfApplication, because the extension now uses WebActivator to configure Ninject on application startup. A NinjectWebCommon.cs file is placed in an App_Start folder. There you simply add code to a RegisterServices method in order to load your Ninject modules and perform the bindings. (When updating the NuGet package on the other projects, I had to manually remove the App_Start folder.)
Typically when exposing a REST-type endpoint from a WCF service, you would leverage the ASP.NET UrlRoutingModule by adding a ServiceRoute to the RouteTable in the Application_Start method of your Global.asax.cs file. Things get a little tricky, however, if you want to expose both SOAP and REST endpoints from the same WCF service. In this case, you’ll want to supply a ServiceHostFactory-derived class when registering the service route, which let’s you specify an endpoint address that is different than the base HTTP address used for the SOAP endpoint.
public class RestServiceHostFactory<TServiceContract> : NinjectWebServiceHostFactory { protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) { ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses); var webBehavior = new WebHttpBehavior { AutomaticFormatSelectionEnabled = true, HelpEnabled = true, FaultExceptionEnabled = true }; var endpoint = host.AddServiceEndpoint(typeof(TServiceContract), new WebHttpBinding(), "Rest"); endpoint.Name = "rest"; endpoint.Behaviors.Add(webBehavior); return host; } }
Here is code from Global.asax.cs which references RestServiceHostFactory:
public class Global : HttpApplication { protected void Application_Start(object sender, EventArgs e) { RegisterRoutes(); } private void RegisterRoutes() { // Add rest service route RouteTable.Routes.Add(new ServiceRoute("GreetingService", new RestServiceHostFactory<IGreetingService>(), typeof(GreetingService))); } }
Browsing to the REST-endpoint, and appending /help to the url, displays a list of available operations.
In my original blog post on this topic I introduced a NinjectServiceHelper class that can be used by test classes to spin up matching services and clients on the fly. I refactored this class to support REST scenarios by adding the required endpoint behavior for the WebHttpBinding.
public class NinjectServiceHelper<TServiceContract, TServiceType> : IDisposable { bool _disposed; public NinjectServiceHelper(IServiceBehavior serviceBehavior, string address, Binding binding) { // Create Ninject service host _serviceHost = new NinjectServiceHost(serviceBehavior, typeof(TServiceType)); // Add endpoint _serviceHost.AddServiceEndpoint(typeof(TServiceContract), binding, address); // Add web behavior if (binding.GetType() == typeof(WebHttpBinding)) { var webBehavior = new WebHttpBehavior { AutomaticFormatSelectionEnabled = true, HelpEnabled = true, FaultExceptionEnabled = true }; _serviceHost.Description.Endpoints[0].Behaviors.Add(webBehavior); } // Add service metadata var metadataBehavior = new ServiceMetadataBehavior(); if (binding.GetType() == typeof(BasicHttpBinding)) { metadataBehavior.HttpGetEnabled = true; metadataBehavior.HttpGetUrl = new Uri(address); } _serviceHost.Description.Behaviors.Add(metadataBehavior); // Open service host _serviceHost.Open(); // Init client var factory = new ChannelFactory<TServiceContract>(binding); _client = factory.CreateChannel(new EndpointAddress(address)); } private readonly ServiceHost _serviceHost; public ServiceHost ServiceHost { get { if (_disposed) throw new ObjectDisposedException("NinjectServiceHelper"); return _serviceHost; } } private readonly TServiceContract _client; public TServiceContract Client { get { if (_disposed) throw new ObjectDisposedException("NinjectServiceHelper"); return _client; } } public void Dispose() { if (!_disposed) { ((IDisposable)_serviceHost).Dispose(); ((IDisposable)_client).Dispose(); _disposed = true; } } }
The test class then loads a Ninject module in the TestFixtureSetup method that adds named bindings to the kernel for SOAP and REST WCF endpoints.
public class ServicesModule : NinjectModule { public override void Load() { // Basic service host Kernel.Bind<NinjectServiceHelper<IGreetingService, GreetingService>>() .ToSelf() .Named("Basic") .WithConstructorArgument("address", "http://localhost:1234/GreetingService/Soap/") .WithConstructorArgument("binding", new BasicHttpBinding()); // Tcp service host Kernel.Bind<NinjectServiceHelper<IGreetingService, GreetingService>>() .ToSelf() .Named("Tcp") .WithConstructorArgument("address", "net.tcp://localhost:9999/GreetingService/Tcp/") .WithConstructorArgument("binding", new NetTcpBinding()); // Rest service host Kernel.Bind<NinjectServiceHelper<IGreetingService, GreetingService>>() .ToSelf() .Named("Rest") .WithConstructorArgument("address", "http://localhost:1234/GreetingService/Rest/") .WithConstructorArgument("binding", new WebHttpBinding()); } }
The test methods then obtain the appropriate helper instance by name.
private void Greeting_Soap(string protocol) { // Arrange using (var helper = _kernel.Get<NinjectServiceHelper<IGreetingService, GreetingService>>(protocol)) { using ((IDisposable)helper.Client) { // Act string greeting = helper.Client.Hello(); // Assert Assert.That(greeting, Is.StringMatching("Hello")); } } } private void Greeting_Rest(string format) { using (var helper = _kernel.Get<NinjectServiceHelper<IGreetingService, GreetingService>>("Rest")) { // Arrange var client = new WebClient(); if (format == "Json") client.Headers.Add(HttpRequestHeader.Accept, "application/" + format); client.BaseAddress = helper.ServiceHost.Description.Endpoints[0].Address.ToString(); // Act string result = client.DownloadString("Hello"); string greeting; if (format == "Xml") greeting = SerializationHelper.DeserializeXml<string>(result); else if (format == "Json") greeting = SerializationHelper.DeserializeJson<string>(result); else throw new Exception("Format not supported: " + format); // Assert Assert.That(greeting, Is.StringMatching("Hello")); } }
My SerializationHelper class simplifies the task of converting Xml and Json to and from CLR objects.
public class SerializationHelper { public static string SerializeXml<T>(T obj) { using (var ms = new MemoryStream()) { var serializer = new DataContractSerializer(typeof(T)); serializer.WriteObject(ms, obj); string retVal = Encoding.Default.GetString(ms.ToArray()); return retVal; } } public static T DeserializeXml<T>(string xml) { using (var reader = new StringReader(xml)) { using (var xmlReader = XmlReader.Create(reader)) { var serializer = new DataContractSerializer(typeof(T)); var obj = (T)serializer.ReadObject(xmlReader); return obj; } } } public static string SerializeJson<T>(T obj) { using (var ms = new MemoryStream()) { var serializer = new DataContractJsonSerializer(typeof(T)); serializer.WriteObject(ms, obj); string retVal = Encoding.Default.GetString(ms.ToArray()); return retVal; } } public static T DeserializeJson<T>(string json) { using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(json))) { var serializer = new DataContractJsonSerializer(typeof(T)); var obj = (T)serializer.ReadObject(ms); return obj; } } }
Ninject’s WCF extension makes it easy to build both SOAP and REST style WCF services that use dependency injection for apps that are loosely coupled to specific infrastructure implementations. The project examples up on GitHub also demonstrate self-hosting scenarios.
Download the code for this blog post here.
Thanks for the information, just what I was looking for.
I’ve discovered some problems with the Ninject WCF extensions and talk about a better approach here.
Tony, you are the best. Thanks from Ukraine 🙂
Here’s an example using newer versions of Ninject and Ninject.Extensions.Wcf
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Ninject.Extensions.Wcf;
using System.ServiceModel.Description;
namespace HelloNinjectWcf.DependencyResolution
{
public class NinjectServiceHelper : IDisposable where TServiceBehavior : NinjectServiceBehavior
{
bool _disposed;
public NinjectServiceHelper(string address, Binding binding, params Uri[] baseAddresses)
{
_serviceHost = new NinjectServiceHost(Activator.CreateInstance(), typeof(TServiceType), baseAddresses);
_serviceHost.AddServiceEndpoint(typeof(TServiceContract), binding, address);
_serviceHost.Open();
// Init client
var factory = new ChannelFactory(binding);
_client = factory.CreateChannel(new EndpointAddress(address));
}
private readonly ServiceHost _serviceHost;
public ServiceHost ServiceHost
{
get
{
if (_disposed)
throw new ObjectDisposedException(“NinjectServiceHelper”);
return _serviceHost;
}
}
private readonly TServiceContract _client;
public TServiceContract Client
{
get
{
if (_disposed)
throw new ObjectDisposedException(“NinjectServiceHelper”);
return _client;
}
}
public void Dispose()
{
if (!_disposed)
{
((IDisposable)_serviceHost).Dispose();
((IDisposable)_client).Dispose();
_disposed = true;
}
}
}
}
Hi. Thanks. But why you only send 2 parameters to NinjectServiceHelper? You never send an implementation of IServiceBehavior. What must I send as first parameter(serviceBehaviior) since NinjectServiceHost’s constructor needs it?.
I believe the Ninject extension for WCF registers a default behavior to resolve IServiceBehavior, so you can safely ignore that parameter.
It’s true. Thank you.