July 07, 2006

Invoking web service without using proxy

This article created for someone but publishing here.......

Abstract

Traditionally, whenever any client application needs to invoke a web service it uses a proxy class generated using web service description language (WSDL). However there are certain situations where traditional approach is obsolete. This article discusses such situations where web service proxy is not much useful. Moreover this article demonstrates a way for invoking the web service without creating a proxy class. You can use example in this article to create a generic assembly which can be used with any web service without need for creating or using a proxy class.

Introduction

In this article you will see how to invoke web service without using a proxy class. Normally, whenever any client needs to access a web service, the first step is to create a proxy class. This proxy class can be generated using different utilities like WSDL.EXE or Visual Studio .NET using Web Service Description Language (WSDL). In VS .NET, when you add a new web reference, a proxy class is automatically created for your client application. This is a very simple way to access web service where WSDL plays an important role.

However there are certain situations where you can not create web service proxy or proxy is not much helpful to implement the complete solution.

In this article you will see a complete example which will demonstrate how to invoke web service without using a proxy class. In order to invoke web service without a proxy class, it uses WebRequest class and sends entire SOAP message as an HTTP request. From this article you will learn basics of WebRequest and its associated class and how to use them to invoke web service. Finally you will create an assembly which can be used with any web service without need of the proxy class.

clip_image002

Fig 1. Various ways to invoke a Web Service.

System Requirements

In this article you will create an example using Visual Studio 2003; however you can also implement same solution in Visual Studio 2005 (.NET Framework 2.0). This article has sample code and requires following to run the code:

  • A web server running on Windows XP professional, Windows 2000 or later
  • The .NET Framework version 1.x
  • VS.NET 2003

Installing and Compiling the Sample Code

The attached sample code has following two different solutions.

  1. Sample CurrencyHandler Web service code
  2. The WSInvoker assembly and TestClient console application.

In order to run CurrencyHandler web service you need to declare it in the IIS. The WSInvoker assembly and TestClient are combined in one solution and can be run directly.

Note that the attached code is slightly different than explained in this article and has additional comments and error handling.

Why not Web Service Proxy?

As discussed earlier, a web service proxy can be created using WSDL. WSDL defines a contract that lets a client invoke a remote service by describing the web service in terms the client understands.

Although the WSDL specification describes only HTTP for communicating with endpoints, many vendors supports additional protocols (such as JMS - Java Messaging Service). There are several occasions where WSDL.exe or Visual Studio cannot generate a web proxy, for example - the web service in Axis or something similar,

In .NET you can use proxy class with SOAP handlers to perform pre or post operations on SOAP messages, e.g. SOAP body encryption/compression. However for few clients it’s very difficult to write a code using SOAP handlers to implement custom SOAP formats. In such scenarios web service proxy is either obsolete or not much helpful.

To overcome proxy generation problems and to invoke web service you can send entire SOAP message as an HTTP request. In order to do that the WebRequest class provides necessary infrastructure. Normally this approach has several advantages/disadvantages compare to the web service proxy class which is illustrated in following table.

Sending SOAP as HTTP request

Easy to use with complex SOAP request which has SOAP body encryption/compression or non-standard WSDL formats.

One time code to invoke any web service.

Require additional coding for creating complete SOAP message.

Better approach if you are changing your service description often.

Using Web Services Proxy class

Requires complex SOAP Handler programming to extract and encrypt the SOAP body. May not work with non-standard WSDL formats.

Need to create a proxy for every web services.

No additional coding – just create instance of proxy and then invoke the web method.

Every time client needs a new proxy for changed service description.

With both approach, it is possible to make asynchronous web requests.

Sending a SOAP message as an HTTP request

The example in this article uses the WebRequest class and its derived classes for sending a SOAP message as an HTTP request. This section covers some basics of the WebRequest and its derived classes.

The WebRequest class

