Pimp Your WCF Runtime - Episode One

If you have ever made any effort in learning WCF, you have probably seen some variation of this:

 

static void Main(string[] args)
{
    ServiceHost host = null;
    try
    {
        host = new ServiceHost(typeof (HelloWCF), new Uri("http://localhost:8080/HelloWCF"));
        host.Description.Endpoints[0].Behaviors.Add(new MyCustomMessageFormatter("message"));
        host.Description.Behaviors.Add(new MyInstanceProvider());

        host.Opening += host_Opening;
        host.Opened += host_Opened;
        host.Faulted += host_Faulted;

        host.Open();

        Console.WriteLine("Service is available.");

        Console.ReadKey();
    }
    finally
    {
        if (host != null)
        {
            Console.WriteLine("Closing service...");
            host.Close();
        }
    }
}

This hosting code, or some variant of it, appears in almost every book on WCF, usually in a console application. It demonstrates creating an instance of ServiceHost for a service implementation (in this case HelloWCF) with a specified URI. A couple of behaviors are added at runtime and we wire up some custom event handlers. All in all, for WCF, nothing complicated.

But what if you want to host your service in IIS? Since it comes with Windows Activation Service (WAS), IIS7 is an ideal environment to host your WCF service. So how do we get the same ability to customize our runtime in IIS?

It's actually pretty easy.

System.ServiceModel.Activation to the rescue!

IIS uses an instance of a ServiceHostFactory to create a runtime for your hosted service. If you do not provide an implementation for this class, IIS will provide you with a default implementation.

If we want to use are own hosting logic, all we have to do is create our own host class which inherits from ServiceHostFactory like so:

 

public class MyCustomHost : ServiceHostFactory
{
    public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
    {
        string serviceType = 
            string.Format("{0}, {1}", constructorString,
            constructorString.Substring(0, constructorString.IndexOf(".")));

        Type t =Type.GetType(serviceType);
        
        ServiceHost host = new ServiceHost(t, baseAddresses);

        return host;
    }

    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        ServiceHost host = new ServiceHost(serviceType, baseAddresses);

        return host;
    }
}

When we do this we need to override both implementations of CreateServiceHost, which is the method IIS calls to create the runtime for you service. For both versions we get a service type and an array of base addresses. The only real difference between the two is that the version that takes the string as the first argument is taking the service type information as a string as opposed to a .NET Type. It's an easy matter to take this string and create an instance of a Type object, in which case we can refactor the code to this:

public class MyCustomHost : ServiceHostFactory
{
    public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
    {
        string serviceType = 
            string.Format("{0}, {1}", constructorString,
            constructorString.Substring(0, constructorString.IndexOf(".")));

        Type t =Type.GetType(serviceType);
        
        return this.CreateServiceHost(t, baseAddresses);
    }

    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        ServiceHost host = new ServiceHost(serviceType, baseAddresses);

        return host;
    }
}

I think this is a better way to handle this as you are able to keep all the specialized host creation logic to the second method. For example, lets add the events from the first example:

public class MyCustomHost : ServiceHostFactory
{
    public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
    {
        string serviceType = 
            string.Format("{0}, {1}", constructorString,
            constructorString.Substring(0, constructorString.IndexOf(".")));

        Type t =Type.GetType(serviceType);
        
        return this.CreateServiceHost(t, baseAddresses);
    }

    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        ServiceHost host = new ServiceHost(serviceType, baseAddresses);

        host.Opening += host_Opening;
        host.Opened += host_Opened;
        host.Faulted += host_Faulted;
        return host;
    }

    void host_Faulted(object sender, EventArgs e)
    {
        // Some logic...
    }

    void host_Opened(object sender, EventArgs e)
    {
        // Some logic...
    }

    void host_Opening(object sender, EventArgs e)
    {
        // Some logic...
    }
}

See, nothing to it!

All that's left is hook this up to our SVC file.

Unfortunately in Visual Studio 2008 it can be a little difficult to edit the SVC file. Double clicking it brings up the code behind file and not the SVC file. To get to that file, right click the file in the Solution Explorer and select "Open with..." Select "XML Editor" from the list and you should see something that looks like this:

<%@ ServiceHost Language="C#" Debug="true" Service="CustomHostExample.Service1" CodeBehind="Service1.svc.cs" %>

To add our service host, we just add a Factory attribute that identifies our service host:

<%@ ServiceHost Language="C#" Debug="true" Service="CustomHostExample.Service1" 
    CodeBehind="Service1.svc.cs" Factory="CustomHostExample.MyCustomHost" %>

Save the SVC file and that's it! You just customized your IIS runtime environment for you WCF service.

Print | posted on Tuesday, May 06, 2008 9:32 PM

Feedback

# re: Pimp Your WCF Runtime - Episode One

left by Adrian Stovold at 6/20/2008 5:35 AM Gravatar
I've seen several variations on this approach, but for some reason none of them worked in my project. Fortunately, yours did! Thanks for posting.
Title  
Name
Email (never displayed)
Url
Comments   
Please add 7 and 7 and type the answer here: