Metadata and WCF Essentials

In this fourth article in a five-part series covering the Windows Communication Foundation, you'll learn about the metadata exchange endpoint, the metadata explorer, and more. It is excerpted from chapter one of the book Programming WCF Services, written by Juval Lowy (O'Reilly, 2007; ISBN: 0596526997). Copyright © 2007 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 4 stars4 stars4 stars4 stars4 stars / 4
October 11, 2007
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

The Metadata Exchange Endpoint

The service can also publish its metadata over a special endpoint called the metadata exchange endpoint, sometimes referred to as the MEX endpoint. Figure 1-7 shows a service with business and a metadata exchange endpoint. However, you typically do not show the metadata exchange endpoint in your design diagrams.


Figure 1-7.  The metadata exchange endpoint

That endpoint supports an industry standard for exchanging metadata, represented in WCF by theIMetadataExchangeinterface:

  [ServiceContract(...)]
  public interface IMetadataExchange
  {
    
[OperationContract(...)]
    
Message Get(Message request);
    
//More members
 
}

The details of this interface are inconsequential. Like most of these industry standards, it is difficult to implement. Fortunately, WCF can have the service host automatically provide the implementation ofIMetadataExchangeand expose the metadata exchange endpoint. All you need to do is designate the address and the binding to use, as well as add the service metadata behavior. For the bindings, WCF provides dedicated binding transport elements for the HTTP, HTTPS, TCP, and IPC protocols. For the address, you can provide a full address or use any of the registered base addresses. There is no need to enable the HTTP-GET option, but there is no harm either. Example 1-12 shows a service that exposes three MEX endpoints, over HTTP, TCP, and IPC. For demonstration purposes, the TCP and IPC MEX endpoints use relative addresses and the HTTP one uses an absolute address.

Example 1-12. Adding MEX endpoints

<service name = "MyService" behaviorConfiguration = "MEX">
   <host>
    
 <baseAddresses> 
         <add baseAddress =
"net.tcp://localhost:8001/"/>
         <add baseAddress = "net.pipe://localhost/"/>
      </baseAddresses>
 
 </host>
   <endpoint
     
address  = "MEX"
      binding  = "mexTcpBinding"
     
contract = "IMetadataExchange"
   />
  
<endpoint
      address  = "MEX"
      binding  = "mexNamedPipeBinding" 
      contract = "IMetadataExchange"
  
/>
  
<endpoint
      address  = "http://localhost:8000/MEX"
      binding  = "mexHttpBinding"
    
contract  = "IMetadataExchange"
  
/>
</service>
<behaviors>
  
<serviceBehaviors>
      <behavior name = "MEX">
         <serviceMetadata/>
      </behavior>
  </serviceBehaviors>
</behaviors>

Adding MEX endpoints programmatically

Like any other endpoint, you can only add a metadata exchange endpoint programmatically before opening the host. WCF does not offer a dedicated binding type for the metadata exchange endpoint. Instead, you need to construct a custom binding that uses the matching transport binding element, and then provide that binding element as a construction parameter to an instance of a custom binding. Finally, call the AddServiceEndpoint() method of the host providing it with the address, the custom binding, and the IMetadataExchange contract type. Example 1-13 shows the code required to add a MEX endpoint over TCP. Note that before adding the endpoint you must verify the presence of the metadata behavior.

Example 1-13. Adding TCP MEX endpoint programmatically

BindingElement bindingElement = new TcpTransportBindingElement();
CustomBinding binding = new CustomBinding(bindingElement);

Uri tcpBaseAddress = new Uri("net.tcp://localhost:9000/");
ServiceHost host = new ServiceHost(typeof(MyService),tcpBaseAddress);

ServiceMetadataBehavior metadataBehavior; metadataBehavior = host.Description.Behaviors.Find <ServiceMetadataBehavior>();
if(metadataBehavior == null)

  
metadataBehavior = new ServiceMetadataBehavior();
 
host.Description.Behaviors.Add(metadataBehavior);
}
host.AddServiceEndpoint(typeof(IMetadataExchange),binding,"MEX"); host.Open();

Streamlining with ServiceHost<T>

You can extend ServiceHost<T> to automate the code in Examples 1-11 and 1-13. ServiceHost<T> offers the EnableMetadataExchange Boolean property that you can call to both add the HTTP-GET metadata behavior and the MEX endpoints:

  public class ServiceHost<T> : ServiceHost
  {
     public bool EnableMetadataExchange
     {get;set;}
     public bool HasMexEndpoint
     {get;}
     public void AddAllMexEndPoints();
     //More members
 
}

