Creating a mock webservice based on a wsdl

Recently, I encountered an implementation challenge. The application I was working on needed to communicate with a third party webservice that was not reachable during the development. The application would be able to access the webservice in the user acceptance environment for the first time. However, I did get the webservice wsdl. In order to make the development easier, I decided to create a mock webservice that would simulate the original third party webservice behavior.
Reverse engineering the webservice

I had a wsdl which described the webservice interface. Based on this information, I could have manually created a mock webservice. This may be trivial for a simple interface, but for more complex webservices it is impractical. Luckily, there is a tool called Web Service Description Utility, a.k.a. wsdl.exe. It is capable of automating the entire process. It generates a .NET code (C#, VB, etc) from the wsdl. The wsdl.exe utility is part of Visual Studio. It is located under C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin or it can be invoked from Visual Studio command prompt:

The wsdl I received from the client looked something like this:

<wsdl:definitions targetnamespace="TestService" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:tns="TestService" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <s:schema elementformdefault="qualified" targetnamespace="TestService">
      <s:element name="HelloWorld">
        <s:complextype>
          <s:sequence>
            <s:element maxoccurs="1" minoccurs="0" name="cc" type="tns:CustomClass">
          </s:element>
        </s:sequence>
      </s:complextype>
      <s:complextype name="CustomClass">
        <s:sequence>
          <s:element maxoccurs="1" minoccurs="0" name="Name" type="s:string">
          <s:element maxoccurs="1" minoccurs="0" name="addressInfo" type="tns:Address">
        </s:element>
      </s:element>
      <s:complextype name="Address">
        <s:sequence>
          <s:element maxoccurs="1" minoccurs="0" name="Street" type="s:string">
          <s:element maxoccurs="1" minoccurs="1" name="Number" nillable="true" type="s:int">
        </s:element>
      </s:element>
      <s:element name="HelloWorldResponse">
        <s:complextype>
          <s:sequence>
            <s:element maxoccurs="1" minoccurs="0" name="HelloWorldResult" type="s:string">
          </s:element>
        </s:sequence>
      </s:complextype>
    </s:element>
  </s:sequence>
  <wsdl:message name="HelloWorldSoapIn">
    <wsdl:part element="tns:HelloWorld" name="parameters">
  </wsdl:part>
  <wsdl:message name="HelloWorldSoapOut">
    <wsdl:part element="tns:HelloWorldResponse" name="parameters">
  </wsdl:part>
  <wsdl:porttype name="Service1Soap">
    <wsdl:operation name="HelloWorld">
      <wsdl:input message="tns:HelloWorldSoapIn">
      <wsdl:output message="tns:HelloWorldSoapOut">
    </wsdl:output>
  </wsdl:input>
  <wsdl:binding name="Service1Soap" type="tns:Service1Soap">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http">
    <wsdl:operation name="HelloWorld">
      <soap:operation soapaction="TestService/HelloWorld">
      <wsdl:input>
        <soap:body use="literal">
      </soap:body>
      <wsdl:output>
        <soap:body use="literal">
      </soap:body>
    </wsdl:output>
  </wsdl:input>
  <wsdl:binding name="Service1Soap12" type="tns:Service1Soap">
    <soap12:binding transport="http://schemas.xmlsoap.org/soap/http">
    <wsdl:operation name="HelloWorld">
      <soap12:operation soapaction="TestService/HelloWorld">
      <wsdl:input>
        <soap12:body use="literal">
      </soap12:body>
      <wsdl:output>
        <soap12:body use="literal">
      </soap12:body>
    </wsdl:output>
  </wsdl:input>
  <wsdl:service name="Service1">
    <wsdl:port binding="tns:Service1Soap" name="Service1Soap">
      <soap:address location="http://localhost/testservice/service.asmx">
    </soap:address>
    <wsdl:port binding="tns:Service1Soap12" name="Service1Soap12">
      <soap12:address location="http://localhost/testservice/service.asmx">
    </soap12:address>
  </wsdl:port>
</wsdl:port>
</wsdl:service></soap12:operation></wsdl:operation></soap12:binding></wsdl:binding></soap:operation></wsdl:operation></soap:binding></wsdl:binding></wsdl:operation></wsdl:porttype></wsdl:message></wsdl:message></s:complextype></s:sequence></s:complextype></s:element></s:schema></wsdl:types></wsdl:definitions>

In order to generate the mock webservice, I invoked the wsdl.exe utility passing it the wsdl file name (webservice.wsdl) and output path::

c:\Program Files\Microsoft Visual Studio 9.0\VC>wsdl c:\webservice.wsdl /out:c:\

The output was Service1.cs file that can be added to a new webservice .C# project:

//------------------------------------------------------------------------------
// 
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.3074
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// 
//------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;

// 
// This source code was auto-generated by wsdl, Version=2.0.50727.3038.
// 


/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(Name="Service1Soap", Namespace="TestService")]
public partial class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol {
    
