Tuesday, September 3, 2013

How to programmatically configure WCF services and clients at runtime pt. 1.


Windows Communication Foundation (WCF) is a framework from Microsoft that helps developers build (web) services. WCF serves as an abstraction layer and allows the developer to focus on _what_ to build, not on all the how’s, if’s and but’s. Choosing transport, protocol and security is done later [config time].
In this post, we will look into how to configure WCF programmatically and postpone the choices to run-time. Note that the service needs to recycle in order to obtain new config settings.
One challenge we meet in software development is configurations sets that change from development, test and production environments. Getting all the endpoints and settings correct in each environment is time consuming and error prone.

One solution is to centralize the configuration and introduce environmental variables to the server images. Let us say that we create a windows server image and inject an appSettings value for environment e.g. development. And we also might want to add a value that can resolve the correct url’s for the services and endpoints needed in a given environment. Add these settings to the machine.config file of your server image, this will make the settings available to all services and applications running on the server.

<add key="Environment" value="Dev" >
<add key="UrlPrefix" value="Dev." >
var endpointUrl=String.Format(“http://{0}MyService.MyCompany.com/Service.svc”,ConfigurationManager.AppSettings[“UrlPrefix”]);

 
This neat little trick makes deployment easy and no configuration transformation is needed. However, you are still stuck with the wcf configuration from your development environment, unless you use config transformations. Config transformations are still a static approach, changing the settings means that you need to change the transformation and update the config file in your service and the service clients.
This is where we will start to play with WCF and configuring this runtime.

The service:

In this post we’ll only cover .svc hosted services. First you need to create a service host factory, which is responsible for creating the service host. During creation of the service host you can configure the service and the available endpoints.

Please note that the sample code uses the really poor man’s IoC (switch - new), in real life scenarios change this to your preferred IoC container. In my production version of this, I use a custom lightweight IoC container.

Service Host Factory

To be able  to configure the service at runtime you need to create a new service host factory. Do this by overriding ServiceHostFactory’s CreateServiceHost method. In this method you can control all aspects of the service you are hosting.
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            var serviceHost = new ServiceHost(serviceType, baseAddresses);
            var configurator = CreateConfigurator();
            configurator.ConfigureServiceHost(serviceHost, serviceType);
            SetBehavior(serviceHost);
            return serviceHost;
        }

First you create the service host, just ‘new up’ the WCF default implementation. We will add the endpoints to this later.

To prepare for more advanced scenarios in later posts we will create an interface to handle the configuration of the endpoints. Create an interface, IServiceConfiguration, with a method called ConfigureServiceHost.

After the endpoints have been added to the service host, we want to set some behavior for the service, like open and close timeout values.

Building the endpoints

Each endpoint has its own binding, all bindings share the same base type, Binding. This allows us to create an abstraction over this using an interface, IBindingBuilder.

var bindingTypes = ConfigurationManager.AppSettings["bindingTypes"].Split(';');
            var contractDescription = ContractDescription.GetContract(serviceType.GetInterfaces()[0]);
            contractDescription.Namespace = "http://demo.wcf.org/" + serviceType.Name;
            foreach (var bindingType in bindingTypes)
            {
                var bindingBuilder = GetBindingBuilder(bindingType);
                var binding = bindingBuilder.CreateBinding();
                var endpoint = CreateEndpoint(binding, new EndpointAddress(GetFormattedAddress(bindingType)), contractDescription);
                serviceHost.AddServiceEndpoint(endpoint);
            }
 
private static ServiceEndpoint CreateEndpoint(Binding binding, EndpointAddress address, ContractDescription cd)
        {
            return new ServiceEndpoint(cd, binding, address);
        }

 Create one binding builder for each endpoint type you need your service to support. In this sample, I have created the BasicHttpBinding and WSHttpBinding as MSMQ and NET.TCP is not supported in IIS Express or the Web Dev Server.

class BasicBindingBuilder : IBindingBuilder
    {
        public Binding CreateBinding()
        {
            return new BasicHttpBinding
            {
                Name = "basic",
                AllowCookies = false,
                HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
                MaxBufferPoolSize = int.MaxValue,
                MaxReceivedMessageSize = int.MaxValue,
                MessageEncoding = WSMessageEncoding.Mtom,
                TextEncoding = Encoding.UTF8,
                ReaderQuotas = XmlDictionaryReaderQuotas.Max,
                BypassProxyOnLocal = true,
                UseDefaultWebProxy = false
            };
        }
    }

 

