Welcome to NetFxFactory Sign in | Join | Help

Papers

An effective way to access WCF services

Introduction

This document introduces the concept of leveraging a service metadata in order to retrieve at runtime its endpoint configuration therefore avoiding duplicating the configuration information on both service and all its clients.

If you ever had to deal with WCF, the first thing you probably heard about was "ABC" where A stands for Address, B for Binding and C for Contract. Well, thanks to the SmartChannelFactory, client side you are only required to known A and C because B is inferred from the service metadata.

Concept

WCF offers a very seamless way to handle service end point configuration. This is done via both the service configuration file and the client's. Specially in an intranet and even more in development environment, where you have to take care of both the cook and his clients, maintaining each and every client up to date with the latest service config can be quite tedious specially if you have a large number of clients to update. One way to overcome this problem is to retrieve the service's metadata in order to infer the services endpoints configuration instead of deploying via configuration file on each client.

This is achieved via the MetadataResolver.Resolve(...) method, I won't drill in too much details about this method, the important thing to remember is that giving a service's wsdl address you get a ServiceEndpointCollection corresponding to the endpoints configuration defined in the service's config file. Thanks to this method, clients no longer requires to know at design time the exact configuration necessary to dialog with a WCF service. Only two pieces of information are required to start "talking" to a service. The first one is obviously the contract. Along with the contract, you will also need to know the address of the service; be careful here the address required here is not the endpoint address which is provided in the metadata but the address of the service's WSDL. Indeed, this WSDL document contains all the info required to configure the client endpoint(s).

Global architecture

Figure 1 bellow presents briefly the conceptual overview behind SmartChannelFactory as well as all the actors involved. As you can easely see each application has its own ServiceEndpointCollection repository and is in charge of requesting the metadata from the service and then ensures that the cached information is in sync with the service. This is done via polling at a defined interval rate (see "Service endpoint cache management" section for more details about the caching policy implemented). The client then uses the information straight from the repository in order to create dynamic proxy.

Figure 1: Conceptual overview

The next diagram shows an implementation overview of SmartChannelFactory. A couple of interesting things to note from this diagram:

  • SmartChannelFactory's GetBestEndPoint method is virtual allowing to provide custom Endpoint sorting mechanism,
  • ServiceEndPointCollectionRepository is a static class,
  • ServiceEndPointCollectionRepository background worker is used to poll and invalidate the cache if needs be.
  • Two events can invalidate the cache; first if the service is no longer accesible then the wsdl timestamp do not match (see "Service endpoint cache management" section about wsdl timestamp).

Figure 2: Implementation diagram

MetadataResolver

The MetadataResolver class allows retrieving and importing service's metadata as a collection of ServiceEndPoint. The Resolve method, responsible for returning the ServiceEndpointCollection offers several signatures, the one I am interested in here takes 4 parameters described bellow:

If you look at the ServiceEndpointCollectionRepository.Resolve method, you may wonder why did I added the following line:

System.Net.WebRequest.Create(serviceMexAddress).GetResponse();

As a matter of fact, this single line allows me to test if the service is available at the address provided, the exception catched allows me to handle nicely this eventuality.

Best endpoint selection

Some time ago, I wrote a post dealing with the same problem. This time the approach is a little different in the way the best endpoint is selected for a particular situation.


Two things are important here:

  • First, the comparer Network.CompareServiceEndpointByZone which sorts the ServiceEndpoints based on the client's securityzone relative to the service
  • Then, the virtual method GetBestEndPoint allows you to define your own sorting algorithm if required.

The code is pretty self explanatory but here is a short pseudo code description of what happens inside GetBestEndPoint:

Foreach serviceEndPoint in ServiceEndPointCollectionRetreivedFromServiceMetadata 
Get the serviceEndPoint's Uri 
Get the serviceEndPoint's Security Zone based on its Uri structure 
Add current serviceEndPoint the list of ServiceEndPoint if Securityzone is relevant 
Sort the list of ServiceEndPoint using custom comparer 
return 1st item of the list if any.

Service endpoint cache management

Because WCF does not force in anyway (and it is not its job) a caching mechanism of the WSDL document and because we cannot rely only on the fact that IIS can cache such a document, we need some kind of flag in the wsdl document that will let us know if the document has changed.

Wsdl limitation

WSDL standard offers no built-in way to timestamp the generated description document. This fact is quite anoying if you want a way to invalidate, client side, a cached representation of the ServiceEndPoint configuration. The idea here is on server side to plug-in custom code within the wsdl document generation process in order to insert a custom timestamp. Then client side, the ServiceEndPoints configuration client side along with the WSDL's timestamp is cached. Then using a background worker, the repository will check regularly if the server side wsdl timestamp changed in order to invalidate client's cache and retrieve the new ServiceEndPoints configuration if any.

Workaround (server side)

The workaround to add a timestamp to the wsdl is to cheat and use the documentation section of the wsdl. It is really easy to inject custom documentation to the wsdl generated thus using a custom section to define the timestamp is child play.

Here are the 2 steps required to do so:

1) implement IWsdlExportExtension

2) implement IContractBehavior

The IWsdlExportExtension interface exposes 2 methods, only one of them is interesting to us here, the method is called ExportContract. This method allows us to inject into the documentation section of the wsdl a custom xml.

In our case the following section is added:

<wsdl:documentation>
  <rd:contractWsdlTimestamp 
    timeStamp="2006-10-11 09:44:47Z" xmlns:rd="http://schema.rioterdecker.net/ServiceModel/Wsdl" />
</wsdl:documentation>

The other method exposed by the IWsdlExportExtension allows customizing the wsdl at Endpoint level which is no use to us in this case.

The other interface, IContractBehavior, exposes 4 methods, none of them actually requires custom code to be written only the inheritance is required (you'll understand why a little bit further down this post).

At this stage, you may wonder, well that all good but how do I add this functionality to my service? The answer is quite simple as well. Seat back, relax and think about what you see more and more often in .Net code... Open up Reflector, browse quickly a system*.dll assembly and you'll see several of them. OK you want a clue, here you go: ServiceContract, OperationContract, DataContract. Now you got it, attributes of course. The trick is to create a custom attribute that implements those interfaces and add it to the service Contract. You end up with something looking like this:

[ServiceContract()]
[WsdlContractTimestamp()] 
public interface IService1 
{ 
  [OperationContract]
  string MyOperation1(string myValue); 
  [OperationContract] 
  string MyOperation2(DataContract1 dataContractValue); 
} 

Just by adding this custom attribute, you extend the wsdl generated in order to add a timestamp. Can't get much simpler...

Client side usage

Now that the wsdl generated for your service includes a timestamp, you need to extract it from the wsdl in an easy way. To do so, before importing the contract from the wsdl I add my custom importer to the WsdlImportExtensions collection. This custom importer is able to read the timestamp information out of the wsdl.

So what does this WsdlContractTimestampImporter class? Well remember what we did to add the timestamp info... You got it, IWsdlExportExtension so we implemented the Ying, now we do the Yang: IWsdlImportExtension.

IWsdlImportExtension exposes 3 methods: BeforeImport, ImportContract and ImportEndPoint. Same deal as we did server side, we only need to implement the ImportContract method to extract the timestamp info. The code is very simple so no need to drill in further. Am sure you're wondering, well how do I access this info? And why did we had to use the IContractBehavior interface server side? Eheh, in the ImportContract method you just have to extract the timestamp info out of the documentation section of the WSDL and then add it to the contract's behavior via the ImportContract context parameter (remember that the Wsdl timestamp attibute added to the contract inherits from IcontractBehavior). From this point, the timestamp info is accessible via the contract's behaviors whereever in your client code.

The idea, client side, is to cache in a dictionary the service's uri (used as the key) and an object storing the timestamp and the associated ServiceEndpointCollection as the dictionary value. This allows retrieving the serviceEndpoints for a particular service thus not calling the MetadataResolver.Resolve each time. The DateTime key used in the dictionary value is the wsdl timestamp that is used to invalidate the cache entry if the timestamps do not match.

Note: A simple way to save service resources once in production is to generate the wsdl once and save it as a static page and then let clients use this page as the service's metadata source. This avoids consuming service resources to generate the wsdl document over and over; however the drawback is the overhead to handle correctly the disposal of the static page in order to maintain metadata up to date.

Transaction management

WCF supports natively WS-AtomicTransaction (WS-AT) protocol through a couple of attributes at contract and service level (yet again) as well as binding configuration in order to allow transaction flow. By implementing the IEnlistmenentNotification interface, the SmartChannelFactory is able to participate actively to the transaction flow in order to take actions based, for instance, on CommunicationState or business logic. For example, if the client is closing the communication and there is an ambient transaction active then the transaction must be rolled back because the client won't be able to honor a commit order anyway, thus throwing an explicit exception will warn the user of the problem and trigger the transaction's rollback mechanism.

Sample code

Uri __baseAddress = new Uri(http://localhost:8081/service1?wsdl); 
SmartChannelFactory<IService1> __channelFactory = new SmartChannelFactory<IService1>(__baseAddress)); 
IService1 _IService1Proxy = __channelFactory.CreateChannel(); 
String __result = _IService1Proxy.MyOperation1(DateTime.Now.ToString()); Console.WriteLine(__result);

and the same call but with the wsdl address stored in the app.config file

SmartChannelFactory<IService1> __channelFactory = new SmartChannelFactory<IService1>()); 
IService1 _IService1Proxy = __channelFactory.CreateChannel(); 
String __result = _IService1Proxy.MyOperation1(DateTime.Now.ToString()); Console.WriteLine(__result); 

