Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#

Web Service Communicator

Rate me:
Please Sign up or sign in to vote.
4.92/5 (13 votes)
9 Feb 2011CDDL5 min read 56.8K   2.4K   65   19
Dynamic web service invocation and interoperability

Introduction

The purpose of this article is to illustrate the potential of dynamic web service invocation made possible by Reflection. This approach consists in: downloading the WSDL file at runtime, creating the proxy class, compiling it into an assembly and executing the methods on it. This allows you to dynamically invoke methods from a web service that aren’t known at build time and potentially build pre-configured routines for message exchange between multiple services.

Background

I had been looking for a couple of days about a way to dynamically invoke a web service without being bound to a prior knowledge of its contract at build-time. As a result of my research, I finally came across http://www.crowsprogramming.com/archives/66, which follows the approach that I've explained in the introduction. I've built a simple Windows application which shows the potential of that approach to make web services communicate between them without a compile-time knowledge of their contracts.

Using the Code

The sample solution includes two projects:

  • WSCommunicator.SL - Sample service layer
  • WSCommunicator.WinForm - Sample Windows Forms application

The Service layer contains a simple web service which returns some sample data. These are its methods:

C#
[WebMethod]
public List<Person> GetAllPersons()

[WebMethod]
public List<string> GetAllPersonsNames()

[WebMethod]
public int GetPersonsAge(string name, string surname)

[WebMethod]
public Address FindPersonsAdress(string name, string surname)

We will use these methods later to see how dynamic message exchange works. The WinForm project contains one WinForm (WSCommunicatorForm) and some classes which are used for the dynamic web service invocation.

The most important class is WSInvoker which uses System.Reflection to dynamically compile the web service descriptions into packages and call their methods. This is how it achieves this:

C#
private Assembly BuildAssemblyFromWSDL(Uri webServiceUri)
{
    if (String.IsNullOrEmpty(webServiceUri.ToString()))
        throw new Exception("Web Service Not Found");

    XmlTextReader xmlreader = new XmlTextReader(webServiceUri.ToString() + "?wsdl");

    ServiceDescriptionImporter descriptionImporter = 
			BuildServiceDescriptionImporter(xmlreader);

    return CompileAssembly(descriptionImporter);
}

private ServiceDescriptionImporter BuildServiceDescriptionImporter
					( XmlTextReader xmlreader )
{
    // make sure xml describes a valid wsdl
    if (!ServiceDescription.CanRead(xmlreader))
        throw new Exception("Invalid Web Service Description");

    // parse wsdl
    ServiceDescription serviceDescription = ServiceDescription.Read(xmlreader);

    // build an importer, that assumes the SOAP protocol, 
    // client binding, and generates properties
    ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
    descriptionImporter.ProtocolName = "Soap";
    descriptionImporter.AddServiceDescription(serviceDescription, null, null);
    descriptionImporter.Style = ServiceDescriptionImportStyle.Client;
    descriptionImporter.CodeGenerationOptions = 
	System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;

    return descriptionImporter;
}

private Assembly CompileAssembly(ServiceDescriptionImporter descriptionImporter)
{
    // a namespace and compile unit are needed by importer
    CodeNamespace codeNamespace = new CodeNamespace();
    CodeCompileUnit codeUnit = new CodeCompileUnit();

    codeUnit.Namespaces.Add(codeNamespace);

    ServiceDescriptionImportWarnings importWarnings = 
		descriptionImporter.Import(codeNamespace, codeUnit);

    if (importWarnings == 0) // no warnings
    {
        // create a c# compiler
        CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");

        // include the assembly references needed to compile
        string[] references = { "System.Web.Services.dll", "System.Xml.dll" };

        CompilerParameters parameters = new CompilerParameters(references);

        // compile into assembly
        CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit);

        foreach (CompilerError oops in results.Errors)
        {
            // trap these errors and make them available to exception object
            throw new Exception("Compilation Error Creating Assembly!!\r\n" + 
				oops.ErrorText);
        }

        // all done....
        return results.CompiledAssembly;
    }
    else
    {
        // warnings issued from importers, something wrong with WSDL
        throw new Exception("Invalid WSDL");
    }
}

First, a ServiceDescriptionImporter is built by parsing the web service WSDL. This is then compiled into an assembly which is ready to be used. This is how the WSInvoker calls its methods:

