之前公司有这样一个需求:服务端需要一个主动推送功能,一旦有了数据的发布,我们就向已经向我们公司注册推送服务的其他平台推送数据(要求通过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