所謂擷取WCF的服務中繼資料(Metadata),歸根結點,實際上就是擷取服務的終結點 (Endpoint)的資訊,這是服務公開在外的資料資訊,包括Address、Binding與Contract,也就是所謂的ABCs。Juval Löwy在《Programming WCF Services》一書中,用生動形象的棒棒糖表示了終結點的構成:

WCF服務可能包含多個終結點,每個終結點相當于是通信的入口,用戶端和服務端通過終結點交換資訊,如下圖所示:
因而,如果能夠擷取終結點的詳細資訊,有助于我們更好地剖析服務的定義、内容與執行方式。
服務有兩種方案可以釋出自己的中繼資料。一種是基于HTTP-GET協定提供中繼資料;另一種則為中繼資料交換方式,它往往使用一個專門的終結點,稱之為 中繼資料交換終結點。中繼資料交換終結點與其它終結點相似,仍然包含了位址、綁定與契約,但是使用的服務契約為WCF提供的接口 IMetadataExchange。
實際上,這兩種釋出中繼資料的方式代表了它使用了兩種不同的标準協定,前者為HTTP/GET請求,後者為WS-MetadataExchange(MEX)。在WCF,以MetadataExchangeClientMode枚舉類型表示這兩種中繼資料交換模式:
public enum MetadataExchangeClientMode
{
MetadataExchange,
HttpGet
}
WCF為終結點定義了一個專門的ServiceEndpoint類,被定義在System.ServiceModel.Description命名 空間中。ServiceEndpoint類包含了EndpointAddress,Binding,ContractDescription三個類型的屬 性,分别對應Endpoint的Address,Binding,Contract,如下圖:
要擷取服務的終結點,可以通過抽象類MetadataImporter擷取,類的定義如下:
public abstract class MetadataImporter
public abstract Collection<ContractDescription> ImportAllContracts();
public abstract ServiceEndpointCollection ImportAllEndpoints();
//其它方法略;
在類中,最重要的一個方法是ImportAllEndpoints(),它能夠擷取服務的所有終結點,并傳回一個 ServiceEndpointCollection類型的對象。該類型為一個終結點集合,可以通過調用 ServiceEndpointCollection的Find()方法或FindAll()方法,找到符合條件的一個或多個終結點。它的定義如下:
public class ServiceEndpointCollection : Collection<ServiceEndpoint>
public ServiceEndpoint Find(Type contractType);
public ServiceEndpoint Find(Uri address);
public Collection<ServiceEndpoint> FindAll(Type contractType);
//其它成員略
我們可以通過契約類型,或者服務契約的位址,查找符合條件的終結點。
MetadataImporter類隻是一個抽象類,如果要擷取WSDL中繼資料,還會需要使用繼承它的子類型WsdlImporter:
public class WsdlImporter : MetadataImporter
public WsdlImporter(MetadataSet metadata);
public Collection<Binding> ImportAllBindings();
public override Collection<ContractDescription> ImportAllContracts();
public override ServiceEndpointCollection ImportAllEndpoints();
public ServiceEndpointCollection ImportEndpoints(Binding wsdlBinding);
//其它成員略;
如果要使用WsdlImporter,需要為其構造函數傳遞一個MetadataSet類型的對象。而MetadataSet類型的對象則可以通過 MetadataExchangeClient類的GetMetadata()方法獲得。MetadataExchangeClient類的定義如下所 示:
public class MetadataExchangeClient
public MetadataExchangeClient();
public MetadataExchangeClient(Binding mexBinding);
public MetadataExchangeClient(EndpointAddress address);
public MetadataExchangeClient(string endpointConfigurationName);
public MetadataExchangeClient(Uri address, MetadataExchangeClientMode mode);
public MetadataSet GetMetadata();
public MetadataSet GetMetadata(EndpointAddress address);
public MetadataSet GetMetadata(Uri address, MetadataExchangeClientMode mode);
假定服務公開的中繼資料位址為http://localhost:8001/IMyService?wsdl,則擷取服務中繼資料的方法如下:
string mexAddress = "http://localhost:8001/IMyService?wsdl";
BasicHttpBinding binding = new BasicHttpBinding();
MetadataExchangeClient mexClient = new MetadataExchangeClient(binding);
MetadataSet metadata = mexClient.GetMetadata(new Uri(mexAddress), MetadataExchangeClientMode.HttpGet);
MetadataImporter importer = new WsdlImporter(metadata);
ServiceEndpointCollection endpoints = importer.ImportAllEndpoints();
注意,如果是HttpGet模式,則中繼資料位址的字尾必須為?wsdl。由于我們在調用MetadataExchangeClient的 GetMetadata()方法時,傳遞的MetadataExchangeClientMode枚舉參數值為HttpGet,是以擷取的為基于 HTTP-GET的中繼資料。
如果服務使用的協定為HTTP或者HTTPS,則可能使用中繼資料交換終結點,也可能為Http-Get模式。此時,我們可以先擷取中繼資料交換終結點,如果沒有找到,再擷取基于HTTP-GET的終結點:
string mexAddress = “http://localhost:8001/IMyService?wsdl”;
MetadataSet metadata = mexClient.GetMetadata(new EndpointAddress(mexAddress));
if (endpoints == null)
string httpGetAddress = mexAddress;
if (!mexAddress.EndsWith(“?wsdl”) )
{
httpGetAddress += “?wsdl”;
}
BasicHttpBinding binding = new BasicHttpBinding();
MetadataExchangeClient mexClient = new MetadataExchangeClient(binding);
MetadataSet metadata = mexClient.GetMetadata(new Uri(mexAddress), MetadataExchangeClientMode.HttpGet);
MetadataImporter importer = new WsdlImporter(metadata);
endpoints = importer.ImportAllEndpoints();
在獲得ServiceEndpointCollection集合對象後,就可以針對每個ServiceEndpoint擷取終結點的Address、Binding、Contract的資訊,如下所示:
foreach (ServiceEndpoint endpoint in endpoints)
Console.WriteLine(“Endpoint Name is {0}”, endpoint.Name);
Console.WriteLine(“Address is {0}”, endpoint.Address.Uri.AbsoluteUri);
Console.WriteLine(“Binding is {0}”, endpoint.Binding.GetType().ToString());
Console.WriteLine(“Address is {0}”, endpoint.Contract.Name);
Console.WriteLine();