• .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

  • 相关阅读:
    2017.10.30 天晴 昨天十公里没减肥
    我的一辩论点,随心而论
    2017.10.27 多云 天气晴
    2017.10.14 多云 天气转冷
    2017.10.9 天晴 准备减肥,有一起打卡的吗
    2017.10.7 国庆第8天
    2017.10.7 国庆第7天{鳏寡孤独}
    java多线程概念
    spring mvc分拣查询参数
    spring mvc 导出excel
  • 原文地址:https://www.cnblogs.com/moxianmanong/p/3265018.html
Copyright © 2020-2023  润新知