.NET实现跨平台动态调用webservice(wcf)

之前公司有这样一个需求:服务端需要一个主动推送功能,一旦有了数据的发布,我们就向已经向我们公司注册推送服务的其他平台推送数据(要求通过web服务,即其他平台通过提供一个webservice,我们推送时调用他们的这个webservice)。

这样的话就存在一个问题:其他平台的webservice地址我们不知道,不能通过提前引用生成代理类;其他平台可能是.net(又分传统的webserice跟wcf),java,php等。所以考虑实现的话必须动态调用webservice。(ps:其实这种主动推送需求还有其他设计方案,不一定非要用webservice,比如http请求(限web平台),socket什么的都是能实现主动推送的)。

下面我就来讲解下我是怎样去实现这个动态调用,并且支持基本的跨平台调用,也给很多想用这种方式而不能很好地兼容的码农们一个参考。

网上有个千篇一律的webservice动态调用的类,当然我这里也是用到了这个,进行了少许修改(别看小看这个少许求改,它就是我们动态解析不同的wsdl文档时的关键)。下面贴代码:

    /// <summary>
    /// 根据wsdl文档动态调用webservice
    /// </summary>
    public class WebServiceHelper
    {
        /// <summary>
        /// 动态调用webservice
        /// </summary>
        /// <param name="url"></param>
        /// <param name="className"></param>
        /// <param name="methodName"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static object Invoke(string url, string className,
                                              string methodName, object[] args)
        {
            try
            {
                object instance = GetInstance(url, className);
                System.Reflection.MethodInfo mi = GetMethodInfo(methodName, instance);
                return mi.Invoke(instance, args);
            }
            catch (Exception ex)
            {
                WebServcieCache.Remove(url);//失败移除缓存
                throw new Exception(ex.Message, new Exception(ex.StackTrace));
            }
        }

        /// <summary>
        /// 获取代理类实例
        /// </summary>
        /// <param name="url"></param>
        /// <param name="className"></param>
        /// <returns></returns>
        public static object GetInstance(string url, string className)
        {
            object instance = WebServcieCache.Get(url);//从缓存获取代理实例
            if (instance == null)
                instance = CreateInstance(url, className);//新建代理实例并缓存
            return instance;
        }

        /// <summary>
        /// 从代理实例中获取指定方法
        /// </summary>
        /// <param name="methodName"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static MethodInfo GetMethodInfo(string methodName, object instance)
        {
            System.Reflection.MethodInfo mi = instance.GetType().GetMethod(methodName);
            if (mi == null)
                throw new Exception("未实现指定方法:" + methodName);
            return mi;
        }

        /// <summary>
        /// 从代理实例中获取指定方法
        /// </summary>
        /// <param name="url"></param>
        /// <param name="className"></param>
        /// <param name="methodName"></param>
        /// <returns></returns>
        public static MethodInfo GetMethodInfo(string url, string className, string methodName)
        {
            System.Reflection.MethodInfo mi = GetInstance(url, className).GetType().GetMethod(methodName);
            if (mi == null)
                throw new Exception("未实现指定方法:" + methodName);
            return mi;
        }

        /// <summary>
        /// 获取代理类类型
        /// </summary>
        /// <param name="url"></param>
        /// <param name="className"></param>
        /// <returns></returns>
        public static Type GetType(string url, string className)
        {
            if (string.IsNullOrEmpty(className))
                className = GetWsClassName(url);

            System.Web.Services.Description.ServiceDescription sd = InitServiceDescription(url);

            System.CodeDom.Compiler.CompilerResults cr = CompileAssembly(url, sd);

            if (cr.Errors.HasErrors)
            {
                StringBuilder sb = new StringBuilder();
                foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
                {
                    sb.Append(ce.ToString());
                    sb.Append(System.Environment.NewLine);
                }
                throw new Exception(sb.ToString());
            }

            return CreateType(className, cr.CompiledAssembly);
        }

        private static object CreateInstance(string url, string className)
        {
            Type t = GetType(url, className);
            if (t == null)
                throw new Exception("无法从指定地址获取代理类");
            object obj = Activator.CreateInstance(t);
            WebServcieCache.Add(url, obj);
            return obj;
        }

        private static Type CreateType(string className, System.Reflection.Assembly assembly)
        {
            Type t = assembly.GetType(className, false, true);
            if (t == null)
            {
                foreach (var item in assembly.GetTypes())
                {
                    if (item.Name.StartsWith(className, true, null))
                    {
                        t = assembly.GetType(item.Name, true, true);
                        break;
                    }
                }
            }
            return t;
        }

        //编译客户端代理类
        private static System.CodeDom.Compiler.CompilerResults CompileAssembly(string url, System.Web.Services.Description.ServiceDescription sd)
        {
            //创建客户端代理代理类。
            System.Web.Services.Description.ServiceDescriptionImporter sdi
                = new System.Web.Services.Description.ServiceDescriptionImporter();

            //检查wsdl文档引用
            CheckWsdl(url, sdi);

            sdi.ProtocolName = "Soap"; // 指定访问协议。
            sdi.Style = System.Web.Services.Description.ServiceDescriptionImportStyle.Client; // 生成客户端代理。
            sdi.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties | System.Xml.Serialization.CodeGenerationOptions.GenerateNewAsync;
            sdi.AddServiceDescription(sd, null, null);

            //使用 CodeDom 编译客户端代理类。
            System.CodeDom.CodeNamespace cn = new System.CodeDom.CodeNamespace();
            System.CodeDom.CodeCompileUnit ccu = new System.CodeDom.CodeCompileUnit();
            ccu.Namespaces.Add(cn);
            sdi.Import(cn, ccu);

            System.CodeDom.Compiler.CompilerResults cr = null;
            using (Microsoft.CSharp.CSharpCodeProvider csc = new Microsoft.CSharp.CSharpCodeProvider())
            {
                System.CodeDom.Compiler.CompilerParameters cplist
                = new System.CodeDom.Compiler.CompilerParameters();
                cplist.GenerateExecutable = false;
                cplist.GenerateInMemory = true;
                cplist.ReferencedAssemblies.Add("System.dll");
                cplist.ReferencedAssemblies.Add("System.XML.dll");
                cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
                cplist.ReferencedAssemblies.Add("System.Data.dll");

                cr = csc.CompileAssemblyFromDom(cplist, ccu);
            }
            return cr;
        }

        //根据指定service地址创建和格式化描述语言文档
        private static System.Web.Services.Description.ServiceDescription InitServiceDescription(string url)
        {
            System.Web.Services.Description.ServiceDescription sd = null;
            //使用 WebClient 下载 WSDL 信息。
            using (System.Net.WebClient wc = new System.Net.WebClient())
            {
                using (System.IO.Stream stream = wc.OpenRead(url + "?wsdl"))
                {
                    //创建和格式化 WSDL 文档。
                    sd = System.Web.Services.Description.ServiceDescription.Read(stream);
                }
            }
            return sd;
        }

        //检查指定service地址的wsdl文档引用
        private static void CheckWsdl(string url, System.Web.Services.Description.ServiceDescriptionImporter sdi)
        {
            System.Web.Services.Discovery.DiscoveryClientProtocol dcp = new System.Web.Services.Discovery.DiscoveryClientProtocol();
            dcp.DiscoverAny(url + "?wsdl");
            dcp.ResolveAll();

            foreach (object doc in dcp.Documents.Values)
            {
                if (doc is System.Web.Services.Description.ServiceDescription) sdi.AddServiceDescription((System.Web.Services.Description.ServiceDescription)doc, null, null); ;
                if (doc is System.Xml.Schema.XmlSchema) sdi.Schemas.Add((System.Xml.Schema.XmlSchema)doc);
            }
        }

        //根据指定service地址获取类名
        private static string GetWsClassName(string wsUrl)
        {
            string[] parts = wsUrl.Split('/');
            string[] words = parts[parts.Length - 1].Split('.');
            if (words.Length == 0)
                return parts[parts.Length - 1];
            else if (words.Length == 1)
                return words[0];
            else if (words.Length >= 2)
                return words[words.Length - 2];
            else
                throw new Exception("未能从地址获取到ClassName");
        }
    }

 -----------------------------------------------------------------------------万恶的分隔符

下面我们用vs随便建一个webservcie跟wcf(自己动手),我们分析2个wsdl文档可以看到wcf比webservice多了这么个节点:

<wsdl:types>
    <xsd:schema targetNamespace="http://tempuri.org/Imports">
        <xsd:import schemaLocation="http://localhost:64790/Service1.svc?xsd=xsd0" namespace="http://tempuri.org/"/>
    </xsd:schema>
</wsdl:types>

如果我们将http://localhost:64790/Service1.svc?xsd=xsd0地址复制到浏览器,可以看到出现了另外一个文档部分。其实个节点就是把http://localhost:64790/Service1.svc?xsd=xsd0处的文档导入到当前文档。如果我们单纯得用网上通用的动态调webservice的类不能调用wcf,甚至其他java的部分webservcie(java有很多不同的调用webservcie的框架,可能存在生成的wsdl文档跟这里相似)。为了解决这个问题,所以我在上面的类中进行了少许修改,可以找到如下图的代码:

在上述调用类中还有个叫WebServcieCache的类,其实这个是对已经生成的代理类进行缓存的类,我们知道上述调用类是个非常消耗资源的过程,这个类就是为了优化而生的,当我们成功生成了代理类,那么没必要每次都重新生成了,直接放缓存里,下次调用时直接拿来用。下面贴上WebServcieCache代码:

    /// <summary>
    /// 服务代理简单缓存
    /// </summary>
    public class WebServcieCache
    {
        static Dictionary<string, object> dic = new Dictionary<string, object>();

        internal static object Get(string url)
        {
            if (Cache.ContainsKey(url))
                return Cache[url];
            return null;
        }

        internal static void Add(string url, object instance)
        {
            if (Cache.ContainsKey(url))
                Cache.Remove(url);
            dic.Add(url, instance);
        }

        internal static void Remove(string url)
        {
            if (Cache.ContainsKey(url))
                Cache.Remove(url);
        }

        public static void Invalidate(string url)
        {
            Remove(url);
        }

        public static void Invalidate()
        {
            dic.Clear();
            dic = null;
        }

        internal static Dictionary<string, object> Cache
        {
            get
            {
                if (dic == null)
                    dic = new Dictionary<string, object>();
                return dic;
            }
        }
    }