The WebRequest class is the abstract base class which makes request/response for accessing data from the Internet. In order to make request to a web server it uses URI (Uniform Resource Identifier). Since this class is abstract class, the actual behavior of instances is controlled by the derived class. This class can be derived depending on what type of protocol (e.g. HTTP/FTP) you want to impalement with a web server. For example you can derive this class in the HttpWebRequest class to perform HTTP specific communication where as it can be derived in the FileWebRequest class to perform file transfer operations. Note that the actual behavior (HTTP or FTP) of WebRequest instances at run time is determined by the descendant class returned by WebRequest.Create() method.

Since example in this article sends SOAP message using HTTP, you'll see how the HttpWebRequest class can be used to communicate with a server.

The HttpWebRequest class

The HttpWebRequest class supports properties and methods defined in WebRequest class. In addition this class also has its own specific properties and methods which can be used to communicate with a server using HTTP protocol. In following section you'll see some information on the HttpWebRequest which is specific to our example. This will help you to understand the code easily.

Creating an instance of the HttpWebRequest class

In order to create instance of HttpWebRequest class do not use the HttpWebRequest constructor. Instead use the WebRequest.Create() method to initialize the new HttpWebRequest instance as shown below:

// Create WebRequest object using web service URL

WebRequest wReq = WebRequest.Create(sUrl);

// Using the WebRequest instance create HttpWebRequest object

HttpWebRequest httpReq = (HttpWebRequest) wReq ;

This class supports following properties specific to our example:

The Method Property

This is overridden property from the WebRequest class. This property gets or sets method for the request. The request can be POST or GET. In our example this property is set to POST since we are posting the data to a web server.

httpReq.Method = "POST";

The ContentType Property

This is overridden property from WebRequest class and gets or sets the value of the Content-type HTTP header. This property determines the media type of the request. Since the web service request is SOAP envelope XML, this property is set to "text/xml”.

httpReq.ContentType = "text/xml";

The Headers Property

This is also overridden property from the WebRequest class. This property has a collection of the name/value pairs that make up the HTTP headers. You can add a new pair in collection using the Add() method as shown:

// Add SOAP action in header

httpReq.Headers.Add ("SOAPAction: " + sSoapAction);

In our example, the SOAPAction HTTP request header field can be used to exactly identify the operation on which service is being invoked. The value of the SOAPAction header provides a hint about how SOAPAction can be used and should be a URI identifying the “extended intent”. The format of the SOAPAction header to be the service namespace, followed by a forward slash, followed by the name of the operation, or urn:Example/sayHello.

An HTTP client MUST use this header field when issuing a SOAP HTTP Request.

The HttpWebRequest class supports following methods specific to our example:

The GetResponse() method

The GetResponse() method is overridden from the WebRequest class. This method sends a request to an Internet resource and returns instance of a WebResponse. However if the request has already been initiated then the GetResponse() method completes the request and returns instance of a WebResponse.

The WebResponse class is the abstract base class and therefore client applications do not create WebResponse objects directly. Usually instances of WebResponse are created by calling the GetResponse() method on a WebRequest instance as in our example:

WebResponse wResp = null;

StreamReader strmRdr = null;

string sResult ;