Going further

With the Longhorn wave rolling-in and specially WCF, MS concretize widespread demand of dynamic proxy generation from wsdl, thus avoiding the necessary manual step of proxy generation in order to be able to communicate with any web service. Far from just solving this issue, MS leverages the .Net 2.0 generics to offer a strongly typed proxy factory. Can the old school react to this new breach? Anyway, we won't leave this flow unaddressed; check out the ProxyFactory project up on Codeplex.You'll probably see some similarities…

Furthermore, with the proliferation announced by the simplicity of creating WCF services it will become crucial to provide a way to catalog and cartography existing services available in order to prop up service reusability. This can be achieved in numerous ways, the most widely known is to use a UDDI server to host the service description and categorization. The inquiring would apply to the SmartChannelFactory where the publishing would apply at service host level. So stay tuned to as this subject will be dealt with soon.

A direction of improvement would be to provide a custom extended service host that would take care of the generation of each service's wsdl as well as the creation of the static page exposing the metadata for production environment and finally its disposal one required..

Wrap-up

Here is a small wrap-up of what has been covered here:

  • How to use server side configuration to infer client's using the MetadataResolver class,
  • How to select an endpoint based on the service address,
  • How to customize service wsdl,
  • How to execute custom actions on transaction events

Downloads

Requires NetFX3.0 RTM

Pdf version is here

Source code and sample application are here.

Sample video

 

Format: wmv
Duration: 3:23

Published Wednesday, January 24, 2007 3:33 AM by admin
Filed under: , ,

Attachment(s): Figure 1 - Conceptual overview.png

Comments

 

spencer205 said:

Where's the sound?  I've tried it on two computers using WM10 and WM11.  Is there really no sound?

March 8, 2008 5:06 PM
 

tmanthey said:

Thanks for the Article, but what I can definately say so far is that using the classes MetadataExchangeClient or MetadataResolver causes havoc as soon as your WSDL becomes larger than 64k. Both classes will fail in this case with MetadataExchangeClient provides the very misleading message that some items can not be resolved.

Definately make sure you provide the binding for MetadataExchangeClient as described here:

http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/f1ad96bf-c5a0-4e6f-a357-0957d32cf2e5/

December 24, 2008 7:42 PM
Anonymous comments are disabled

This Blog

Syndication

Powered by Community Server (Personal Edition), by Telligent Systems