Click here to Skip to main content
11,634,631 members (67,138 online)
Click here to Skip to main content

Tagged as

Calling a WCF service from a client without having the contract interface

, 11 Feb 2012 CPOL 32.1K 24
Rate this:
Please Sign up or sign in to vote.
Calling a WCF service from a client without having the contract interface

I was asked yesterday in the Hebrew C#/.NET Framework MSDN forums a tough question – is it possible to dynamically call a WCF service using only the contract name, operation name, and metadata address?

At first, I agreed with the answer given in the forum – move from SOAP bindings to WebHttpBinding (“REST”). This of course makes things a lot easier, only requiring you to create a WebHttpRequest and parse the response. However the question remains - is it possible to do this in the case of a SOAP-based service endpoint?

The short answer is – YES!

The full answer is – YES, but you’ll need to do a lot of coding to make it work properly, and even more coding for complex scenarios (who said passing a data contract?)

How is it done you ask?

First let’s start with the contract – you have a simple contract that looks like so:

   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:  }

At this point, the implementation doesn’t matter, but you can assume the service compiles and loads successfully.

Second, make sure your service has either a MEX endpoint or metadata exposed over HTTP GET. Read here for more info about the difference between the two.

Third – do the client coding!!! To create the client code, I took some ideas from the following links:

   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("http://localhost: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:  }

I’ve placed comments that describe the code, but basically it imports the WSDL, generates types for the contract (service + data), generates C# code from it, compiles it, and uses reflection to create a proxy and invoke the correct method.

If you want to use this technique to call methods that require a data contract, you will need some extra work to create the correct type and initialize it.

A compiled and running version of this code (+ the service) can be found here.

Hope you find this piece of code useful.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ido Flatow
Architect Sela Group
Israel Israel
Web developer since 1997. I'm a senior architect at Sela Group in Israel. I'm a consultant, trainer (Microsoft MCT), and a speaker in conferences worldwide.
My main fields are WCF, ASP.NET, Windows Azure, IIS, Entity Framework, and Silverlight.

You may also be interested in...

Comments and Discussions

 
Questionworks only for simple generic types Pin
stadelma30-Jun-15 22:00
memberstadelma30-Jun-15 22:00 
QuestionOperationContext Pin
Patrick Chang25-May-14 6:58
memberPatrick Chang25-May-14 6:58 
QuestionHow to pass a DTO instead of simple parameters Pin
Ruwan Jayalath14-May-13 18:11
memberRuwan Jayalath14-May-13 18:11 
GeneralMy vote of 5 Pin
Ruwan Jayalath14-May-13 1:07
memberRuwan Jayalath14-May-13 1:07 
QuestionWCF Web Service with Jax-WS(Java Client) using Visual Studiio 2008 SP1 Pin
MrKyaw1-Apr-13 23:43
memberMrKyaw1-Apr-13 23:43 
QuestionGetting Null reference on lines 93-95 Pin
josetzamora20-Jan-13 23:34
memberjosetzamora20-Jan-13 23:34 
AnswerRe: Getting Null reference on lines 93-95 Pin
josetzamora4-Feb-13 12:41
memberjosetzamora4-Feb-13 12:41 
QuestionHow can i pass complex data type (i.e. DataContract and DataMember) Pin
ramuknavap26-Nov-12 15:40
memberramuknavap26-Nov-12 15:40 
GeneralMy vote of 5 Pin
RogerFraser25-Oct-12 21:56
memberRogerFraser25-Oct-12 21:56 
QuestionCallbacks Pin
mr0mega12-Oct-12 4:50
membermr0mega12-Oct-12 4:50 
GeneralMy vote of 4 Pin
mgh136615-Aug-12 22:38
membermgh136615-Aug-12 22:38 
QuestionGood explanation Pin
oceanIndian25-Jul-12 7:24
memberoceanIndian25-Jul-12 7:24 
AnswerRe: Good explanation Pin
Ido Flatow25-Jul-12 8:10
memberIdo Flatow25-Jul-12 8:10 
QuestionHow to Invoke a rest service and get all its method dynamically Pin
shirasshiru25-Jun-12 2:26
membershirasshiru25-Jun-12 2:26 
QuestionThe maximum message has been exceeded Pin
Dennis Dam16-Apr-12 5:10
memberDennis Dam16-Apr-12 5:10 
AnswerRe: The maximum message has been exceeded Pin
Ido Flatow17-Apr-12 8:00
memberIdo Flatow17-Apr-12 8:00 
After you get the ServiceEndpoint object, you can use it to change the binding configuration, such as:
(se.Binding as BasicHttpBinding).MaxReceivedMessageSize = 9999999;
In the above example I needed to know which type of binding we are using, instead you can use reflection to get the MaxReceivedMessageSize property from the se.Binding object - since this property exists in each of the binding types and is named the same way in all binding, reflection code should work quite easily.
GeneralRe: The maximum message has been exceeded Pin
Dennis Dam17-Apr-12 21:08
memberDennis Dam17-Apr-12 21:08 
Questionthis looks a little bit like this http://blogs.msdn.com/b/vipulmodi/archive/2006/11/16/dynamic-programming-with-wcf.aspx Pin
Gil.Schmidt11-Feb-12 23:00
memberGil.Schmidt11-Feb-12 23:00 
AnswerRe: this looks a little bit like this http://blogs.msdn.com/b/vipulmodi/archive/2006/11/16/dynamic-programming-with-wcf.aspx Pin
Ido Flatow13-Feb-12 3:46
memberIdo Flatow13-Feb-12 3:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150728.1 | Last Updated 11 Feb 2012
Article Copyright 2012 by Ido Flatow
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid