在没有契约接口的情况下从客户端调用 WCF 服务






4.91/5 (15投票s)
在没有契约接口的情况下从客户端调用 WCF 服务
昨天在希伯来语 C#/.NET Framework MSDN 论坛上有人问了一个棘手的问题 – 是否有可能仅使用契约名称、操作名称和元数据地址来动态调用 WCF 服务?
起初,我同意论坛中给出的答案 – 从 SOAP 绑定切换到 WebHttpBinding
(“REST”)。这当然使事情变得容易得多,只需要创建一个 WebHttpRequest
并解析响应即可。但是问题仍然存在 – 是否有可能在基于 SOAP 的服务终结点的情况下做到这一点?
简短的答案是 – 是的!
完整的答案是 – 是的,但你需要编写大量的代码才能使其正常工作,并且对于复杂的场景(谁说要传递数据契约?)需要编写更多的代码。
你问是怎么做到的?
首先,让我们从契约开始 – 你有一个如下所示的简单契约
1: [ServiceContract]
2: public interface ICalculator
3: {
4: [OperationContract]
5: double Add(double n1, double n2);
6: [OperationContract]
7: double Subtract(double n1, double n2);
8: [OperationContract]
9: double Multiply(double n1, double n2);
10: [OperationContract]
11: double Divide(double n1, double n2);
12: }
此时,实现并不重要,但你可以假设服务可以成功编译和加载。
其次,确保你的服务具有 MEX 终结点或通过 HTTP GET
暴露的元数据。阅读这篇文章以获取有关两者之间区别的更多信息。
第三 – 开始编写客户端代码!!!为了创建客户端代码,我从以下链接中获取了一些想法
- http://msdn.microsoft.com/en-us/library/ms733780.aspx – 为 WCF 契约生成客户端类型信息
- https://codeproject.org.cn/Articles/42278/Call-a-Web-Service-Without-Adding-a-Web-Reference – 相同的动态调用概念,但适用于 ASP.NET Web 服务 (ASMX)
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.ServiceModel;
5: using System.ServiceModel.Description;
6: using System.Globalization;
7: using System.Collections.ObjectModel;
8: using System.CodeDom.Compiler;
9:
10: namespace Client
11: {
12: class Program
13: {
14: static void Main(string[] args)
15: {
16: // Define the metadata address, contract name, operation name,
// and parameters.
17: // You can choose between MEX endpoint and HTTP GET by
// changing the address and enum value.
18: Uri mexAddress =
new Uri("https://:8732/CalculatorService/?wsdl");
19: // For MEX endpoints use a MEX address and a
// mexMode of .MetadataExchange
20: MetadataExchangeClientMode mexMode = MetadataExchangeClientMode.HttpGet;
21: string contractName = "ICalculator";
22: string operationName = "Add";
23: object[] operationParameters = new object[] { 1, 2 };
24:
25: // Get the metadata file from the service.
26: MetadataExchangeClient mexClient =
new MetadataExchangeClient(mexAddress, mexMode);
27: mexClient.ResolveMetadataReferences = true;
28: MetadataSet metaSet = mexClient.GetMetadata();
29:
30: // Import all contracts and endpoints
31: WsdlImporter importer = new WsdlImporter(metaSet);
32: Collection<ContractDescription> contracts =
importer.ImportAllContracts();
33: ServiceEndpointCollection allEndpoints = importer.ImportAllEndpoints();
34:
35: // Generate type information for each contract
36: ServiceContractGenerator generator = new ServiceContractGenerator();
37: var endpointsForContracts =
new Dictionary<string, IEnumerable<ServiceEndpoint>>();
38:
39: foreach (ContractDescription contract in contracts)
40: {
41: generator.GenerateServiceContractType(contract);
42: // Keep a list of each contract's endpoints
43: endpointsForContracts[contract.Name] = allEndpoints.Where(
44: se => se.Contract.Name == contract.Name).ToList();
45: }
46:
47: if (generator.Errors.Count != 0)
48: throw new Exception("There were errors during code compilation.");
49:
50: // Generate a code file for the contracts
51: CodeGeneratorOptions options = new CodeGeneratorOptions();
52: options.BracingStyle = "C";
53: CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("C#");
54:
55: // Compile the code file to an in-memory assembly
56: // Don't forget to add all WCF-related assemblies as references
57: CompilerParameters compilerParameters = new CompilerParameters(
58: new string[] {
59: "System.dll", "System.ServiceModel.dll",
60: "System.Runtime.Serialization.dll" });
61: compilerParameters.GenerateInMemory = true;
62:
63: CompilerResults results = codeDomProvider.CompileAssemblyFromDom(
64: compilerParameters, generator.TargetCompileUnit);
65:
66: if (results.Errors.Count > 0)
67: {
68: throw new Exception("There were errors during
generated code compilation");
69: }
70: else
71: {
72: // Find the proxy type that was generated for the specified contract
73: // (identified by a class that implements
// the contract and ICommunicationbject)
74: Type clientProxyType = results.CompiledAssembly.GetTypes().First(
75: t => t.IsClass &&
76: t.GetInterface(contractName) != null &&
77: t.GetInterface(typeof(ICommunicationObject).Name) != null);
78:
79: // Get the first service endpoint for the contract
80: ServiceEndpoint se = endpointsForContracts[contractName].First();
81:
82: // Create an instance of the proxy
83: // Pass the endpoint's binding and address as parameters
84: // to the ctor
85: object instance = results.CompiledAssembly.CreateInstance(
86: clientProxyType.Name,
87: false,
88: System.Reflection.BindingFlags.CreateInstance,
89: null,
90: new object[] { se.Binding, se.Address },
91: CultureInfo.CurrentCulture, null);
92:
93: // Get the operation's method, invoke it, and get the return value
94: object retVal = instance.GetType().GetMethod(operationName).
95: Invoke(instance, operationParameters);
96:
97: Console.WriteLine(retVal.ToString());
98: }
99: }
100: }
101: }
我添加了描述代码的注释,但基本上它导入 WSDL,为契约(服务 + 数据)生成类型,从中生成 C# 代码,编译它,并使用反射创建代理并调用正确的方法。
如果你想使用这种技术调用需要数据契约的方法,你需要做一些额外的工作来创建正确的类型并初始化它。
此代码(以及服务)的编译和运行版本可以在这里找到。
希望这段代码对你有所帮助。