这样我们就基本完成了动态调用类的编写,但是调用过程中还存在某些问题

下面针对WCF给出解决办法:

        internal static bool Push(string url, string jsonData, int businessType)
        {
            System.Reflection.MethodInfo mi = WebServiceHelper.GetMethodInfo(url, SccinPushService.ClassName, SccinPushService.MethodPush);
            System.Reflection.ParameterInfo[] parmInfoes = mi.GetParameters();

            #region 固定兼容未作wsdl处理的WCF服务方法(原wcf服务接口契约上加上[XmlSerializerFormat(Style = OperationFormatStyle.Rpc)]属性即可不走此步奏)
            if (parmInfoes.Count() > 2)
            {
                System.Collections.ArrayList methodParams = new System.Collections.ArrayList();
                int resultParamIndex = 3;
                string resultParamName = SccinPushService.MethodPush + "Result";
                for (int i = 0; i < parmInfoes.Count(); i++)
                {
                    if (i == 0)
                        methodParams.Add(jsonData);
                    else if (i == 1)
                        methodParams.Add(businessType);
                    else
                        methodParams.Add(null);

                    if (parmInfoes[i].Name == resultParamName)
                    {
                        resultParamIndex = i;
                    }
                }
                object[] wcfParms = (object[])methodParams.ToArray(typeof(object));
                WebServiceHelper.Invoke(url, SccinPushService.ClassName, SccinPushService.MethodPush, wcfParms);
                return (bool)wcfParms[resultParamIndex];
            }
            #endregion

            object[] parms = new object[] { jsonData, businessType };
            object result = WebServiceHelper.Invoke(url, SccinPushService.ClassName, SccinPushService.MethodPush, parms);
            return (bool)result;
        }

上述方法中,url是我们要调用的webservice地址,jsonData是参数1,businessType是参数2。

预编译指令#region #endregion块中的代码是解决默认的wcf文档序列化设置的,其中string resultParamName = SccinPushService.MethodPush + "Result"是我们手动分析wcf的wsdl文档而得到的返回值在文档中的名称。for语句中我们设置了个局部变量resultParamIndex,我们通过循环根据返回值resultParamName的名称确定返回值在object[] wcfParms = (object[])methodParams.ToArray(typeof(object))中的索引。当我们调用wcf时WebServiceHelper.Invoke(url, SccinPushService.ClassName, SccinPushService.MethodPush, wcfParms)这个方法并没有返回值,而是将返回值写入了wcfParms中,所以才有了return (bool)wcfParms[resultParamIndex]来取到返回值并返回。

但是这个块中的代码也可以省略,毕竟是写死了的东西,不推荐使用。如果不用这个块中的代码,我们将对wcf契约进行一些设置,在契约上加上[XmlSerializerFormat(Style = OperationFormatStyle.Rpc)]特性。这样我们可以直接获取到返回值object result = WebServiceHelper.Invoke(url, SccinPushService.ClassName, SccinPushService.MethodPush, parms)。请参详上述调用代码和块注解。

本文以代码跟注解为主。上述代码本人只在.net中的webservice和wcf,java的webservcie(忘了什么框架了)做过测试,成功调用。如果有需要用到这个调用类的,请酌情考虑及修改。

转载请注明原文出处:http://www.cnblogs.com/moxianmanong/archive/2013/08/17/3265018.html

原文地址:https://www.cnblogs.com/moxianmanong/p/3265018.html