Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Extending WCF: DependentServiceHost<T>

5.00/5 (4 votes)
26 Oct 2016CPOL2 min read 12.3K   46  
Extending the ServiceHost class to support dependency injection

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

C#
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)
    {
        //Hook up our custom service behavior into the behavior collection
        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;

    //IInstanceProvider implementation
    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();

    //IServiceBehavior implementation
    public void ApplyDispatchBehavior
           (ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        //Hook up our custom instance provider into the user-defined endpoints for the service
        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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)