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

Custom WCF Web Service Startup Method in Host Agnostic Environment

5.00/5 (4 votes)
24 Dec 2014CPOL2 min read 42.1K  
Implementing WCF behavior extension to run code on service launch

Introduction

There are many different real-world scenarios requiring a web service warm-up, i.e., run custom code once the service is launched and don't on subsequent requests servicing. An obvious example is IoC container initialization.

Background

Currently, there are many ways to host a WCF service, such as Windows Service Self-Hosting, IIS Web Application, Windows Process Activation, local development/debug using Visual Studio Development Server or IIS Express, and fortunately many others.

The good news is almost each of them supports custom service warm-up technique, and the bad news is each supports its own way to do this.

The Configuration

The solution I've developed is pretty straight forward and based on the built-in WCF functionality to support various extension points such as custom service host, custom endpoints, behavior extensions, etc. My one is about to implement a custom behavior extension.

As I far as I can see, WCF always (or almost always) provides two ways to achieve the same goal: using code, usually attribute decoration, or using configuration. As a person with strong enterprise background having experience working with leviathan-like business applications controlled by enormous configuration layer, I still like configuration way. It just requires additional attention in details. But code does just the same. So below, I will use configuration where possible.

The entry point to the implementation is service-side Web.config to declare behavior extension section and extension itself:

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="serviceInitialization"
             type="Project.Services.ServiceInitializationBehaviorSection, Project.Core" />
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceInitialization type="Project.Services.ServiceInitialization, Project.Services" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration> 

As you can observe, section attribute name value corresponds to section opening tag name (e.g. serviceInitialization).

The Code

Now, we need to write an in-code representation of the section using WCF Configuration API and implement BehaviorExtensionElement abstract class:

C#
namespace Project.Services
{
    public class ServiceInitializationBehaviorSection : BehaviorExtensionElement
    {
        private readonly Lazy<ConfigurationPropertyCollection> _properties =
            new Lazy<ConfigurationPropertyCollection>(() =>
            {
                return new ConfigurationPropertyCollection
                {
                    new ConfigurationProperty("type",
                                              typeof(string),
                                              String.Empty,
                                              null,
                                              new StringValidator(Int32.MinValue,
                                                                  Int32.MaxValue,
                                                                  null),
                                              ConfigurationPropertyOptions.IsRequired)
                };
            });

        public override Type BehaviorType
        {
            get { return typeof(ServiceInitializationBehavior); }
        }

        [StringValidator(MinLength = Int32.MinValue,
                         MaxLength = Int32.MaxValue,
                         ConfigurationProperty("type"),
                         DefaultValue = "")]
        public string InitializationType
        {
            get { return (string)base["type"]; }
            set { base["type"] = value; }
        }

        protected override ConfigurationPropertyCollection Properties
        {
            get { return _properties.Value; }
        }

        public override void CopyFrom(ServiceModelExtensionElement from)
        {
            base.CopyFrom(from);

            var element = (ServiceInitializationBehaviorSection)from;

            InitializationType = element.InitializationType;
        }

        protected override object CreateBehavior()
        {
            var type = Type.GetType(InitializationType);
            if (type == null)
                throw new InvalidOperationException
                    ("Can't get read type from initialization type given");

            var serviceInitialization = Activator.CreateInstance(type) as IServiceInitialization;
            if (serviceInitialization == null)
                throw new InvalidOperationException
                    ("Can't create object from initialization type given");

            return new ServiceInitializationBehavior(serviceInitialization);
        }
    }
} 

and of behavior extension itself implementing IServiceBehavior interface:

C#
namespace Project.Services
{
    public class ServiceInitializationBehavior : IServiceBehavior
    {
        private readonly IServiceInitialization _serviceInitialization;

        public ServiceInitializationBehavior(IServiceInitialization serviceInitialization)
        {
            if (serviceInitialization == null)
                throw new ArgumentNullException("serviceInitialization");

            _serviceInitialization = serviceInitialization;    
        }

        public void AddBindingParameters(ServiceDescription serviceDescription,
                                         ServiceHostBase serviceHostBase,
                                         Collection<ServiceEndpoint> endpoints,
                                         BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
                                          ServiceHostBase serviceHostBase)
        {
            _serviceInitialization.Initialize();
        }

        public void Validate(ServiceDescription serviceDescription,
                             ServiceHostBase serviceHostBase)
        {
        }
    }
} 

The logic is simple: instantiate behavior extension and pass into its constructor an object specified in the configuration by FQN using the type attribute. This code will be run once on service launch, its configuration initialization.

Here is this custom interface declaration:

C#
namespace Project.Services
{
    public interface IServiceInitialization
    {
        void Initialize();
    }
}

and the article's key code - custom interface implementation:

C#
namespace Project.Services
{
    public class ServiceInitialization : IServiceInitialization
    {
        public void Initialize()
        {
            // your service warm-up logic here
        }
    }
}

History

  • 14/12/2012 - Version 1.0 - Initial publication

License

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