Web Service學習筆記:動態呼叫WebService
多數時候我們通過 "新增 Web 引用..." 建立客戶端代理類的方式呼叫WebService,但在某些情況下我們可能需要在程式執行期間動態呼叫一個未知的服務。在 .NET Framework 的 System.Web.Services.Description 名稱空間中有我們需要的東西。
具體步驟:
1. 從目標 URL 下載 WSDL 資料。
2. 使用 ServiceDescription 建立和格式化 WSDL 文件檔案。
3. 使用 ServiceDescriptionImporter 建立客戶端代理類。
4. 使用 CodeDom 動態建立客戶端代理類程式集。
5. 利用反射呼叫相關 WebService 方法。
OK,看看具體的例子。
我們要呼叫的目標 WebService,其 URL 是
HelloWorld.asmx [WebService(Namespace = "http://www.rainsts.net/", Description="我的Web服務")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class WebService : System.Web.Services.WebService { public WebService () { } [WebMethod] public string HelloWorld() { return "Hello Wolrd!"; } } 1. 動態呼叫 WebService 客戶端動態呼叫程式碼 using System.IO; using System.Net; using System.Reflection; 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.GenerateInMemory = true; 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); // 5. 使用 Reflection 呼叫 WebService。 if (!result.Errors.HasErrors) { Assembly asm = result.CompiledAssembly; Type t = asm.GetType("WebService"); // 如果在前面為代理類添加了名稱空間,此處需要將名稱空間新增到型別前面。 object o = Activator.CreateInstance(t); MethodInfo method = t.GetMethod("HelloWorld"); Console.WriteLine(method.Invoke(o, null)); }
2. 生成客戶端代理程式集檔案
上面的程式碼通過在記憶體中建立動態程式集的方式完成了動態呼叫過程。如果我們希望將客戶端代理類生成程式集檔案儲存到硬碟,則可以進行如下修改。生成程式集檔案後,我們可以通過 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));
3. 獲取客戶端代理類原始碼
還有一種情形,就是我們需要獲得客戶端代理類的 C# 原始碼。
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");
// 5. 儲存原始碼到檔案。當然,你也可以直接儲存到記憶體字串中。
TextWriter writer = File.CreateText("test.cs"); // 指定你所需的原始碼檔名。
provider.GenerateCodeFromCompileUnit(unit, writer, null);
writer.Flush();
writer.Close();
如果你呼叫時觸發 "WebException: 請求因 HTTP 狀態 415 失敗: Unsupported Media Type。" 這樣的異常,那麼恭喜你和我一樣鬱悶,趕緊把伺服器端的 WSE 關掉吧。在必須使用 WSE 的情況下,需要對客戶端進行調整.
================================================================================================================================= =================================================================================================================================
方法一:
Web Service內容沒有變,只是換了各地方。比如從localhost:8080/a.asmx換到了localhost:8090/a.asmx。如此一來你不必重新修改Web Reference只需要在使用WebMethod時
a.Url="http://localhost:8090/a.asmx"
a.remoteDosth(); 新的地址上的WEBSERVICE和舊的位置上的WEBSERVICE不全一樣,這樣在呼叫時會出現問題。
否則的話,如果WEBSERVICE只是換一個地址,那應該是沒問題的呀。
MyWebService.Service m_service = new TestApp.MyWebService.Service();
m_service.Url = @"WEBSERVICE目前所在地址";
這樣就可以正常使用了呀。
可以把Url字串存到config裡面,用的時候去取。
方法二:
private void Page_Load(object sender, System.EventArgs e)
{
// 在此處放置使用者程式碼以初始化頁面
object obj = InvokeWebservice("http://localhost/getPro.asmx","EtoonProduct.web.manage","getPro","getArea",new object[]{"1"});
this.DataGrid1.DataSource=obj;
this.DataGrid1.DataBind();
}
/**//// 根據指定的資訊,呼叫遠端WebService方法
///
/// WebService的http形式的地址
/// 欲呼叫的WebService的名稱空間
/// 欲呼叫的WebService的類名(不包括名稱空間字首)
/// 欲呼叫的WebService的方法名
/// 引數列表
/// WebService的執行結果
///
/// 如果呼叫失敗,將會丟擲Exception。請呼叫的時候,適當截獲異常。
/// 異常資訊可能會發生在兩個地方:
/// 1、動態構造WebService的時候,CompileAssembly失敗。
/// 2、WebService本身執行失敗。
///
///
///
/// object obj = InvokeWebservice("http://localhost/GSP_WorkflowWebservice/common.asmx","Genersoft.Platform.Service.Workflow","Common","GetToolType",new object[]{"1"});
///
///
private object InvokeWebservice(string url, string @namespace, string classname, string methodname, object[] args)
{
try
{
System.Net.WebClient wc = new System.Net.WebClient();
System.IO.Stream stream = wc.OpenRead(url+"?WSDL");
System.Web.Services.Description.ServiceDescription sd = System.Web.Services.Description.ServiceDescription.Read(stream);
System.Web.Services.Description.ServiceDescriptionImporter sdi = new System.Web.Services.Description.ServiceDescriptionImporter();
sdi.AddServiceDescription(sd,"","");
System.CodeDom.CodeNamespace cn = new System.CodeDom.CodeNamespace(@namespace);
System.CodeDom.CodeCompileUnit ccu = new System.CodeDom.CodeCompileUnit();
ccu.Namespaces.Add(cn);
sdi.Import(cn,ccu);
Microsoft.CSharp.CSharpCodeProvider csc = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.ICodeCompiler icc = csc.CreateCompiler();
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");
System.CodeDom.Compiler.CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu);
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));
}
}