C#
public T InvokeMethod<T>( string serviceName, string methodName, params object[] args )
{
    // create an instance of the specified service
    // and invoke the method
    object obj = this.webServiceAssembly.CreateInstance(serviceName);

    Type type = obj.GetType();

    return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);
}

In order to make data visualization possible on the client, an XML serializer (CustomSerializer class) is used which serializes the WS Output and loads a dataset with it, when possible. Here are the serialization-deserialization methods:

C#
public String SerializeObject(Object pObject, Type myType)
{
    try
    {
        String XmlizedString = null;
        MemoryStream memoryStream = new MemoryStream();
        XmlSerializer xs = new XmlSerializer(myType);
        XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
        xs.Serialize(xmlTextWriter, pObject);
        memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
        XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray());

        XmlizedString = changeInvalidCharacters(XmlizedString);
        
        return XmlizedString.Substring(1);
    }
    catch (Exception ex) 
    { 
        throw new Exception("Serialization error!\r\n" + ex.Message); 
    }
}

public Object DeserializeObject(String pXmlizedString, Type myType)
{
    try
    {
        XmlSerializer xs = new XmlSerializer(myType);
        MemoryStream memoryStream = new MemoryStream
				(StringToUTF8ByteArray(pXmlizedString));
        XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
        return xs.Deserialize(memoryStream);
    }
    catch (Exception ex)
    {
        throw new Exception("Deserialization error!\r\n" + ex.Message);
    }
}

The CustomSerializer is also used to serialize/deserialize the application configuration file which we're going to examine further on. The WSCommunicatorForm class defines the user interface. This is how it looks:

winform.gif

The window is divided in two main parts: the left one contains the input web service configuration, the right one contains the output configuration and the input-output parameter mapping. When a valid web service URL is entered into the left top textbox (Input web service) and the corresponding Load button is pressed, the Form calls the BuildAssemblyFromWSDL method of the WSInvoker class, which parsed the web service descriptor and acquires information on its definition and methods. The left part of the form is thus populated with the input web service information.

By changing the web method selection, the corresponding return type information is updated to display the details of the currently selected method output. There is also a textbox in which the input web service method parameters must be written separated by semicolon to allow a correct invocation of the web method. A click on the left Invoke button invokes the selected method with the specified input parameters and displays the result on the left DataGrid.

I haven't worked much on the data visualization aspect, as this is just a test application, but you should be able to see a result for most types of output. When the result is displayed, the Available Output Fields ListBox on the right gets populated with the column definitions of the left DataGridView. These items will later be mapped to the output web service input parameters.

If we now digit a valid web service URL into the right top textbox (Output web service) and press the corresponding Load button, the right part of the form will be populated with the details of the output web service. This web service can even be the input web service, or any other which is reachable by the client machine. In the illustrated example, the output web service is the same as the input one.

This is how the window looks now:

winform-data.gif

In order to make the message exchange possible between the two web services, we need to configure correctly the input-output mapping. We can do this by using the arrows on the right side which allow us to move into the Input parameters ListBox the available output fields obtained by the input web service.

The parameters can also be moved up and down. The right-most ListBox on the mapping section (Input types) contains the input parameter types for the currently selected output web service method. The number and the type of the input parameters must correspond to these types. Once the mapping is done, we're ready for the "magic" to happen: Press the right Invoke button to pass the input-web-service-output to the output web service method and see its output on the right DataGridView.

This is how the window looks now:

winform-data-output.gif

The application can save/load its configuration from XML. In order to make this simpler and make a creative use of the CustomSerializer, I created some data structures in the WSComConfig.cs file as follows:

C#
public struct WSComConfig
{
    public WSConfig wsInputConfig { get; set; }
    public WSConfig wsOutputConfig { get; set; }
}
public struct WSConfig
{
    public string wsAddress { get; set; }
    public string wsName { get; set; }
    public string wsMethod { get; set; }
    public List<string> wsParams { get; set; }
}

The CustomSerializer serializes this into (and deserializes this from):

XML
<?xml version="1.0" encoding="utf-8"?>
<WSComConfig xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance 
	xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsInputConfig>
    <wsAddress>http://localhost:51425/SampleService.asmx</wsAddress>
    <wsName>SampleService</wsName>
    <wsMethod>GetAllPersons</wsMethod>
    <wsParams />
  </wsInputConfig>
  <wsOutputConfig>
    <wsAddress>http://localhost:51425/SampleService.asmx</wsAddress>
    <wsName>SampleService</wsName>
    <wsMethod>FindPersonsAdress</wsMethod>
    <wsParams>
      <string>Name</string>
      <string>Surname</string>
    </wsParams>
  </wsOutputConfig>
