通常我们在程序中调用WebService时,都是通过“添加Web引用”,让VS.NET环境来为我们生成客户端代理类,然后调用对应的Web服务。
如果哪一天发布Web服务的URL改变了,则我们需要使用新的asmx文件连接添加“添加web引用”,以便让VS.NET生成代理,并重新编译。在某些情况下,这是不能忍受的。为此,我们需求一种WebService的动态调用功能,.net平台也提供了相应的解决方案。
比如我们可以把Web服务的URL保存在配置文件中,这样,当服务URL改变时,只需要修改配置文件就可以了。
动态调用WebService有多种方式,应用的场景有所不同。
方法一
Web Service内容没有变,只是换了个地方。比如从localhost:8080/a.asmx换到了localhost:8090/a.asmx。如此一来你不必重新修改Web Reference只需要覆写Url。
操作:我们手工添加一个"Web引用",例如命名为GLWebReference。
然后我们通过ConfigurationManager.AppSettings["gqWebService"]调用webconfig中所配置的webservice链接。通过覆写webService对象的Url地址,然后调用相应的方法即可。
GLWebReference.SingerLoginWebService service = new GLWebReference.SingerLoginWebService();
service.Url = ConfigurationManager.AppSettings["gqWebService"];
string certificate = service.GetUserCertificate(loginName, domain);
方法二、
在某些情况下我们可能需要在程序运行期间动态调用一个未知的服务。在 .NET Framework 的 System.Web.Services.Description 命名空间中有我们需要的东西。
具体步骤:
1. 从目标 URL 下载 WSDL 数据。
2. 使用 ServiceDescription 创建和格式化 WSDL 文档文件。
3. 使用 ServiceDescriptionImporter 创建客户端代理类。
4. 使用 CodeDom 动态创建客户端代理类程序集。
5. 利用反射调用相关 WebService 方法。
上述步骤需要引用如下四个名称空间:
using System.Web.Services.Description; //WS的描述
以下几个用于根据描述动态生成代码并动态编译获取程序集
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
上述几个名称空间中包括如下几个重要的类:
using System.Web.Services.Description下:
ServiceDescription //WS描述
ServiceDescriptionImporter //通过描述生成客户端代理类,特别注意其中的Style
以下是MSDN对其的描述:
XMLWeb services 的接口通常由 Web 服务描述语言 (WSDL) 文件来说明。例如,若要获取有关使用 http://localhost/service.asmx 处公开的 ASP.NET 的 Web 服务的 WSDL 说明,只需导航到http://localhost/service.asmx?WSDL。使用ServiceDescriptionImporter 类可以方便地将 WSDL 说明中包含的信息导入到 System.CodeDom.CodeCompileUnit 对象。通过调整 Style 参数的值,可以指示ServiceDescriptionImporter 实例生成客户端代理类(通过透明调用该类可提供 Web 服务的功能)或生成抽象类(该类封装 Web 服务的功能而不实现该功能)。如果将 Style 属性设置为 Client,则 ServiceDescriptionImporter 生成客户端代理类,通过调用这些类来提供说明的 Web 服务的功能。如果将 Style 属性设置为 Server,则 ServiceDescriptionImporter 实例生成抽象类,这些类表示所说明的 XMLWeb services 的功能而不进行实现。然后,可以通过编写从这些抽象类继承的类来对其进行实现,并实现相关的方法。
using System.CodeDom下:
CodedomUnit//它用于设定动态代码的名称空间,类名等,可以通过ServiceDescriptionImporter.Import()方法将WS的描述代码写入该类,以作动态编译用
using System.CodeDom.Compiler下:
CodedomProvider //用于创建和检索代码生成器和代码编译器的实例,我们主要用到其实现子类CShareCodeProvider
可以直接用CShareCodeProvider provider=new CShareCodeProvider()来生成,或者用CodedomProvider.CreateProvider("CSharp")来生成
ICodeCompiler //用于编译基于System.CodeDom 的源代码表示形式。
它通过CodedomProvider的CreateCompiler()方法来
CompilerResults //表示从编译器返回的编译结果。 它由ICodeCompiler根据指定的编译器设置从指定的 CodeCompileUnit 所包含的 System.CodeDom 树中编译程序集并返回。CompiledAssembly 属性指示编译的程序集。
了解如上信息后,就可动态调用WS了。
例如Web.Config配置文件中有一下的webService连接:
调用
if(!string.IsNullOrEmpty(company))
{
webServiceUrl = ConfigurationManager.AppSettings[company + "WebService"]; //获取配置文件的WebService地址
hcsUrl = ConfigurationManager.AppSettings[company + "HCSLink"];
}
if (!string.IsNullOrEmpty(webServiceUrl)) //如果webService地址不为空,则进行WebService的动态调用
{
userCertificate = InvokeWebservice(webServiceUrl, "HCSSingerLoginService", "SingerLoginWebService",
"GetUserCertificate", new object[] { loginName, domain },company).ToString();
if (!string.IsNullOrEmpty(userCertificate))
{
hcsUrl += string.Format("?userid={0}&password=&IsAdmin=false&LoginTime=&Certificate={1}&Referrer={2}",
loginName, userCertificate, domain);
Response.Write("");
}
}
/// 根据指定的信息,调用远程WebService方法
///
/// WebService的http形式的地址
/// 欲调用的WebService的命名空间
/// 欲调用的WebService的类名(不包括命名空间前缀)
/// 欲调用的WebService的方法名
/// 参数列表
/// 公司别
/// WebService的执行结果
public object InvokeWebservice(string url, string nameSpace, string classname, string methodname, object[] args,string company)
{
try
{
//1.使用WebClient 下载WSDL信息
WebClient wc = new WebClient();
Stream stream = wc.OpenRead(url + "?WSDL");
//2.创建和格式化WSDL文档
ServiceDescription srvDesc = ServiceDescription.Read(stream);
//3. 创建客户端代理代理类
ServiceDescriptionImporter srvDescInporter = new ServiceDescriptionImporter();
srvDescInporter.ProtocolName = "Soap";//指定访问协议
srvDescInporter.Style = ServiceDescriptionImportStyle.Client;//生成客户端代理,默认。
srvDescInporter.AddServiceDescription(srvDesc, "", ""); //添加WSDL文档。
//4 .使用 CodeDom 编译客户端代理类。
CodeNamespace codeNamespce = new CodeNamespace(nameSpace);
CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
codeCompileUnit.Namespaces.Add(codeNamespce);
srvDescInporter.Import(codeNamespce, codeCompileUnit);
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
//表示用于调用编译器的参数。
System.CodeDom.Compiler.CompilerParameters parameter = new System.CodeDom.Compiler.CompilerParameters();
parameter.GenerateExecutable = false; //设置是否生成可执行文件。
parameter.GenerateInMemory = true; //设置是否在内存中生成输出。
parameter.ReferencedAssemblies.Add("System.dll"); //ReferencedAssemblies 获取当前项目所引用的程序集。
parameter.ReferencedAssemblies.Add("System.XML.dll");
parameter.ReferencedAssemblies.Add("System.Web.Services.dll");
parameter.ReferencedAssemblies.Add("System.Data.dll");
//获取从编译器返回的编译结果。
System.CodeDom.Compiler.CompilerResults cr = provider.CompileAssemblyFromDom(parameter, codeCompileUnit);
if (true == cr.Errors.HasErrors)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
{
sb.Append(ce.ToString());
sb.Append(System.Environment.NewLine);
}
throw new Exception(sb.ToString());
}
//获取已编译的程序集,然后通过反射进行动态调用。
System.Reflection.Assembly assembly = cr.CompiledAssembly;
Type t = assembly.GetType(nameSpace + "." + classname, true, true); // 如果在前面为代理类添加了命名空间,此处需要将命名空间添加到类型前面。
object obj = Activator.CreateInstance(t);
System.Reflection.MethodInfo mi = t.GetMethod(methodname);
return mi.Invoke(obj, args);
}
catch (Exception ex)
{
//throw new Exception(ex.InnerException.Message, new Exception(ex.InnerException.StackTrace));
switch (company)
{
case "gl":
companyName = "广乐公司";
break;
case "gq":
companyName = "广清公司";
break;
case "bm":
companyName="包茂公司";
break;
case "zh":
companyName="肇花公司";
break;
case "ch":
companyName = "潮惠公司";
break;
}
ShowFormat.ShowMsg("未能远程到" + companyName);
return "";
}
}
生成客户端代理程序集文件
上面的代码通过在内存中创建动态程序集的方式完成了动态调用过程。如果我们希望将客户端代理类生成程序集文件保存到硬盘,则可以进行如下修改。生成程序集文件后,我们可以通过 Assembly.LoadFrom() 载入并进行反射调用。对于需要多次调用的系统,要比每次生成动态程序集效率高出很多。
using System.IO;
using System.Net;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
// 1. 使用 WebClient 下载 WSDL 信息。
WebClient web = new WebClient();
Stream stream = web.OpenRead("http://localhost:60436/Learn.WEB/WebService.asmx?WSDL");
// 2. 创建和格式化 WSDL 文档。
ServiceDescription description = ServiceDescription.Read(stream);
// 3. 创建客户端代理代理类。
ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
importer.ProtocolName = "Soap"; // 指定访问协议。
importer.Style = ServiceDescriptionImportStyle.Client; // 生成客户端代理。
importer.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateNewAsync;
importer.AddServiceDescription(description, null, null); // 添加 WSDL 文档。
// 4. 使用 CodeDom 编译客户端代理类。
CodeNamespace nmspace = new CodeNamespace(); // 为代理类添加命名空间,缺省为全局空间。
CodeCompileUnit unit = new CodeCompileUnit();
unit.Namespaces.Add(nmspace);
ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit);
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameter = new CompilerParameters();
parameter.GenerateExecutable = false;
parameter.OutputAssembly = "test.dll"; // 可以指定你所需的任何文件名。
parameter.ReferencedAssemblies.Add("System.dll");
parameter.ReferencedAssemblies.Add("System.XML.dll");
parameter.ReferencedAssemblies.Add("System.Web.Services.dll");
parameter.ReferencedAssemblies.Add("System.Data.dll");
CompilerResults result = provider.CompileAssemblyFromDom(parameter, unit);
if (result.Errors.HasErrors)
{
// 显示编译错误信息
}
调用程序集文件演示
Assembly asm = Assembly.LoadFrom("test.dll");
Type t = asm.GetType("WebService");
object o = Activator.CreateInstance(t);
MethodInfo method = t.GetMethod("HelloWorld");
Console.WriteLine(method.Invoke(o, null));