代码改变世界

Chapter 2.2:元数据通用查询类 MetadataHelper

2011-01-24 15:30  田志良  阅读(...)  评论(... 编辑 收藏

  本章主要介绍元数据通用查询类 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。