</WSComConfig>

This makes configuration loading and saving extremely easy. For this test application, the click handler on the Load configuration button automates the input-output configuration and the invocation of the input web service method, but leaves the final Invoke to the user. It obviously takes nothing to automate that too. This illustrates the potential of this approach to create automated web service message exchange routines!

Points of Interest

I learnt a lot through this project about dynamic web service invocation and explored dynamic web service mapping. Although the sample application illustrates communication between only two web services, this concept can be extended to an unlimited number of web services which can be configured to create a business process workflow.

I have previously worked on the Java Open ESB and have been fascinated by its technology. This is surely less than 1% complex compared to it, but it makes web service interoperability possible.

Notes

I'd be very happy if someone takes this idea further.

History

  • 9th February, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Engineer
United Kingdom United Kingdom
I've been involved in object-oriented software development since 2006, when I graduated in Information and TLC Engineering. I've been working for several software companies / departments, mainly on Microsoft and Sun/Oracle technologies. My favourite programming language is C#, next comes Java.
I love design patterns and when I need to resolve a problem, I try to get the best solution, which is often not the quickest one.

"On the best teams, different individuals provide occasional leadership, taking charge in areas where they have particular strengths. No one is the permanent leader, because that person would then cease to be a peer and the team interaction would begin to break down. The structure of a team is a network, not a hierarchy ..."
My favourite team work quotation by DeMarco - Lister in Peopleware

Comments and Discussions

 
QuestionTyped Dataset Problem Pin
Rohit Kumar Mumbai20-May-13 1:47
Rohit Kumar Mumbai20-May-13 1:47 
AnswerRe: Typed Dataset Problem Pin
Erion Pici20-May-13 4:24
Erion Pici20-May-13 4:24 
GeneralRe: Typed Dataset Problem Pin
Rohit Kumar Mumbai20-May-13 6:11
Rohit Kumar Mumbai20-May-13 6:11 
GeneralRe: Typed Dataset Problem Pin
Erion Pici21-May-13 1:10
Erion Pici21-May-13 1:10 
GeneralRe: Typed Dataset Problem Pin
Rohit Kumar Mumbai22-May-13 22:20
Rohit Kumar Mumbai22-May-13 22:20 
GeneralMy vote of 5 Pin
Amir Mehrabi-Jorshari21-Feb-11 1:24
Amir Mehrabi-Jorshari21-Feb-11 1:24 
GeneralRe: My vote of 5 Pin
Erion Pici24-Feb-11 4:28
Erion Pici24-Feb-11 4:28 
GeneralRe: My vote of 5 Pin
zainbhimani19-May-11 19:06
zainbhimani19-May-11 19:06 
GeneralRe: My vote of 5 Pin
Erion Pici19-May-11 20:56
Erion Pici19-May-11 20:56 
GeneralMy vote of 5 Pin
flexmis16-Feb-11 19:41
flexmis16-Feb-11 19:41 
GeneralRe: My vote of 5 Pin
Erion Pici16-Feb-11 20:54
Erion Pici16-Feb-11 20:54 
GeneralMy vote of 5 Pin
Thomas A14-Feb-11 21:34
Thomas A14-Feb-11 21:34 
GeneralRe: My vote of 5 Pin
Erion Pici16-Feb-11 20:54
Erion Pici16-Feb-11 20:54 
GeneralMy vote of 5 Pin
Venkatesh Mookkan10-Feb-11 16:02
Venkatesh Mookkan10-Feb-11 16:02 
GeneralRe: My vote of 5 Pin
Erion Pici11-Feb-11 2:59
Erion Pici11-Feb-11 2:59 
GeneralI like that, my colleague wrote something like this too. This is cool and high fives and bookmarked Pin
Sacha Barber10-Feb-11 0:52
Sacha Barber10-Feb-11 0:52 
GeneralRe: I like that, my colleague wrote something like this too. This is cool and high fives and bookmarked Pin
Erion Pici10-Feb-11 0:54
Erion Pici10-Feb-11 0:54 
GeneralMy vote of 5 Pin
fjdiewornncalwe9-Feb-11 6:40
professionalfjdiewornncalwe9-Feb-11 6:40 
GeneralRe: My vote of 5 Pin
Erion Pici10-Feb-11 0:54
Erion Pici10-Feb-11 0:54 

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

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