65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (15投票s)

2012年2月11日

CPOL

2分钟阅读

viewsIcon

82040

在没有契约接口的情况下从客户端调用 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 暴露的元数据。阅读这篇文章以获取有关两者之间区别的更多信息。

第三 – 开始编写客户端代码!!!为了创建客户端代码,我从以下链接中获取了一些想法

   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# 代码,编译它,并使用反射创建代理并调用正确的方法。

如果你想使用这种技术调用需要数据契约的方法,你需要做一些额外的工作来创建正确的类型并初始化它。

此代码(以及服务)的编译和运行版本可以在这里找到。

希望这段代码对你有所帮助。

© . All rights reserved.