Section 2.4. Contract Queries


2.4. Contract Queries

Sometimes the client needs to programmatically verify whether a particular endpoint (identified by its address) supports a particular contract. For example, imagine an application where the end user specifies or configures the application during setup (or even at runtime) to consume and interact with a service. If the service does not support the required contracts, the application should alert the user that an invalid address was specified, and ask for an alternative or a correct address. For example, the Credentials Manager application used in Chapter 10 has just such a feature: the user needs to provide it with the address of the security credentials service that manages accounts membership and roles. Credentials Manager only allows the user to select a valid address, after verifying the address supports the required service contracts.

2.4.1. Programmatic Metadata Processing

In order to support such functionality, the application needs to retrieve the metadata of the service endpoints and see if at least one of the endpoints supports the requested contract. As explained in Chapter 1, the metadata is available in special metadata exchange endpoints that the service might support or over the HTTP-GET protocol. When you use HTTP-GET, the address of the metadata exchange is the HTTP-GET address (usually just the base address of the service suffixed by ?wsdl). To ease the task of parsing the returned metadata, WCF offers a few helper classes, available in the System.ServiceModel.Description namespaces, as shown in Example 2-7.

Example 2-7. Metadata processing supporting types

 public enum MetadataExchangeClientMode {    MetadataExchange,    HttpGet } class MetadataSet : ... {...} public class ServiceEndpointCollection : Collection<ServiceEndpoint> {...} public class MetadataExchangeClient {    public MetadataExchangeClient( );    public MetadataExchangeClient(Binding mexBinding);    public MetadataSet GetMetadata(Uri address,MetadataExchangeClientMode mode);    //More members } public abstract class MetadataImporter {    public abstract ServiceEndpointCollection ImportAllEndpoints( );    //More members } public class WsdlImporter : MetadataImporter {    public WsdlImporter(MetadataSet metadata);    //More members } public class ServiceEndpoint {    public EndpointAddress Address    {get;set;}    public Binding Binding    {get;set;}    public ContractDescription Contract    {get;}    //More members } public class ContractDescription {    public string Name    {get;set;}    public string Namespace    {get;set;}    //More members } 

MetadataExchangeClient can use the binding associated with metadata exchange in the application config file. You can also provide the constructor MetadataExchangeClient with an already initialized binding instance that has some custom values, such as a capacity for larger messages if the metadata returned exceeds the default received message size. The GetMetadata( ) method of MetadataExchangeClient accepts an endpoint address instance, wrapping the metadata exchange address as well as an enum specifying the access method, and returns the metadata in an instance of MetadataSet. You should not work with that type directly. Instead, instantiate a subclass of MetadataImporter such as WsdlImporter and provide the raw metadata as a construction parameter, and then call the ImportAllEndpoints( ) method to obtain a collection of all endpoints found in the metadata. The endpoints are represented by the ServiceEndpoint class.

ServiceEndpoint provides the Contract property of the type ContractDescription. ContractDescription provides the name and namespace of the contract.

Using HTTP-GET, to find out if a specified base address supports a particular contract, follow the steps just described yielding the collection of endpoints. For each endpoint in the collection, compare the Name and Namespace properties in the ContractDescription with the requested contract, as shown in Example 2-8.

Example 2-8. Querying an address for a contract

 bool contractSupported = false; string mexAddress = "...?WSDL"; MetadataExchangeClient MEXClient = new MetadataExchangeClient(new Uri(mexAddress),                                                MetadataExchangeClientMode.HttpGet); MetadataSet metadata =  MEXClient.GetMetadata( ); MetadataImporter importer = new WsdlImporter(metadata); ServiceEndpointCollection endpoints = importer.ImportAllEndpoints( ); foreach(ServiceEndpoint endpoint in endpoints) {    if(endpoint.Contract.Namespace == "MyNamespace" &&       endpoint.Contract.Name == "IMyContract")    {       contractSupported = true;       break;    } } 

The Metadata Explorer tool presented in Chapter 1 follows steps similar to Example 2-8 to retrieve the service endpoints. When given an HTTP-based address, the tool tries both HTTP-GET and an HTTP-based metadata exchange endpoint. The Metadata Explorer can also retrieve the metadata using a TCP- or IPC-based metadata exchange endpoint. The bulk of the implementation of the tool was in processing the metadata and rendering it because the difficult task of retrieving and parsing the metadata is done by the WCF-provided classes.


2.4.2. The MetadataHelper Class

I encapsulated and generalized the steps on Example 2-8 in my general-purpose static utility class called MetadataHelper in the QueryContract( ) method:

 public static class MetadataHelper {    public static bool QueryContract(string mexAddress,Type contractType);    public static bool QueryContract(string mexAddress,string contractNamespace,                                                               string contractName);    //More members } 

You can provide MetadataHelper with either the Type of the contract you wish to query for or with the name and namespace of the contract:

 string address = "..."; bool contractSupported = MetadataHelper.QueryContract(address,typeof(IMyContract)); 

For a metadata exchange address, you can provide MetadataHelper with an HTTP-GET address, or a metadata exchange endpoint address over HTTP, HTTPS, TCP, or IPC. Example 2-9 shows the implementation of MetadataHelper.QueryContract( ), with some of the error handling code removed.

Example 2-9. Implementing MetadataHelper.QueryContract( )

 public static class MetadataHelper {   const int MessageMultiplier = 5;   static ServiceEndpointCollection QueryMexEndpoint(string mexAddress,                                                     BindingElement bindingElement)   {      CustomBinding binding = new CustomBinding(bindingElement);      MetadataExchangeClient MEXClient = new MetadataExchangeClient(binding);      MetadataSet metadata = MEXClient.GetMetadata                                                 (new EndpointAddress(mexAddress));      MetadataImporter importer = new WsdlImporter(metadata);      return importer.ImportAllEndpoints();   }   public static ServiceEndpoint[] GetEndpoints(string mexAddress)   {      /* Some error handling */      Uri address = new Uri(mexAddress);      ServiceEndpointCollection endpoints = null;      if(address.Scheme == "net.tcp")      {         TcpTransportBindingElement tcpBindingElement =                                                  new TcpTransportBindingElement();         tcpBindingElement.MaxReceivedMessageSize *= MessageMultiplier;         endpoints = QueryMexEndpoint(mexAddress,tcpBindingElement);      }      if(address.Scheme == "net.pipe")      {...}      if(address.Scheme == "http")  //Checks for HTTP-GET as well      {...}      if(address.Scheme == "https") //Checks for HTTPS-GET as well      {...}      return Collection.ToArray(endpoints);   }   public static bool QueryContract(string mexAddress,Type contractType)   {      if(contractType.IsInterface == false)      {         Debug.Assert(false,contractType + " is not an interface");         return false;      }      object[] attributes = contractType.GetCustomAttributes(                                           typeof(ServiceContractAttribute),false);      if(attributes.Length == 0)      {         Debug.Assert(false,"Interface " + contractType +                         " does not have the ServiceContractAttribute");         return false;      }      ServiceContractAttribute attribute = attributes[0] as                                                          ServiceContractAttribute;      if(attribute.Name == null)      {         attribute.Name = contractType.ToString( );      }      if(attribute.Namespace == null)      {         attribute.Namespace = "http://tempuri.org/";      }      return QueryContract(mexAddress,attribute.Namespace,attribute.Name);    }    public static bool QueryContract(string mexAddress,string contractNamespace,                                                                string contractName)    {       if(String.IsNullOrEmpty(contractNamespace))       {          Debug.Assert(false,"Empty namespace");          return false;       }       if(String.IsNullOrEmpty(contractName))       {          Debug.Assert(false,"Empty name");          return false;       }       try       {          ServiceEndpoint[] endpoints = GetEndpoints(mexAddress);          foreach(ServiceEndpoint endpoint in endpoints)          {             if(endpoint.Contract.Namespace == contractNamespace &&                endpoint.Contract.Name == contractName)             {                return true;             }          }       }       catch       {}       return false;    } } 

In Example 2-9, the GetEndpoints() method parses out the schema of the metadata exchange address. According to the transport schema found (such as TCP), GetEndpoints() constructs a binding element to use so that it could set its MaxReceivedMessageSize property:

 public abstract class TransportBindingElement : BindingElement {    public virtual long MaxReceivedMessageSize    {get;set;} } public abstract class ConnectionOrientedTransportBindingElement :                                                         TransportBindingElement,... {...} public class TcpTransportBindingElement : ConnectionOrientedTransportBindingElement {...} 

MaxReceivedMessageSize defaults to 64K. While it is adequate for simple services, services that have many endpoints that use complex types will generate larger messages that will fail the call to MetadataExchangeClient.GetMetadata(). My experimentations indicate that 5 is an adequate fudge factor for most cases. GetEndpoints() then uses the QueryMexEndpoint() private method to actually retrieve the metadata. QueryMexEndpoint() accepts the metadata exchange endpoint address and the binding element to use. It uses the binding element to construct a custom binding and provide it to an instance of MetadataExchangeClient, which retrieves the metadata and returns the endpoint collection. Instead of returning a ServiceEndpointCollection, GetEndpoints() uses my Collection helper class to return an array of endpoints.

The QueryContract( ) method that accepts a Type first verifies that the type is an interface and that it is decorated with the ServiceContract attribute. Because the ServiceContract attribute can be used to alias both the name and namespace of the requested type of contract, QueryContract( ) uses those for looking up the contract. If no aliasing is used, QueryContract( ) uses the name of the type and the default http://tempuri.org for the namespace, and calls the QueryContract( ) that operates on the name and namespace. That version of QueryContract( )calls GetEndpoints() to obtain the array of endpoints and then it iterates over the array and returns TRue if it finds at least one endpoint that supports the contract. Any errors make QueryContract( ) return false.

Example 2-10 shows additional metadata querying methods offered by MetadataHelper.

Example 2-10. The MetadataHelper class

 public static class MetadataHelper {    public static ServiceEndpoint[] GetEndpoints(string mexAddress);    public static string[] GetAddresses(Type bindingType,string mexAddress,                                                                 Type contractType);    public static string[] GetAddresses(string mexAddress,Type contractType);    public static string[] GetAddresses(Type bindingType,string mexAddress,                                       string contractNamespace,string contractName)                                                                  where B : Binding;    public static string[] GetAddresses(string mexAddress,string contractNamespace,                                                               string contractName);    public static string[] GetContracts(Type bindingType,string mexAddress);    public static string[] GetContracts(string mexAddress);    public static string[] GetOperations(string mexAddress,Type contractType);    public static string[] GetOperations(string mexAddress,                                         string contractNamespace,                                         string contractName);    public static bool QueryContract(string mexAddress,Type contractType);    public static bool QueryContract(string mexAddress,                                     string contractNamespace,string contractName);    //More members } 

These powerful and useful features are often required during setup or in administration applications and tools, and yet the implementation of them is all based on processing the array of endpoints returned from the GetEndpoints( ) method.

The GetAddresses( ) methods return all the endpoint addresses that support a particular contract or only the addresses of the endpoints that also use a particular binding.

Similarly, GetContracts( ) returns all the contracts supported across all endpoints or the contracts supported across all endpoints that use a particular binding. Finally, GetOperations( ) returns all the operations on a particular contract.

Chapter 10 uses the the MetadataHelper class in the Credentials Manager application, and Appendix B uses it for administering persistent subscribers.





Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net