Monday, May 2, 2011

[C#] Dynamic Web Service Invoker

Invoking a web service dynamically is not a new topic. I have been using the following method to invoke a web service without a proxy till recently. Later below you can see a better way of doing this.

Create your SOAP envelope and contents. You will be playing with some strings for a while.

The conventional method


 // Create and furnish the basic requestor
HttpWebRequest oHttpWebRequest = (HttpWebRequest) WebRequest.Create(URL);
oHttpWebRequest.UseDefaultCredentials = true;
oHttpWebRequest.ContentType = "text/xml";
oHttpWebRequest.Method = "POST";
oHttpWebRequest.Accept = "text/xml";
oHttpWebRequest.Headers.Add("SOAPAction:" + SoapAction);
oHttpWebRequest.UserAgent = "Mozilla/4.0+";
// Encode and post the request
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(sSoapMessage);
Stream oSendStream = oHttpWebRequest.GetRequestStream();
oSendStream.Write(bytes, 0, bytes.Length);
oSendStream.Close();
// Get the response
oHttpResponse = (HttpWebResponse) oHttpWebRequest.GetResponse();
Stream oReceiveStream = oHttpResponse.GetResponseStream();
// Manipulate the stream and extract data here
oHttpResponse.Close();
oReadStream.Close();

I have omitted many plumbing statements here as this is intended only to give a basic idea about how difficult this is. 

A different  approach

The next method also technically works the same way, because there is only one way a SOAP request can be made. Only difference is that the new approach wraps the whole string manipulation inside itself and does all plumbing work for us. The basic idea is to generate a proxy assembly when you need as a one time activity.

Uri uri = new Uri(_mUrl); // Create the uri object
WebRequest webRequest = WebRequest.Create(uri);

// Supply credentials, if required
Authenticate(webRequest);

WebResponse webResponse = webRequest.GetResponse();
Stream requestStream = webResponse.GetResponseStream();
// Get a WSDL file describing a service
ServiceDescription serviceDescription = ServiceDescription.Read(requestStream);
// Initialize a service description importer
ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
descriptionImporter.AddServiceDescription(serviceDescription, String.Empty, String.Empty);

//The allowed values of this property are:  "Soap", "Soap12", "HttpPost" ,"HttpGet" and "HttpSoap".
//The default value is "Soap", which indicates the SOAP 1.1 standard. This is case sensitive.
descriptionImporter.ProtocolName = "Soap";
descriptionImporter.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties;
CodeNamespace codeNamespace = new CodeNamespace();

// Compile to assembly
compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);
Assembly assembly = compilerResults.CompiledAssembly;

// We have a valid assembly now. Try to get the Type from it.
Type type = assembly.GetType(_mTypeName) ?? FindTypeByName(assembly);

// Create the object instance
_mTargetInstance = assembly.CreateInstance(_mTypeName);
// Get the method info object
_mMethodInfo = type.GetMethod(
_mMethodName,
BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.IgnoreReturn);

// Invoke the method with necessary arguments
object result = _mMethodInfo.Invoke(_mTargetInstance, parameters);

The advantage in this approach is that you need to execute this whole thing only once when you find the MethodInfo object as null (for the first time). The rest of the times this will behave just like a normal web service invoke using the conventional method.

I know you might be thinking that why should you do like this? Why can’t I invoke like the old way? Well the answer is ‘it depends’. There are some situations where you decide to post a piece of data to a web service who’s URL, method and parameters are known only at run-time, probably from a database configuration or a config file. I found this very useful for my application integration project.
Enjoy coding and let me know if you need help in this.

20 comments:

  1. Nice one. Really useful.

    ReplyDelete
  2. in my code oHttpWebRequest is not declared
    i have to declare it as what?

    ReplyDelete
  3. "in my code oHttpWebRequest is not declared
    i have to declare it as what?"
    Anonymous,
    You don't need a separate "HttpWebRequest" as you are already using "WebRequest" class.

    James

    ReplyDelete
  4. Hello,i'm beginner in SOAP and web service
    can you tell me how to create SOAP envelope and content,please?

    ReplyDelete
  5. Anonymous,
    You don't have to worry about the tedious SOAP construction complexities. Instead you can use the second approach (which is the very purpose of this post). But if you need to do that for some reason, you can find it here.

    ReplyDelete
  6. owh,,i see
    thank you :D

    ReplyDelete
  7. How to add a soap header if I need to provide authentication information?

    ReplyDelete
    Replies
    1. You can use "oHttpWebRequest.Headers.Add(..)" method to add SOAP headers to this.

      Delete
  8. Thanks for the post. How can we get the SoapFault information bound to the operation?

    ReplyDelete
    Replies
    1. The basic idea is to catch exceptions at appropriate places and handling them accordingly. You can find how this is handled at this MSDN (http://msdn.microsoft.com/en-us/library/cc540447.aspx) article.

      Delete
  9. Hi, nice post. I have a question: how can you set the "maxRequestLength" parameter by any of these approaches?

    ReplyDelete
  10. Hi James

    // Compile to assembly compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);

    Where does codeProvider come from?

    Thanks
    Angelo

    ReplyDelete
    Replies
    1. @Anonymous,
      You can use any derivations of 'System.CodeDom.Compiler.CodeDomProvider'.

      e.g.Microsoft.CSharp.CSharpCodeProvider, Microsoft.JScript.JScriptCodeProvider,
      Microsoft.VisualBasic.VBCodeProvider.

      James

      Delete
    2. Hi James

      Even the code after that are not compiling. Do you have a compiling code sample?

      Thanks
      Angelo

      Delete
    3. @Anonymous, Sorry, i do not have a sample with me at the moment. What is the error you are getting?

      Delete
    4. Type type = assembly.GetType(_mTypeName) ?? FindTypeByName(assembly);




      // Create the object instance

      _mTargetInstance = assembly.CreateInstance(_mTypeName);

      // Get the method info object

      _mMethodInfo = type.GetMethod(

      _mMethodName,

      BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.IgnoreReturn);


      In the above
      _mTypeName, FindByTypeName, _mTargetInstance are not found.

      Thanks
      Angelo

      Delete
    5. I got it working. Thanks!

      Angelo

      Delete
  11. Can someone please explain what these two lines will do
    compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);
    Assembly assembly = compilerResults.CompiledAssembly;

    What are compilerParameters and codeCompileUnit?

    Please can anyone reply me a little fast

    Thanks,

    ReplyDelete
  12. http://blogs.msdn.com/b/kaevans/archive/2006/04/27/dynamically-invoking-a-web-service.aspx

    Is more complete

    ReplyDelete