WebClient.OpenRead(url + "?WSDL"))。ServiceDescriptionImporter 產生客戶端 Proxy 原始碼 (CodeDOM)。CSharpCodeProvider 記憶體編譯組件 (CompilerParameters.GenerateInMemory = true)。MethodInfo.Invoke)。
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Web.Services.Description;
using System.Net;
using System.IO;
using Microsoft.CSharp;
public static class DynamicWsClient
{
private const string DefaultNamespace = "DynamicWsProxy";
public static object Invoke(string wsdlUrl, string methodName, params object[] args)
{
return Invoke(wsdlUrl, null, methodName, args);
}
public static object Invoke(string wsdlUrl, string className, string methodName, params object[] args)
{
// 1. 下載 WSDL
using (var wc = new WebClient())
using (var stream = wc.OpenRead(wsdlUrl + "?WSDL"))
{
var sd = ServiceDescription.Read(stream);
var importer = new ServiceDescriptionImporter();
importer.AddServiceDescription(sd, "", "");
importer.ProtocolName = "Soap12"; // 或 Soap
importer.Style = ServiceDescriptionImportStyle.Client;
importer.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties | CodeGenerationOptions.GenerateNewAsync;
// 2. 產生 CodeDOM
var ns = new CodeNamespace(DefaultNamespace);
var unit = new CodeCompileUnit();
unit.Namespaces.Add(ns);
var errors = importer.Import(ns, unit);
if (errors > 0) throw new InvalidOperationException("WSDL 匯入失敗");
// 3. 記憶體編譯
var provider = new CSharpCodeProvider();
var cp = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false,
TreatWarningsAsErrors = false
};
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
cp.ReferencedAssemblies.Add("System.Web.Services.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
var results = provider.CompileAssemblyFromDom(cp, unit);
if (results.Errors.HasErrors)
{
var msg = string.Join(Environment.NewLine, results.Errors.Cast().Select(e => e.ToString()));
throw new InvalidOperationException("Proxy 編譯失敗:\n" + msg);
}
// 4. 反射呼叫
var asm = results.CompiledAssembly;
var typeName = className ?? ExtractClassName(wsdlUrl);
var type = asm.GetType(DefaultNamespace + "." + typeName, true, true);
var instance = Activator.CreateInstance(type);
var method = type.GetMethod(methodName);
if (method == null) throw new MissingMethodException($"方法 {methodName} 不存在");
return method.Invoke(instance, args);
}
}
private static string ExtractClassName(string url)
{
// 從 URL 取得服務名稱,如 .../MyService.asmx → MyService
var uri = new Uri(url);
var segments = uri.Segments;
var file = segments[segments.Length - 1];
return Path.GetFileNameWithoutExtension(file);
}
}
// 呼叫公開的火車時刻表 Web Service
string wsdl = "https://webservice.webxml.com.cn/WebServices/TrainTimeWebService.asmx";
object result = DynamicWsClient.Invoke(wsdl, "getDetailInfoByTrainCode", "K123", "");
if (result is System.Data.DataSet ds)
{
// 繫結到 GridView
GridView1.DataSource = ds.Tables[0];
GridView1.DataBind();
}
// 非同步版本 (APM 模式)
public static IAsyncResult BeginInvoke(string wsdlUrl, string methodName, object[] args, AsyncCallback callback, object state)
{
// 同步產生 Proxy (可快取組件提升效能)
var asm = BuildProxyAssembly(wsdlUrl);
var type = asm.GetType(DefaultNamespace + "." + ExtractClassName(wsdlUrl));
var instance = Activator.CreateInstance(type);
var beginMethod = type.GetMethod("Begin" + methodName);
return (IAsyncResult)beginMethod.Invoke(instance, args.Concat(new object[] { callback, state }).ToArray());
}
public static object EndInvoke(string wsdlUrl, string methodName, IAsyncResult asyncResult)
{
var asm = BuildProxyAssembly(wsdlUrl); // 應快取
var type = asm.GetType(DefaultNamespace + "." + ExtractClassName(wsdlUrl));
var instance = Activator.CreateInstance(type);
var endMethod = type.GetMethod("End" + methodName);
return endMethod.Invoke(instance, new object[] { asyncResult });
}
// 組件快取策略 (簡易版)
private static readonly ConcurrentDictionary _asmCache = new ConcurrentDictionary();
private static Assembly BuildProxyAssembly(string wsdlUrl)
{
return _asmCache.GetOrAdd(wsdlUrl, url => {
// ... 前面的 WSDL 匯入與編譯邏輯 ...
return results.CompiledAssembly;
});
}
Assembly 物件,Key 為 WSDL URL。ConcurrentDictionary.GetOrAdd 確保單次建構。| 方案 | 優點 | 缺點 |
|---|---|---|
| HttpClient + 手工 SOAP XML | 零依賴、完全控制、效能最佳 | 需手寫 XML、處理命名空間、SOAP Header |
| WCF ChannelFactory<T> | 型別安全、支援合約介面、內建快取 | 需共用合約組件、配置較繁瑣 |
| Service Reference (VS 產生 Proxy) | 開發體驗最好、IntelliSense、設計時驗證 | 端點變更需重新產生、程式碼膨脹 |
| 動態編譯 (本文方案) | 執行時決定端點、無設計時依賴 | 首次冷啟動慢、除錯困難、錯誤訊息不友善 |
**新專案首選 HttpClient + 手工 SOAP** 或 **gRPC/REST API** 取代傳統 ASMX/WCF。