本章主要介绍元数据通用查询类 MetadataHelper。
有时候,客户端需要通过编程方式验证一个特定的终结点(通过地址进行识别)是否支持一个特定的契约。设想有这样一个应用程序,终端用户在安装时(甚至在运行时)指定或配置应用程序,用以使用服务并与服务交互。如果服务不支持所需的契约,应用程序就会向用户发出警告,提示配置的地址是无效的,询问是否更正地址或替换地址。为了支持这一功能,应用程序需要获取服务终结点的元数据,查看是否存在至少一个终结点支持请求的契约。为了简化对返回元数据的解析工作 ,现提供元数据通用查询类 MetadataHelper,如下所示:
using System; using System.ServiceModel; using System.Diagnostics; using System.Collections.Generic; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.Linq; namespace WCF.Chapter2.Metadata.Client { public static class MetadataHelper { const int MessageSizeMultiplier = 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) { if (String.IsNullOrEmpty(mexAddress)) { Debug.Assert(false, "Empty address"); return null; } Uri address = new Uri(mexAddress); ServiceEndpointCollection endpoints = null; if (address.Scheme == "http") { HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement(); httpBindingElement.MaxReceivedMessageSize *= MessageSizeMultiplier; //Try the HTTP MEX Endpoint try { endpoints = QueryMexEndpoint(mexAddress, httpBindingElement); } catch { } //Try over HTTP-GET if (endpoints == null) { string httpGetAddress = mexAddress; if (mexAddress.EndsWith("?wsdl") == false) { httpGetAddress += "?wsdl"; } CustomBinding binding = new CustomBinding(httpBindingElement); MetadataExchangeClient MEXClient = new MetadataExchangeClient(binding); MetadataSet metadata = MEXClient.GetMetadata(new Uri(httpGetAddress), MetadataExchangeClientMode.HttpGet); MetadataImporter importer = new WsdlImporter(metadata); endpoints = importer.ImportAllEndpoints(); } } if (address.Scheme == "https") { HttpsTransportBindingElement httpsBindingElement = new HttpsTransportBindingElement(); httpsBindingElement.MaxReceivedMessageSize *= MessageSizeMultiplier; //Try the HTTPS MEX Endpoint try { endpoints = QueryMexEndpoint(mexAddress, httpsBindingElement); } catch { } //Try over HTTP-GET if (endpoints == null) { string httpsGetAddress = mexAddress; if (mexAddress.EndsWith("?wsdl") == false) { httpsGetAddress += "?wsdl"; } CustomBinding binding = new CustomBinding(httpsBindingElement); MetadataExchangeClient MEXClient = new MetadataExchangeClient(binding); MetadataSet metadata = MEXClient.GetMetadata(new Uri(httpsGetAddress), MetadataExchangeClientMode.HttpGet); MetadataImporter importer = new WsdlImporter(metadata); endpoints = importer.ImportAllEndpoints(); } } if (address.Scheme == "net.tcp") { TcpTransportBindingElement tcpBindingElement = new TcpTransportBindingElement(); tcpBindingElement.MaxReceivedMessageSize *= MessageSizeMultiplier; endpoints = QueryMexEndpoint(mexAddress, tcpBindingElement); } if (address.Scheme == "net.pipe") { NamedPipeTransportBindingElement ipcBindingElement = new NamedPipeTransportBindingElement(); ipcBindingElement.MaxReceivedMessageSize *= MessageSizeMultiplier; endpoints = QueryMexEndpoint(mexAddress, ipcBindingElement); } return endpoints.ToArray(); } public static Type GetCallbackContract(string mexAddress, Type contractType) { if (contractType.IsInterface == false) { Debug.Assert(false, contractType + " is not an interface"); return null; } object[] attributes = contractType.GetCustomAttributes(typeof(ServiceContractAttribute), false); if (attributes.Length == 0) { Debug.Assert(false, "Interface " + contractType + " does not have the ServiceContractAttribute"); return null; } ServiceContractAttribute attribute = attributes[0] as ServiceContractAttribute; if (attribute.Name == null) { attribute.Name = contractType.ToString(); } if (attribute.Namespace == null) { attribute.Namespace = "http://tempuri.org/"; } return GetCallbackContract(mexAddress, attribute.Namespace, attribute.Name); } public static Type GetCallbackContract(string mexAddress, string contractNamespace, string contractName) { if (String.IsNullOrEmpty(contractNamespace)) { Debug.Assert(false, "Empty namespace"); return null; } if (String.IsNullOrEmpty(contractName)) { Debug.Assert(false, "Empty name"); return null; } try { ServiceEndpoint[] endpoints = GetEndpoints(mexAddress); foreach (ServiceEndpoint endpoint in endpoints) { if (endpoint.Contract.Namespace == contractNamespace && endpoint.Contract.Name == contractName) { return endpoint.Contract.CallbackContractType; } } } catch { } return null; } 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); return endpoints.Any(endpoint => endpoint.Contract.Namespace == contractNamespace && endpoint.Contract.Name == contractName); } catch { } return false; } public static string[] GetContracts(string mexAddress) { return GetContracts(typeof(Binding), mexAddress); } public static string[] GetContracts(Type bindingType, string mexAddress) { Debug.Assert(bindingType.IsSubclassOf(typeof(Binding)) || bindingType == typeof(Binding)); ServiceEndpoint[] endpoints = GetEndpoints(mexAddress); List<string> contracts = new List<string>(); string contract; foreach (ServiceEndpoint endpoint in endpoints) { if (bindingType.IsInstanceOfType(endpoint.Binding)) { contract = endpoint.Contract.Namespace + " " + endpoint.Contract.Name; if (contracts.Contains(contract) == false) { contracts.Add(contract); } } } return contracts.ToArray(); } public static string[] GetAddresses(string mexAddress, Type contractType) { if (contractType.IsInterface == false) { Debug.Assert(false, contractType + " is not an interface"); return new string[] { }; } object[] attributes = contractType.GetCustomAttributes(typeof(ServiceContractAttribute), false); if (attributes.Length == 0) { Debug.Assert(false, "Interface " + contractType + " does not have the ServiceContractAttribute"); return new string[] { }; } ServiceContractAttribute attribute = attributes[0] as ServiceContractAttribute; if (attribute.Name == null) { attribute.Name = contractType.ToString(); } if (attribute.Namespace == null) { attribute.Namespace = "http://tempuri.org/"; } return GetAddresses(mexAddress, attribute.Namespace, attribute.Name); } public static string[] GetAddresses(string mexAddress, string contractNamespace, string contractName) { ServiceEndpoint[] endpoints = GetEndpoints(mexAddress); List<string> addresses = new List<string>(); foreach (ServiceEndpoint endpoint in endpoints) { if (endpoint.Contract.Namespace == contractNamespace && endpoint.Contract.Name == contractName) { Debug.Assert(addresses.Contains(endpoint.Address.Uri.AbsoluteUri) == false); addresses.Add(endpoint.Address.Uri.AbsoluteUri); } } return addresses.ToArray(); } public static string[] GetAddresses(Type bindingType, string mexAddress, Type contractType) { Debug.Assert(bindingType.IsSubclassOf(typeof(Binding)) || bindingType == typeof(Binding)); if (contractType.IsInterface == false) { Debug.Assert(false, contractType + " is not an interface"); return new string[] { }; } object[] attributes = contractType.GetCustomAttributes(typeof(ServiceContractAttribute), false); if (attributes.Length == 0) { Debug.Assert(false, "Interface " + contractType + " does not have the ServiceContractAttribute"); return new string[] { }; } ServiceContractAttribute attribute = attributes[0] as ServiceContractAttribute; if (attribute.Name == null) { attribute.Name = contractType.ToString(); } if (attribute.Namespace == null) { attribute.Namespace = "http://tempuri.org/"; } return GetAddresses(bindingType, mexAddress, attribute.Namespace, attribute.Name); } public static string[] GetAddresses(Type bindingType, string mexAddress, string contractNamespace, string contractName) { Debug.Assert(bindingType.IsSubclassOf(typeof(Binding)) || bindingType == typeof(Binding)); ServiceEndpoint[] endpoints = GetEndpoints(mexAddress); List<string> addresses = new List<string>(); foreach (ServiceEndpoint endpoint in endpoints) { if (bindingType.IsInstanceOfType(endpoint.Binding)) { if (endpoint.Contract.Namespace == contractNamespace && endpoint.Contract.Name == contractName) { Debug.Assert(addresses.Contains(endpoint.Address.Uri.AbsoluteUri) == false); addresses.Add(endpoint.Address.Uri.AbsoluteUri); } } } return addresses.ToArray(); } public static string[] GetOperations(string mexAddress, Type contractType) { if (contractType.IsInterface == false) { Debug.Assert(false, contractType + " is not an interface"); return new string[] { }; } object[] attributes = contractType.GetCustomAttributes(typeof(ServiceContractAttribute), false); if (attributes.Length == 0) { Debug.Assert(false, "Interface " + contractType + " does not have the ServiceContractAttribute"); return new string[] { }; } ServiceContractAttribute attribute = attributes[0] as ServiceContractAttribute; if (attribute.Name == null) { attribute.Name = contractType.ToString(); } if (attribute.Namespace == null) { attribute.Namespace = "http://tempuri.org/"; } return GetOperations(mexAddress, attribute.Namespace, attribute.Name); } public static string[] GetOperations(string mexAddress, string contractNamespace, string contractName) { ServiceEndpoint[] endpoints = GetEndpoints(mexAddress); List<string> operations = new List<string>(); foreach (ServiceEndpoint endpoint in endpoints) { if (endpoint.Contract.Namespace == contractNamespace && endpoint.Contract.Name == contractName) { foreach (OperationDescription operation in endpoint.Contract.Operations) { Debug.Assert(operations.Contains(operation.Name) == false); operations.Add(operation.Name); } break; } } return operations.ToArray(); } public static Binding GetBinding(string address) { if (String.IsNullOrEmpty(address)) { Debug.Assert(false, "Empty address"); return null; } string baseAddress = GetBaseAddress(address) + "?wsdl"; ServiceEndpoint[] endpoints = GetEndpoints(address); foreach (ServiceEndpoint endpoint in endpoints) { if (endpoint.Address.Uri.AbsoluteUri == address) { return endpoint.Binding; } } return null; } static string GetBaseAddress(string address) { string[] segments = address.Split('/'); return segments[0] + segments[1] + segments[2] + "/"; } } }
GetEndpoints() 方法对元数据交换地址的样式进行了解析。根据找到的传输样式,GetEndpoints() 方法创建了一个需要使用的绑定元素, 这样就可以设置它的 MaxReceivedMessageSize 属性值。MaxReceiveMessageSize 的默认值为 64K 。它适用于简单的服务。如果服务包含多个终结点 ,终结点又使 用了复杂类型,就会生成更大的消息。此时,调用 MetadataExchangeClient.GetMetadata() 方法就会失败。根据经验,大多数情况下最合适的倍数因子是 5。接着,GetEndpoints() 调用了 QueryMexEndpoint() 私有方法,以获取元数据。
QueryMexEndpoint() 方法接收元数据交换终结点的地址以及要使用的绑定元素。使用绑定元素是为了创建定制绑定,并将它提供给 MetadataExchange-Client 实例。MetadataExchangeClient 实例能够获取元数据,返回终结点集合。
QueryContract() 方法首先会验证传入的Type类型是否是接口类型,如果是,则判断该接口是否标记了 ServiceContract 特性。因为 ServiceContract特性可以为契约的请求类型指定名称和命名空间的别名,QueryContract()使用这些值查询符合条件的契约。如果没有指定别名, QueryContract()方法则使用类型的名字与默认的命名空间 http :// tem puri.org,然后调用另一个重载版本的 QueryContract()方法,它能够操作契约的名称和命名空间。该版本的 QueryContract() 方法调用了GetEndpoints()方法,以获得终结点数组,然后遍历该数组。如果找到至少一个终结点支持该契约,则返回 true。不管出现何种错误, QueryContract()方法都会返回false。