public Binding CreateBinding()
        {
            return new WSHttpBinding(SecurityMode.None)
                       {
                           Name = "ws",
                           AllowCookies = false,
                           TransactionFlow = false,
                           HostNameComparisonMode = HostNameComparisonMode.StrongWildcard,
                           MaxBufferPoolSize = int.MaxValue,
                           MaxReceivedMessageSize = int.MaxValue,
                           MessageEncoding = WSMessageEncoding.Text,
                           TextEncoding = Encoding.UTF8,
                           ReaderQuotas = XmlDictionaryReaderQuotas.Max,
                           BypassProxyOnLocal = true,
                           UseDefaultWebProxy = false
                       };
        }

 

The values to configure the bindings with can be read from a configuration store. I’ll post a simple version of a centralized configuration store later.

Bringing it all together

To enable the service to be configured by our new factory we need to add one simple statement to the svc markup (marked in red).

<%@ ServiceHost Language="C#" Debug="true" Service="Syrstad.Blog.WcfDemo.Service.DemoService" CodeBehind="DemoService.svc.cs" Factory="Syrstad.Blog.WcfDemo.WcfConfigurationModule.HostFactory" %>

When the development team is building both the service and the client(s) it is a good thing to extract the interface and the data classes in a separate assembly for easy distribution, through the build system, to the projects that depend on it.

Configuring the client

The client also needs to be configured in the same way. We want to ensure that resources used by WCF are properly cleaned, so we will create a container for the proxy that we make disposable.

For ease of use we will create a factory for client proxy containers, called ClientProxyFactory. This has one static method that returns an initialized instance of the container. We implement the container as a generic and uses the type parameter is the interface used in the service itself.

public static class ClientProxyFactory
    {
        public static ServiceClientContainer CreateProxy()
        {
            //Replace with your IoC of choice
            return new ServiceClientContainer().Initialize();
        }
    }

The service container creates a new channel factory with the appropriate settings collected from the interface and the config store. This factory is used to create the actual proxy. The factory is kept as a instance field so it will be a part of the dispose.

For binding creation, we reuse the BindingFactory used by the service so that there is no possibility for configuration mismatch.

internal ServiceClientContainer()
        {
            ServiceName = typeof(T).Name;
            BindingType = ConfigurationManager.AppSettings["useClientBinding"];
            Url = AddressHelper.GetFormattedAddress(BindingType);
        }
 
        public ServiceClientContainer Initialize()
        {
            ClientFactory = CreateChannelFactory(ServiceName, Url);
            return this;
        }
 
        internal ChannelFactory CreateChannelFactory(string servicename, string uri)
        {
            var builder = GetProxyBuilder();
            var channelFactory = new ChannelFactory(builder.GetServiceBinding(servicename, uri, BindingType));
            foreach (OperationDescription op in channelFactory.Endpoint.Contract.Operations)
            {
                var dataContractBehavior = op.Behaviors[typeof(DataContractSerializerOperationBehavior)] as DataContractSerializerOperationBehavior;
                if (dataContractBehavior != null)
                {
                    dataContractBehavior.MaxItemsInObjectGraph = int.MaxValue;
                }
            }
            return channelFactory;
        }
 
        private static IProxyBindingBuilder GetProxyBuilder()
        {
            return new ProxyBindingBuilder();
        }

Next we need a GetClient method that creates the proxy and returns it to the client code.

public T GetClient()
        {
            if (Client == null) Client = ClientFactory.CreateChannel(new EndpointAddress(Url));
            return Client;
        }

This approach makes it easy and clean to create and use runtime generated WCF proxies and ensure proper disposal of the underlying resources.

Download the demo solution and play with WCF. I apologize for the use of regions in some of the source files; they are added as an effort to make this post and the code easier to follow.

Please note that I have done some code cleaning (separated some code into new methods) after I wrote this post.


 The demo solution

Project
Description
Syrstad.Blog.WcfDemo
Contains WCF Service interface and data transfer objects for the demo
Syrstad.Blog.WcfDemo.Client
The demo client
Syrstad.Blog.WcfDemo.Service
The demo service
Syrstad.Blog.WcfDemo.WcfConfigurationModule
The WCF configuration code. This will be updated in future posts to include centralized config store and more binding builders

 

No comments: