Tuesday, July 12, 2011

Calling WCF Web Services from a SharePoint Timer Job

Imagine that you are building an enterprise application on top of SharePoint 2010, which is installed on a multi-server farm. The application consumes a WCF web service – custom libraries use generated proxy classes and endpoint configuration is stored inside of a web application’s web.config file. Configuration settings are applied to all servers in the farm by a script when application is provisioned. And your application needs to be deployed to development, testing and production farms, all of which have differences in how WCF endpoints and bindings are configured, including variance in WCF binding types.

Now imagine that you need to call this web service from two places: from your custom web application code and from a timer job, perhaps because you need to cache results for better performance, but also be able to fall back to synchronous call when results get outdated. You will face a complication: how do you configure your WCF client when you invoke the service from a timer job?

You have a few options:

1. Implement a configuration file OWSTIMER.exe.config;

2. Construct binding and endpoint objects inside of a timer job, set their properties through code then create a channel or extended ClientBase<T> object and execute a service method call on it.

3. Load service model configuration from application’s web.config file and create and populate appropriate binding and endpoint objects. Then create and use a channel or a ClientBase<T> object to invoke the service method.

Option 1 has issues with provisioning files to locations not intended for custom application files and keeping same configuration information in two locations on all farm servers.

Option 2 hard-codes binding information, which makes it very difficult to maintain code and troubleshoot WCF issues in multiple environments.

Option 3 is apparently the best choice since configuration is stored in one place, it can easily be changed in web.config, and the changes will affect WCF service client objects in both locations. So let us look at what’s involved in implementing the option 3.

Before you can load a configuration object from web.config file you need to locate it. Here we can leverage SPIisSettings object, which is available for each security zone. Next you load web.config content by using WebConfigurationManager.OpenMappedConfiguration() method:


private System.Configuration.Configuration GetWebConfig(SPUrlZone zone)
{
SPIisSettings iisSettings = this.WebApplication.IisSettings[zone];
string rootPath = iisSettings.Path.ToString();
WebConfigurationFileMap map = new WebConfigurationFileMap();
map.VirtualDirectories.Add(
"/",
new VirtualDirectoryMapping(rootPath, true));
System.Configuration.Configuration webConfig =
WebConfigurationManager.OpenMappedWebConfiguration(
map,
"/web.config");

return webConfig;
}

Once you have obtained configuration object, you need to infer type of binding from service model configuration, and apply all attributes to it, as well as create an endpoint. Given names of a binding and an endpoint you find corresponding BindingCollectionElement and ChannelEndpointElement. The actual binding object is created using .NET Reflection and value of BindingCollectionElement.BindingType property:

private MyServiceClient MakeMyServiceClient(
System.Configuration.Configuration config)
{
//
// Get endpoint and binding names from settings.
//

string bindingName = GetValue("Key_MyBindingName");
string endpointName = GetValue("Key_MyEndpointName");

//
// Determine endpoint and binding elements used.
//

var sectionGroup = ServiceModelSectionGroup.
GetSectionGroup(config);
ChannelEndpointElement endpointElement = null;

for (int i = 0; i < sectionGroup.Client.Endpoints.Count; ++i)
{
if (sectionGroup.Client.Endpoints[i].Name == endpointName)
{
endpointElement = sectionGroup.Client.Endpoints[i];
break;
}
}

BindingCollectionElement collectionElement = sectionGroup.
Bindings.BindingCollections.Find(
item => item.BindingName == endpointElement.Binding);
IBindingConfigurationElement bindingConfig = new
List<IBindingConfigurationElement>(
collectionElement.ConfiguredBindings).Find(item =>
item.Name == endpointElement.BindingConfiguration);

//
// Create address and binding of proper type and populate them.
//

Binding binding = (Binding)collectionElement.BindingType.
GetConstructor(new Type[0]).Invoke(new object[0]);
bindingConfig.ApplyConfiguration(binding);
EndpointAddress address = new EndpointAddress(
endpointElement.Address);

MyServiceClient client = new MyServiceClient(binding, address);
return client;
}

That’s it. The credit for MakeMyServiceClient() method goes to Microsoft. When I was searching for examples of inferring binding type and properties from configuration I bumped into implementation of a read-only property named Binding on an internal type Microsoft.SharePoint.Administration.Claims.SPSecurityTokenServiceApplication inside of Microsoft.SharePoint assembly. I have reused that code with minor deviations in the example above. Open up Reflector and take a look at that property.