Dynamically Invoking a Web Service

One of the promises of web services is that you can dynamically discover new services and invoke them.  Without venturing into the debate of appropriate discovery and delivery mechanisms (UDDI, DISCO, WS-Discovery, WS-Transfer, WS-MetaDataExchange), we will focus on the dynamic invocation aspect and will assume that you have already obtained the URL to the WSDL document of interest.

Perhaps you have lots of web references generated and you want to programmatically invoke them.  You can rifle through the executing assembly's types and invoke a method via reflection.  If each web method has the same signature and you just want to invoke lots of web services in a sprinkler head fashion, you could pre-gen the type and then just change the URL on the proxy:

 WebServiceProxy.localhost.WebService s = new WebServiceProxy.localhost.WebService();
s.Url = "https://foo/webservice.asmx";
Console.WriteLine(s.HelloWorld());
s.Url = "https://bar/webservice.asmx";
Console.WriteLine(s.HelloWorld());

That's a pretty contrived example and not very applicable... it is highly unlikely that you will have lots of methods with a common signature in pre-generated proxies. 

Instead of using pre-generated assemblies, perhaps you want to generate the assembly on the fly and invoke it.  In other words, instead of right-clicking and using the "Add Web Reference" dialog or using WSDL.exe to create a proxy, perhaps you want to point your code at a URL and invoke a method without previously creating a proxy. 

The easiest way to dynamically invoke a web service is to leverage the work of others.  Christian Weyer's DynWsLib is a great tool that accomplishes the above task very nicely.

If you are a glutton for punishment, then you can leverage the System.Web.Services.Description.ServiceDescriptionImporter.  The example on that page is good, but there's a better example of using ServiceDescriptionImporter in the System.Web.Services.Description.WebReference class documentation.  Below, I modified that example and added some code to invoke a HelloWorld web method.

 using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Security.Permissions;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.Services.Discovery;
using System.Xml;
using System.Xml.Serialization;

class Program
{
    [SecurityPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
    static void Main()
    {
        System.Net.WebClient client = new System.Net.WebClient();
        System.IO.Stream stream = client.OpenRead("https://localhost/webservicedemo/webservice.asmx?wsdl");
        // Get a WSDL file describing a service.
        ServiceDescription description = ServiceDescription.Read(stream);

        // Initialize a service description importer.
        ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
        importer.ProtocolName = "Soap12";  // Use SOAP 1.2.
        importer.AddServiceDescription(description, null, null);

        // Report on the service descriptions.
        Console.WriteLine("Importing {0} service descriptions with {1} associated schemas.",
                          importer.ServiceDescriptions.Count, importer.Schemas.Count);

        // Generate a proxy client.
        importer.Style = ServiceDescriptionImportStyle.Client;

        // Generate properties to represent primitive values.
        importer.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;

        // Initialize a Code-DOM tree into which we will import the service.
        CodeNamespace nmspace = new CodeNamespace();
        CodeCompileUnit unit1 = new CodeCompileUnit();
        unit1.Namespaces.Add(nmspace);

        // Import the service into the Code-DOM tree. This creates proxy code
        // that uses the service.
        ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit1);

        if (warning == 0)
        {
            // Generate and print the proxy code in C#.
            CodeDomProvider provider1 = CodeDomProvider.CreateProvider("CSharp");
            
            // Compile the assembly with the appropriate references
            string[] assemblyReferences = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
            CompilerParameters parms = new CompilerParameters(assemblyReferences);
            CompilerResults results = provider1.CompileAssemblyFromDom(parms, unit1);
            
            foreach (CompilerError  oops in results.Errors)
            {
                Console.WriteLine("========Compiler error============");
                Console.WriteLine(oops.ErrorText);
            }

            //Invoke the web service method
            object o = results.CompiledAssembly.CreateInstance("WebService");
            Type t = o.GetType();
            Console.WriteLine(t.InvokeMember("HelloWorld", System.Reflection.BindingFlags.InvokeMethod, null, o, null)); 
        }
        else
        {
            // Print an error message.
            Console.WriteLine("Warning: " + warning);
        }
 }

You can see that there really isn't that much code required to dynamically invoke the web service method without requiring a pre-existing SoapHttpClientProtocol.  However, you will want to flesh out other details of this before considering putting this into an application, such as using intelligent caching to avoid the lookups and assembly generation.

If you are considering writing this type of code just to support development and testing efforts, then you should have a look at the free WebServiceStudio.  This is a great utility for dynamically creating web service proxies and invoking them based on WSDLs.  Once you play around with it as well as the code above, you can easily figure out how many of the features were implemented.  Here are a couple screen shots to whet your appetite.

WebServiceStudio allows you to dynamically invoke web services based on the WSDL description of the service. After invocation, you can also inspect the message trace.