    private System.Threading.SendOrPostCallback HelloWorldOperationCompleted;
    
    /// 
    public Service1() {
        this.Url = "http://localhost/testservice/service.asmx";
    }
    
    /// 
    public event HelloWorldCompletedEventHandler HelloWorldCompleted;
    
    /// 
    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("TestService/HelloWorld", RequestNamespace="TestService", ResponseNamespace="TestService", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    public string HelloWorld(CustomClass cc) {
        object[] results = this.Invoke("HelloWorld", new object[] {
                    cc});
        return ((string)(results[0]));
    }
    
    /// 
    public System.IAsyncResult BeginHelloWorld(CustomClass cc, System.AsyncCallback callback, object asyncState) {
        return this.BeginInvoke("HelloWorld", new object[] {
                    cc}, callback, asyncState);
    }
    
    /// 
    public string EndHelloWorld(System.IAsyncResult asyncResult) {
        object[] results = this.EndInvoke(asyncResult);
        return ((string)(results[0]));
    }
    
    /// 
    public void HelloWorldAsync(CustomClass cc) {
        this.HelloWorldAsync(cc, null);
    }
    
    /// 
    public void HelloWorldAsync(CustomClass cc, object userState) {
        if ((this.HelloWorldOperationCompleted == null)) {
            this.HelloWorldOperationCompleted = new System.Threading.SendOrPostCallback(this.OnHelloWorldOperationCompleted);
        }
        this.InvokeAsync("HelloWorld", new object[] {
                    cc}, this.HelloWorldOperationCompleted, userState);
    }
    
    private void OnHelloWorldOperationCompleted(object arg) {
        if ((this.HelloWorldCompleted != null)) {
            System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
            this.HelloWorldCompleted(this, new HelloWorldCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
        }
    }
    
    /// 
    public new void CancelAsync(object userState) {
        base.CancelAsync(userState);
    }
}

/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="TestService")]
public partial class CustomClass {
    
    private string nameField;
    
    private Address addressInfoField;
    
    /// 
    public string Name {
        get {
            return this.nameField;
        }
        set {
            this.nameField = value;
        }
    }
    
    /// 
    public Address addressInfo {
        get {
            return this.addressInfoField;
        }
        set {
            this.addressInfoField = value;
        }
    }
}

/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="TestService")]
public partial class Address {
    
    private string streetField;
    
    private System.Nullable numberField;
    
    /// 
    public string Street {
        get {
            return this.streetField;
        }
        set {
            this.streetField = value;
        }
    }
    
    /// 
    [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
    public System.Nullable Number {
        get {
            return this.numberField;
        }
        set {
            this.numberField = value;
        }
    }
}

/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
public delegate void HelloWorldCompletedEventHandler(object sender, HelloWorldCompletedEventArgs e);

/// 
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class HelloWorldCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {
    
    private object[] results;
    
    internal HelloWorldCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : 
            base(exception, cancelled, userState) {
        this.results = results;
    }
    
    /// 
    public string Result {
        get {
            this.RaiseExceptionIfNecessary();
            return ((string)(this.results[0]));
        }
    }
}

As you can see, all the complex types were generated including the asynchronous methods. The code can be cleaned up by removing irrelevant methods. That gives us:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
namespace TestService
{
    [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Web.Services.WebServiceBindingAttribute(Name = "ServiceSoap", Namespace = "TestService")]
    public partial class Service : System.Web.Services.Protocols.SoapHttpClientProtocol
    {
        public Service()
        {
            this.Url = "http://localhost/testservice/service.asmx";
        }
        [System.Web.Services.Protocols.SoapDocumentMethodAttribute("TestService/HelloWorld", RequestNamespace = "TestService", ResponseNamespace = "TestService", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
        public string HelloWorld(CustomClass cc)
        {
            return string.Empty;
        }
    }
    [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace = "TestService")]
    public partial class CustomClass
    {
        private string nameField;
        private Address addressInfoField;
        public string Name
        {
            get
            {
                return this.nameField;
            }
            set
            {
                this.nameField = value;
            }
        }
        public Address addressInfo
        {
            get { return this.addressInfoField; }
            set { this.addressInfoField = value; }
        }
    }
    [System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace = "TestService")]
    public partial class Address
    {
        private string streetField;
        private System.Nullable numberField;
        public string Street
        {
            get{return this.streetField;}
            set{this.streetField = value;}
        }
        [System.Xml.Serialization.XmlElementAttribute(IsNullable = true)]
        public System.Nullable Number
        {
            get{return this.numberField;}
            set{this.numberField = value;}
        }
    }
}

This simple and clean code will return an empty string on HelloWorld call. This is of course just an example, but it can be modified to return any value, or even perform some business logic based on the input.
Conclusion
It is a common situation that we need to integrate with another external or internal component that is either not ready or not reachable from the development environment. With the help of wsdl.exe utility we can quickly generate a mock webservice.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.