Introduction
Most of the time, a service is stateless or requires state in such a way that InstanceContextMode.Single
or InstanceContextMode.PerSession
does the job just fine. In the case that you require a persisting state independent of the service itself, but that the service needs to use, you're out of luck with the base toolkit in WCF. WCF does provide many points to extend the framework, however. This article uses those to show you how to create your own dependency-injected ServiceHost
.
Background
The extensibility points we're going to be interested in are System.ServiceModel.Description.
IServiceBehavior
and System.ServiceModel.Dispatcher.IInstanceProvider
. IServiceBehavior
allows extending the service description (via dispatch behavior), binding parameters, and validation of the service itself. IInstanceProvider
allows extending the way an instance is provided/handled by WCF. Let's jump right into it!
The Code
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
public class DependentServiceHost<T> : ServiceHost
{
public DependentServiceHost(Type serviceType, T dependency,
params Uri[] uris) : base(serviceType, uris)
{
this.Description.Behaviors.Add(new DependencyInstanceProvider<T>(serviceType, dependency));
}
}
public class DependencyInstanceProvider<T> : IInstanceProvider, IServiceBehavior
{
public DependencyInstanceProvider(Type serviceType, T dependency)
{
_dependency = dependency;
_serviceType = serviceType;
}
private readonly T _dependency;
private readonly Type _serviceType;
public object GetInstance(InstanceContext instanceContext, Message message) =>
Activator.CreateInstance(_serviceType, _dependency);
public object GetInstance(InstanceContext instanceContext) =>
GetInstance(instanceContext, null);
public void ReleaseInstance(InstanceContext instanceContext, object instance) =>
(instance as IDisposable)?.Dispose();
public void ApplyDispatchBehavior
(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers)
foreach (EndpointDispatcher ed in cd.Endpoints)
if (!ed.IsSystemEndpoint)
ed.DispatchRuntime.InstanceProvider = this;
}
public void AddBindingParameters(ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase,
System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters) { }
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
}
Because this implementation is so simple, I packed both IServiceBehavior
and IInstanceProvider
implementations into the same class, but you can separate them if you wish or have more complex behavior. Just made more sense for this specific example to do it this way.
So first, we add our IServiceBehavior
class to the ServiceHost
's list of behaviors. This is so that our ApplyDispatchBehavior()
method will get called and add our IInstanceProvider
class to the user-defined endpoints for this service. What this accomplishes is whenever a client connects to those endpoints, our custom GetInstance()
method gets called and returns an instance with the dependency injected into it. WCF's default instance provider only supports parameterless constructors which is why we must provide our own.
Using the Code
It's as easy as doing it exactly the way you did before with a normal ServiceHost
. Assuming we have some local service, say an UpdateService
class defined, it would look like this:
string someImportantInfo = "Important Info";
string baseServiceAddress = "net.pipe://localhost/Service/";
NetNamedPipeBinding binding = new NetNamedPipeBinding();
DependentServiceHost<string> host = new DependentServiceHost<string>(
typeof(UpdateService), someImportantInfo, new Uri(baseServiceAddress));
host.AddServiceEndpoint(typeof(IUpdateService), binding, "Update");
If you'd like a full example, I attached one using a self-hosted WCF service in a console project that sends messages back and forth between a client and service.
History
- 10/26/2016: Initial release