try {

// Get the response

wResp = httpReq.GetResponse () ;

The GetResponseStream() method

This method is WebResponse base class method and returns the data stream from the Internet resource.

// Get the response stream

Stream respStrm = wResp.GetResponseStream () ;

Closing the connection

It is necessary to close the connection and frees system resources once the client receives response from the Internet resource. You can use either WebResponse.Close() method or Stream.Close() method to close the connection.

Creating the Sample Application

The sample application in this article has three different applications:

  1. The CurrencyHandler web service
  2. The generic assembly – WSInvoker, to send HTTP request to the web service
  3. The client application – WSTester, to invoke web service using the generic assembly

The CurrencyHandler web service

This article assumes that you are familiar with a web service in VS .NET. However in order to demonstrate our example you’ll create a simple web service named CurrencyHandler in Visual Studio 2003. The CurrencyHandler web service performs some tasks related to currency conversion and has two interfaces:

  1. The CurrencyRate interface, which takes country code and returns currency rate against US dollar. This interface eventually calls CurrencyRateForUSDollar function which performs currency conversion.
  1. The ProductPrice interface takes product code and country code and returns price of a product against US dollar.

Complete code for the CurrencyHandler web service is illustrated below:

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Diagnostics;

using System.Web;

using System.Web.Services;

namespace CurrencyHandler {

/// <summary>

/// The Converter service provides currency conversion services.

/// </summary>

[WebService(Namespace="http://example.asptoday.com/")]

public class Converter : System.Web.Services.WebService {

#region Component Designer generated code

/// <summary>

/// Default constructor

/// </summary>

public Converter() {

InitializeComponent();

}

//Required by the Web Services Designer

private IContainer components = null;

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent() {

}

/// <summary>

/// Clean up any resources being used.

/// </summary>

protected override void Dispose( bool disposing ) {

if(disposing && components != null) {

components.Dispose();

}

base.Dispose(disposing);

}

#endregion

/// <summary>

/// Returns Currency Rate for given country code

/// </summary>

[WebMethod]

public double CurrencyRate(string CountryCode) {

return CurrencyRateForUSDollar(CountryCode);

}

/// <summary>

/// Returns Product price in US dollar for given product and country code

/// </summary>

[WebMethod]

public double ProductPrice(int productCode, string CountryCode) {

double dProductPrice = -1;

switch (productCode) {

case 1:

dProductPrice = 10 * CurrencyRateForUSDollar(CountryCode);

break;

case 2:

dProductPrice = 20 * CurrencyRateForUSDollar(CountryCode);

break;

case 3:

dProductPrice = 30 * CurrencyRateForUSDollar(CountryCode);

break;

case 4:

dProductPrice = 40 * CurrencyRateForUSDollar(CountryCode);

break;

}

return dProductPrice;

}

/// <summary>

/// Returns currency rate in US dollar for given country

/// code. If country code is not present then this method returns -1.

/// </summary>

private double CurrencyRateForUSDollar(string CountryCode) {

double dCurrencyRate = -1;

switch (CountryCode) {

case "EUR": // EURO

dCurrencyRate = 0.779775;

break;

case "CAD": // Canadian Dollar

dCurrencyRate = 1.13020;

break;

case "GBP": // British Pound

dCurrencyRate = 0.533020;

break;

case "AUS": // Australian Dollar

dCurrencyRate = 1.30677;

break;

}

return dCurrencyRate;

}

}

}

In above example, the CurrencyRateForUSDollar() function performs currency conversion for only four countries with fixed currency conversion rate.

Note:

While writing web service, always make sure that you have given the default namespace for your web service. Purpose of the default namespace is to distinguish it from other services on the Web. However while invoking web service using HTTP request, the default namespace is used as SOAP action with method name. If you do not provide the default namespace then each SOAP action is created using the default URI - http://tempuri.org/. In such case SOAP action for web methods ProductPrice and CurrencyRate looks as shown below:

http://tempuri.org/ProductPrice

http://tempuri.org/CurrencyRate

Although the default namespace look like URLs, they need not point to actual resources on the Web. The default namespace can be changed using the WebService attribute's Namespace property as shown:

[WebService(Namespace="http://example.asptoday.com/")]

public class Converter : System.Web.Services.WebService {

In above example http://example.asptoday.com/ is the default namespace. SOAP action URI for web methods ProductPrice and CurrencyRate looks as shown below:

http://example.asptoday.com/ProductPrice

http://example.asptoday.com/CurrencyRate

The generic assembly – WSInvoker

While writing the web service invoker you’ll create a generic assembly - WSInvoker which can be used with any web service. In order to invoke web service using the HTTPWebRequest object we need following three parameters:

  1. Web Service URL
  2. SOAP Action
  3. SOAP Envelope

In our example you can also access SOAP action and SOAP envelope by browsing the web service URL. To do that browse the CurrencyHandler web service it looks as shown below:

clip_image004

Fig 2. CureencyHandler web service in Internet Explorer

The above page shows links for various interfaces and service description. If you click on ProductService then you will get the following page:

clip_image006

Fig 3. SOAP details for CureencyHandler web service

To create WSInvoker assembly, create a new assembly project named ‘WSInvoker’ in VS2003 and rename default class Class1 to ‘Invoke’.

Add the following namespaces which are require to perform HTTP post and to handle request/response streams.

using System;

using System.Net;

using System.IO;

using System.Text ;

Next change the default constructor of Invoke class as shown below. The constructor accepts web service URL which is used for sending SOAP request.

namespace WSInvoker {

public class Invoke {

// Holds web service URL

private string m_sURL;

/// <summary>

/// Constructor for Invoke class.

/// </summary>

public Invoke(string sUrl) {

m_sURL = sUrl;

}

Next add a method SendSoapMessage which will take two parameters one for SOAP action URI and another SOAP message as shown:

public string SendSoapMessage(string sSoapAction,string sSoapMsg) {

The SendSoapMessage method creates necessary objects required to post a request and to get a response from the web service. The WebRequest object instance is created using the Create method and web service URL.

// Create WebRequest object using web service URL

WebRequest wReq = WebRequest.Create(m_sURL);

As discussed earlier, the WebRequest is an abstract class that can be used to create an instance of the HTTPWebRequest class.

// Using the WebRequest instance create HttpWebRequest object

HttpWebRequest httpReq = (HttpWebRequest) wReq ;

Next set properties for Method, Content Type and add SOAP Action in the header.

// Assign Method as "Post" since we are posting the request

httpReq.Method = "POST";

// Cintenttype for SOAP envelope id text/xml

httpReq.ContentType = "text/xml";

// Add SOAP action in header

httpReq.Headers.Add ("SOAPAction: " + sSoapAction);

The SOAP message then converted in to stream using StreamWriter object. The GetRequestStream () method initiates a request to send data to the Internet resource and Write() method actually writes stream to underlying HTTP connection.

// Get the request stream

Stream sendStream = httpReq.GetRequestStream ();

/// 2.0 put the Request text into the request stream

// create a StreamWriter object and write SOAP envelope string into it

StreamWriter strmWrtr = new StreamWriter(sendStream);

strmWrtr.Write (sSoapMsg);

strmWrtr.Close ();

The abstract WebResponse class can be used to process response. Since WebResponse is abstract class client applications do not create WebResponse objects directly, they are created by calling the GetResponse() method on a WebRequest instance as shown:

// 3.0 make the request and get the response.

WebResponse wResp = null;

StreamReader strmRdr = null ;

string sResult = string.Empty;

try {

// Get the response

wResp = httpReq.GetResponse () ;

Further the WebResponse instance is used with GetResponseStream () method to read the response stream. Finally the stream is converted back in to string to get XML formatted web response.

// Get the response stream

Stream respStrm = wResp.GetResponseStream () ;

// Create StreamReader to read the stream

strmRdr = new StreamReader (respStrm) ;

// read the stream in to the string

sResult = strmRdr.ReadToEnd () ;

}

catch (WebException wex) {

throw wex;

}

catch (Exception ex) {

throw ex;

}

// 4,0 Finally return the xml response in string format

return sResult ;

}

Once you complete coding compile the code and create WSInvoker assembly.

Note:

Since we are posting SOAP envelope as an HTTP Post, consideration of using SOAP handlers at client side is not applicable. SOAP handlers at client side are useful when you use proxy. SOAP Handlers are used to perform additional processing on SOAP envelope (e.g. SOAP body encryption, inserting SOAP header etc.) However with HTTP post approach we have full control on SOAP envelope and therefore it easy to use direct HTTP post instead of SOAP handlers. Note that SOAP handlers at server side won’t affect if you use any method for invoking web service.

The client application – WSTester

To test WSInvoker generic assembly we need to create a web service client. Note that programming for web service client using direct HTTP module is different than using web service proxy. With web service proxy you can easily invoke web service by using proxy instance and web method name. However the client for invoking web service using direct HTTP call requires a complete SOAP message. In this section you will see how to create a client application which will use WSInvoker assembly to invoke web service using HTTP post.

To start create TestClient console application in VS 2003. Rename the default Class1 to Tester and add reference to WSInvoker assembly. While using HTTP post method you need to send complete SOAP message to web service. However most of SOAP message is constant except the SOAP body, which changes according to web method and parameters. Therefore in our example we have declared most of the SOAP message part as constant strings.

In the following code, the SOAP_START string has beginning of the SOAP message whereas SOAP_END has the string which is required to complete SOAP message. The SOAP body is dynamic and will be seated within SOAP_START and SOAP_END strings. Since web service URL is fixed it is also declared as constant.

using System;

namespace TestClient {

/// <summary>

/// Test Class for invoking Web Service using WSInvoker assembly.

/// </summary>

class Tester {

// Starting SOAP start message

private const string SOAP_START = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +

"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/ 2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +

"<soap:Body>";

// Ending SOAP message

private const string SOAP_END = "</soap:Body></soap:Envelope>";

// Web service URL

private const string WS_URL = "http://localhost/CurrencyHandler/Converter.asmx";

In the Main() method the SOAP action URI indicates which web service method you want to invoke. In this example you will first invoke CurrencyRate() method and then the ProductPrice() method.

To start define SOAP action URI for CurrencyRate() method and then create SOAP body.

[STAThread]

static void Main(string[] args) {

try {

/// 1. Invoking CurrencyRate method

// Define SOAP action (decides which method to invoke)

string sSOAPAction = "http://example.asptoday.com/CurrencyRate";

// Create SOAP Body

string SOAP_Body = "<CurrencyRate xmlns=\"http://example.asptoday.com/\">" +

"<CountryCode>GBP</CountryCode>" +

"</CurrencyRate>";

Note: The biggest advantage of this method is you can perform various operations like compression and encryption on SOAP body very easily. You can call your compression or encryption routine before creating complete SOAP message.

Next the complete SOAP message is created using SOAP constants and SOAP body.

// Complete SOAP message

string sSOAPMessage = SOAP_START + SOAP_Body + SOAP_END;

Finally an instance of WSInvoker is created by passing web service URL and then SendSoapMessage() method is invoked by passing SOAP action URI and SOAP message.

// Invoke web service by using WSInvoker

WSInvoker.Invoke oInvoke = new WSInvoker.Invoke(WS_URL);

Console.WriteLine ("Invoking CurrencyRate method. Output Response is - \n");

// Send SOAP message and print the response

Console.WriteLine (oInvoke.SendSoapMessage(sSOAPAction,sSOAPMessage));

In similar fashion the ProductPrice method is called and output response is displayed on console.

// Define SOAP action (decides which method to invoke)

sSOAPAction = "http://example.asptoday.com/ProductPrice";

// Create SOAP Body for ProductPrice nethod

SOAP_Body = "<ProductPrice xmlns=\"http://example.asptoday.com/\">" +

"<productCode>2</productCode>" +

"<CountryCode>CAD</CountryCode>" +

"</ProductPrice>";

// Complete SOAP message

sSOAPMessage = SOAP_START + SOAP_Body + SOAP_END;

Console.WriteLine("\n");

Console.WriteLine ("Invoking ProductPrice method. Output Response is - \n");

// Send SOAP message and print the response

Console.WriteLine (oInvoke.SendSoapMessage(sSOAPAction,sSOAPMessage));

Lastly compile the project and run the application. The output of TestClient application looks as shown below:

clip_image008

Fig 4.Output of TestClient

Further Work

You can extend this application to send compressed or encrypted SOAP message to the web service. In such case you need to write SOAP handler at web service side to decompress and decrypt SOAP body. If web service is also sending compressed and/or encrypted message then you can write code to decompress/decrypt at client side.

Conclusion

In this article you have seen how to invoke web service using direct HTTP post. This method is useful where traditional SOAP proxy is not useful or when you want to send encrypted/compressed SOAP body without using SOAP handler. The generic WSInvoker assembly can be used with any web service without need of proxy service.