When set to true,EnableMetadataExchangeadds the metadata exchange behavior, and if no MEX endpoint is available,EnableMetadataExchangeadds a MEX endpoint for each registered base address scheme. UsingServiceHost<T>, Examples 1-11 and 1-13 are reduced to:

  ServiceHost<MyService> host = new ServiceHost<MyService>();
  host.EnableMetadataExchange = true;
  host.Open();

ServiceHost<T>also offers theHasMexEndpoint Boolean property, which returnstrueif the service has any MEX endpoint (regardless of transport protocol), and theAddAllMexEndPoints()method, which adds a MEX endpoint for each registered base address of the scheme type of HTTP, TCP, or IPC. Example 1-14 shows the implementation of these methods.

Example 1-14. Implementing EnableMetadataExchange and its supporting methods

public class ServiceHost<T> : ServiceHost
{
   public bool EnableMetadataExchange
   {
     
set
     
{
         if(State == CommunicationState.Opened)
         {
            throw new InvalidOperationException("Host is already opened");
         }
         ServiceMetadataBehavior metadataBehavior;
         metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>();
         if(metadataBehavior == null)
         {
           
metadataBehavior = new ServiceMetadataBehavior();
            metadataBehavior.HttpGetEnabled = value;
            Description.Behaviors.Add(metadataBehavior); 
        
}
         if(value == true)
         {
           
if(HasMexEndpoint == false)
            {
               AddAllMexEndPoints();
            }
        
}
      }
      get
      {
        
ServiceMetadataBehavior metadataBehavior;
         metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>();
         if(metadataBehavior == null)
         {
            return false;
         }
         return metadataBehavior.HttpGetEnabled;
      }
    }
    public bool HasMexEndpoint
    { 
      
get
       {
         Predicate<ServiceEndpoint> mexEndPoint= delegate(ServiceEndpoint endpoint)
            
{
               return endpoint.Contract.ContractType == typeof(IMetadataExchange);
             };
         return Collection.Exists(Description.Endpoints,mexEndPoint);
       }
    }
  public void AddAllMexEndPoints() 
  {
     Debug.Assert(HasMexEndpoint == false);

     foreach(Uri baseAddress in BaseAddresses)
     
{
       BindingElement bindingElement = null;
       switch(baseAddress.Scheme)
       { 
         
case "net.tcp":
        
{
           bindingElement = new TcpTransportBindingElement();
           break;
         }
         case "net.pipe":
         {...}
         case "http":
         {...}
         case "https":
         {...} 
     
}
      if(bindingElement != null)
      { 
       
Binding binding = new CustomBinding(bindingElement); 
        AddServiceEndpoint(typeof(IMetadataExchange),binding,"MEX");
      }
    }
  }
}

EnableMetadataExchangeverifies that the host has not been opened yet using theStateproperty of theCommunicationObjectbase class.EnableMetadataExchangedoes not override the configured value from the config file and will only set the value if no metadata behavior was found in the config file. When reading the value, the property checks if a value is configured. If no metadata behavior is configured at all,EnableMetadataExchangereturnsfalse, and if a behavior is configured, it simply returns itsHttpGetEnabledvalue. TheHasMexEndpointproperty uses an anonymous method* to initialize a predicate that checks if a given endpoint’s contract is indeedIMetadataExchange. The property then uses my staticCollection class and calls theExists()method, providing the collection of endpoints available with the service host.Exists()invokes the predicate on each item in the collection, and returnstrueif any one of the items in the collection satisfies the predicate (that is, if the invocation of the anonymous method returnedtrue), andfalseotherwise. TheAddAllMexEndPoints()method iterates over theBaseAddressescollection. For each

* If you are unfamiliar with anonymous methods, see my MSDN Magazinearticle “Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes,” May 2004.

base address found, it creates a matching MEX transport-binding element, creates a custom binding, and uses that, as in Example 1-13 to add the endpoint.

The Metadata Explorer

The metadata exchange endpoint provides metadata that describes not just contracts and operations, but also information about data contracts, security, transactions, reliability, and faults. To visualize the metadata of a running service I developed the Metadata Explorer tool, available along with the rest of the source code of this book. Figure 1-8 shows the Metadata Explorer reflecting the endpoints of Example 1-7. To use the Metadata Explorer, simply provide it with the HTTP-GET address or the metadata exchange endpoint of the running service to reflect the returned metadata.


Figure 1-8.  The Metadata Explorer

Client-Side Programming

To invoke operations on the service, the client first needs to import the service contract to the client’s native representation. If the client uses WCF, the common way of invoking operations is to use a proxy. The proxy is a CLR class that exposes a single CLR interface representing the service contract. Note that if the service supports several contracts (over at least as many endpoints), the client needs a proxy per contract type. The proxy provides the same operations as service’s contract, but also has additional methods for managing the proxy life cycle and the connection to the service. The proxy completely encapsulates every aspect of the service: its location, its implementation technology and runtime platform, and the communication transport.

Generating the Proxy

You can use Visual Studio 2005 to import the service metadata and generate a proxy. If the service is self-hosted, first launch the service and then select Add Service Reference… from the client project’s context menu. If the service is hosted in IIS or the WAS, there is no need to pre-launch the service. Interestingly enough, if the service is self-hosted in another project in the same solution as the client project, you can launch the host in Visual Studio 2005 and still add the reference, because unlike most project settings, this option is not disabled during a debug session (see Figure 1-9).


Figure 1-9.  Generate a proxy using Visual Studio 2005

This brings up the Add Service Reference dialog box, where you need to supply the base address of the service (or a base address and a MEX URI) and the namespace to contain the proxy.

Instead of Visual Studio 2005, you can use the SvcUtil.execommand-line utility. You need to provide SvcUtil with the HTTP-GET address or the metadata exchange endpoint address and, optionally, with a proxy filename. The default proxy filename is output.csbut you can also use the/outswitch to indicate a different name.

For example, if you’re hosting the serviceMyServicein IIS or the WAS, and have enabled metadata public sharing over HTTP-GET, simply run this command line:

  SvcUtil http://localhost/MyService/MyService.svc
/out:Proxy.cs

When you are hosting in IIS and selecting a port other than port 80 (such as port 81), you must provide that port number as part of the base address:

SvcUtil http://localhost:81/MyService/MyService.svc
/out:Proxy.cs

With self-hosting, assuming the self-hosted service enabled metadata publishing over HTTP-GET, registers these base addresses and exposes the matching metadata exchange endpoints with a relative address ofMEX:

  http://localhost:8002/
  net.tcp://localhost:8003
  net.pipe://localhost/MyPipe

After launching the host, you can use the following commands to generate the proxy:

  SvcUtil http://localhost:8002/MEX      /out:Proxy.cs
  SvcUtil
http://localhost:8002/         /out:Proxy.cs
  SvcUtil net.tcp://localhost:8003/MEX   /out:Proxy.cs
  SvcUtil
net.pipe://localhost/MyPipe/MEX
                               /out:Proxy.cs

The main advantage of using SvcUtil over Visual Studio 2005 is the numerous options it offers through switches for controlling the generated proxies, as you will see later in this book.

For this service definition:

  [ServiceContract(Namespace = "MyNamespace")]
  interface IMyContract
  {
    
[OperationContract]
    
void MyMethod();
  }
  class MyService : IMyContract
  {
    
public void MyMethod()
     {...}
  }

SvcUtil generates the proxy shown in Example 1-15. You can safely remove the settings ofActionandReplyActionin most cases, since the default of using the method name is good enough.

Example 1-15. Client proxy file

[ServiceContract(Namespace = "MyNamespace")] public interface IMyContract
{
   [OperationContract(Action = "MyNamespace/IMyContract/MyMethod",
                      ReplyAction = "MyNamespace/IMyContract/MyMethodResponse")]
   void MyMethod();
}

public partial class MyContractClient : ClientBase<IMyContract>,IMyContract
{
  
public MyContractClient()
   {}
   public MyContractClient(string endpointName) : base(endpointName)
   {}
   public MyContractClient(Binding binding,EndpointAddress remoteAddress) :
                base(binding,remoteAddress)
   {}
   /* Additional constructors */

   public void MyMethod()
   {
     
Channel.MyMethod();
   }
}

The most glaring aspect of the proxy class is that it has no reference to the service-implementing class, only to the contract exposed by the service. You can use the proxy in conjunction with a client-side config file that provides the address and the binding, or you can use it without a config file. Note that each proxy instance points at exactly one endpoint. The endpoint to interact with is provided to the proxy at construction time. As I mentioned previously, if the service-side contract does not provide a namespace, it will implicitly use the http://tempuri.orgnamespace.

Administrative Client Configuration

The client needs to know where the service is located and use the same binding as the service, and, of course, import the service contract definition. In essence, this is exactly the same information captured in the service’s endpoint. To reflect that, the client config file contains information about the target endpoints and even uses the same endpoint configuration schema as the host.

Example 1-16 shows the client configuration file required to interact with a service whose host is configured according to Example 1-6.

Example 1-16. Client config file

<system.serviceModel>
  
<client>
     
<endpoint name = "MyEndpoint"
          address  = http://localhost:8000/MyService/
          binding  = "wsHttpBinding"
          contract = "IMyContract"
     
/>
  
</client>
</system.serviceModel>

The client config file may list as many endpoints as the services it deals with support, and the client may use any one of them. Example 1-17 shows the client config file matching the host config file of Example 1-7. Note that each endpoint in the client config file has a unique name.

Example 1-17. Client config file with multiple target endpoints

<system.serviceModel>
   <client>
     
<endpoint name = "FirstEndpoint"
         address  = "http://localhost:8000/MyService/
         binding  = "wsHttpBinding"
         contract = "IMyContract"
      />
     
<endpoint name = "SecondEndpoint"
         address  = "net.tcp://localhost:8001/MyService/"
         binding  = "netTcpBinding"
         contract = "IMyContract"
     
/>
     
<endpoint name = "ThirdEndpoint"
         address  = "net.tcp://localhost:8002/MyService/"
         binding  = "netTcpBinding"
         contract = "IMyOtherContract"
     
/>
   </client>
</system.serviceModel>

Binding configuration

You can customize the client-side standard bindings to match the service binding in a manner identical to the service configuration, as shown in Example 1-18.

Example 1-18. Client-side binding configuration

<system.serviceModel>
   <client>
      <endpoint name = "MyEndpoint"
         address = "net.tcp://localhost:8000/MyService/"
        
bindingConfiguration = "TransactionalTCP"
        
binding  = "netTcpBinding"
         contract = "IMyContract"
      />
   </client>
   <bindings>
      <netTcpBinding>
         <binding name = "TransactionalTCP"
            transactionFlow = "true"
         />
      </netTcpBinding>
   </bindings>
</system.serviceModel>

Client-Side Programming

Generating the client config file

By default, SvcUtil also auto-generates a client-side config file called output.config. You can specify a config filename using the /config switch:

  SvcUtil http://localhost:8002/MyService/ /out:Proxy.cs
/config:App.Config

And you can suppress generating the config file using the/noconfigswitch:

  SvcUtil http://localhost:8002/MyService/ /out:Proxy.cs
/noconfig

I recommend never letting SvcUtil generate the config file. The reason is that it generates fully articulated binding sections that often just state the default values, which tends to clutter the config file.

In-proc configuration

With in-proc hosting, the client config file is also the service host config file, and the same file contains both service and client entries, as shown in Example 1-19.

Example 1-19. In-proc hosting config file

<system.serviceModel>
  
<services>
     
<service name = "MyService">
        
<endpoint
           
address  = "net.pipe://localhost/MyPipe"
           
binding  = "netNamedPipeBinding"
           
contract = "IMyContract"
         />
    
 </service>
   </services>
   <client>
     
<endpoint name = "MyEndpoint"
        
address  = "net.pipe://localhost/MyPipe"
        
binding  = "netNamedPipeBinding"
        
contract = "IMyContract"
      />
   </client>
</system.serviceModel>

Note the use of the named pipe binding for in-proc hosting.

The SvcConfigEditor

WCF provides a config file editor called SvcConfigEditor.exethat can edit both host and client configuration files (see Figure 1-10). You can also launch the editor from within Visual Studio by right-clicking on the configuration file (both the client and the host files) and selecting Edit WCF Configuration.

I have mixed feelings about SvcConfigEditor. On the one hand, it edits the config files nicely and it saves developers the need to know the configuration schema. On


Figure 1-10.  SvcConfigEditor is used to edit both host and client config files

the other hand, it does not save the need to thoroughly understand WCF configuration, and for the most part, the light editing done in a config file is faster by hand than editing using Visual Studio 2005.

Please check back next week for the conclusion to this article.

blog comments powered by Disqus
WINDOWS SCRIPTING ARTICLES

- More Windows Scripting Workarounds from Nilpo
- Overloading Methods and More in VBScript
- Improving MFC for Windows Vista
- Regular Expressions in VBScript
- Working with Dates in WMI
- Completing Calendars with VBScript Date Func...
- Building Calendars with VBScript Date Functi...
- Working With Dates and Times in VBScript
- Designing WCF DataContract Classes Using the...
- Understanding Dates and Times in VBScript
- Working With Arrays in VBScript
- Compressed Folders in WSH
- Using .NET Interops in VBScript
- Nilpo`s Scripting Secrets, Vol I
- Database operations using Silverlight 2.0 WC...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 3 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials