Export (0) Print
Expand All

C# and the Web: Writing a Web Client Application with Managed Code in the Microsoft .NET Framework

MSDN Magazine

C# and the Web: Writing a Web Client Application with Managed Code in the Microsoft .NET Framework

Avi Ben-Menahem
This article assumes you're familiar with C#, XML, and the .NET Framework
Level of Difficulty     1   2   3 
Download the code for this article: CWeb.exe (40KB)
Browse the code for this article at Code Center: HTTPXML
SUMMARY When the author wanted to build a middleware Web client to connect to other applications over the Internet, he realized that the XMLHttpRequest COM object was not sufficient for his purposes. In order to build a Web client using managed code, the author had to use the HTTPWebRequest and HTTPWebResponse classes provided by the Microsoft .NET framework. These classes are used in the sample project as a substitute for the less powerful XMLHttpRequest COM object, allowing the author to build a full-featured Web client. They also take advantage of all the benefits that the CLR and managed code have to offer.
It began as a simple project—all projects do. My assignment was to write a demo for Tech•Ed 2001 in Eilat, Israel. It wasn't supposed to be something too complicated or something I haven't done before, just a simple example of how to query the Universal Description, Discovery, and Integration (UDDI) repository, and, of course, it had to be written using C# and the Microsoft® .NET Framework. I thought I'd just download the UDDI SDK, write some code around it, and I'd be home free.
      About five minutes later, reality set in. I couldn't use the UDDI SDK since it is a COM-based SDK and I needed a managed code-based SDK. And even if I wanted to write my own UDDI SDK, the good old MSXML XMLHttpRequest object, which can be used as a Web client to send and receive HTTP messages, wouldn't do anymore. Again, the problem was that it's a COM object. Even though it's possible to write managed code that will use standard COM objects (also known as unmanaged code) I decided to go the whole nine yards and write it all from the ground up using C#. That night I found myself staring at my laptop in disbelief; I needed to write my own Web client from scratch.

What is a Web Client?

      As you know, accessing data from the Internet can be accomplished in several ways. Browsers are one type of Web client that enable you to send requests to a particular Uniform Resource Identifier (URI) and receive the response in a standard format. Browsers are great Web clients for human-readable content; they display the results in HTML format allowing you to read the text and see the images. Performing more advanced tasks such as extracting a stock price from a Web page can be pretty difficult and techniques such as page scraping aren't trivial to implement. So how can applications connect to other applications via the Web?
      The answer to that is the XMLHttpRequest object, which is another kind of Web client. Using this object you can send and receive information to and from a Web resource in various formats, as shown in the following code:
var httpOb = new ActiveXObject("Microsoft.XMLHTTP");

httpOb.Open("POST","http://uddi.microsoft.com/inquire", false);
httpOb.send(MySOAPMessage);
var Ans = httpOb.GetResponseXML();
      The .NET Framework has a request/response model for accessing data from the Internet. Applications that use the request/response model can request data from the Internet in a protocol-agnostic manner—the application works with instances of a Web client (the WebRequest and WebResponse classes), while the details of the request are carried out by protocol-specific descendant classes. The .NET Framework provides a rich class structure for enabling the request/response model. The base classes for that are WebRequest and WebResponse. In my Web client, their descendants—the HTTPWebRequest and HTTPWebResponse classes—play a major role.

A Managed Web Client is Born

      The .NET Framework provides all the goodies you need to have full Web client functionality in the world of managed code, but it doesn't provide a full-featured client that can be reused in your projects, like the XMLHttpRequest object. I decided to wrap some managed classes and create a new managed class with identical behavior to the unmanaged XMLHttpRequest object. It turned out to be easier than I thought.
      In order to write the managed Web client, it's important to understand the common language runtime (CLR) and how to program against it. In addition, understanding the Unified Class Library (UCL) structure is even more important in my case so that I'm able to select the right class for the right job. I won't bore you with the technical details about the structure of the CLR and the exact implementation of the garbage collection, and I won't go through every class of the base classes and explain its interface. Instead I will explain only those classes that are relevant to my implementation of the Web client.
      Take a look at the .NET Framework structure in Figure 1. It provides a schematic overview of the UCL namespace structure. The base classes actually expose the operating system services. For example, the System.IO library enables services such as file streaming, reading, and writing, and the Net library allows you to handle Web requests, Web responses, sockets, and more. On top of it there are the System.Web services which are used to create Web Forms and Web Services, and the System.WindowsForms services which are used to create Windows®-based applications. My application will make extensive use of the System.Net and System.IO libraries for the XMLHttpRequest class.

Figure 1 The Unified Class Library Structure
Figure 1 The Unified Class Library Structure

      The first task is to create a WebRequest object for a specific network request schema—in my case, HTTP. In order to do that I'll use the System.Net.WebRequest class's CreateDefault method. However, instead of using the returned WebRequest object, I'll typecast it to the HttpWebRequest class, which is actually the HTTP-specific implementation of the WebRequest object.
Uri uriObj = new Uri("http://uddi.Microsoft.com/inquire")
HttpWebRequest request =
 (HttpWebRequest)WebRequest.CreateDefault(ObjURI);
The HttpWebRequest class contains support for the properties and methods defined in WebRequest, along with additional properties and methods that enable the user to interact directly with the HTTP protocol.
      The next step is to format my message and send it to the specified URI. To do that I'll use the System.IO.StreamWriter. The StreamWriter is an implementation of the System.IO.TextWriter for writing characters to a stream using a particular encoding. While creating the StreamWriter object, I provide the constructor with a stream to write to and the stream encoding, as you can see in the following code snippet:
StreamWriter s = new StreamWriter(request.GetRequestStream(), 
    Encoding.ASCII);
s.Write(body);
s.Close();
      The next logical step is to get the response. Doing that will create a WebResponse class, which is an abstract base class from which protocol-specific classes are derived. Again, I'll typecast it to the protocol-specific version of the class—the HttpWebResponse:
StreamWriter s = new StreamWriter(request.GetRequestStream(), 
    Encoding.ASCII);
s.Write(body);
s.Close();
response = (HttpWebResponse)request.GetResponse();
The HttpWebResponse class contains support for the properties and methods included in WebResponse, with additional elements that enable the user to interact directly with the HTTP protocol. I will use the HttpWebResponse later to get the Response Stream.
      Except for cosmetic changes that need to be performed on the HttpWebRequest (such as defining headers and user authentication) and on the HttpWebResponse (such as transforming the response stream into an XMLDocument), I'm finished with the core request/response operations.
      The last thing I need to take care of is the proxy. To do that I'll use the ProxyData class, which contains proxy information used by an HttpWebRequest to access resources through a proxy server:
WebProxy prx = new WebProxy (http://MyProxy, 80);
Request.Proxy = prx;
      Now that you have the core knowledge of how to perform request/response operations to write the Web client, I can get down to business and get my hands dirty. Let's code!

The Interface

      Once I decided to emulate the XMLHttpRequest object's behavior and interface, it wasn't too hard to determine my Web client's interface, except for some minor changes that I'll describe later. The code for my managed XMLHttpRequest is shown in Figure 2.
      At this point, I need to decide which libraries I'm going to use in my class (except for the default System library). The first one is the System.Net library; I need that for the WebRequest and WebResponse classes and their descendants—the HttpWebRequest and HttpWebResponse classes. Next is the System.Xml library, I'll use its classes for XML processing later on. To facilitate the use of this library I first have to add a reference to the System.Xml.dll. The System.IO namespace is needed for the Stream, StreamWriter, and StreamReader classes. And with the intention of using the Encoding class, I've added the System.Text namespace. Figure 3 lists the methods, properties, and events, and a brief description of each.

Implementing the Synchronous Operations

      After declaring the Web client's interface, it's time for the implementation phase. I've decided to explain the synchronous operation mode implementation separately from the asynchronous one since there is a difference in their programming concepts. Programming the synchronous mode is much easier because the program flow is strict, no method call is returned before it is complete, and there is no need for callback assignments.
      Before I begin coding each member, there is one variable I need to address—the Web client's ReadyState. It has a major effect on each member's implementation and behavior. The ReadyState represents the state of the request and holds a different value during every one of the request phases (see Figure 4).
      To translate the ReadyState available values into code, I've created a new public Enum with the same values and a private member that holds the current ReadyState initialized to 0—UNINITIALIZED (see Figure 5). This member will be updated in almost all of the public members' implementations.
      The workflow for the XMLHttpRequest is the following: open request to a specified URI, set request headers as necessary, send a message, and process the response. I will implement my class in the same order. The Open method implementation is pretty immediate, and I actually showed you the core coding needed in the first three code snippets. The Open method accepts five arguments considered necessary for creating the request:
  • The Method argument is the HTTP method used to open the connection, such as GET, POST, PUT, or PROPFIND.
  • The Url argument is the requested URL.
  • The Asynch argument is a flag indicating whether a synchronous or asynchronous call will be made.
  • The User and Password arguments provide authentication information for the request.
      As part of the implementation I will check whether the arguments' values are allowed and fall within the expected range; if not, a proper exception will be thrown. Now I'll set the default request headers and create the request object, as you can see in Figure 6. The request object declaration is a class-level private member of type HttpWebRequest:
private HttpWebRequest lgRequest=null;
      At this point I will just store the Asynch argument in a class-level private member; it will be used later on. The last remaining task is to set the ReadyState value to 1 (LOADING) to notify that the request is created. Setting the request headers and invoking the Send method are now allowed.
      Now that the request is open, I'd like to allow the user to set additional request headers. The implementation of the SetRequestHeader method is pretty straightforward. The only thing I need to do is map the values to the request object's header values. As you can see in Figure 7, most of the headers are predefined in the request object. In this case, I just map the values to the request header properties. For the undefined headers, I create new header entries and for the headers which are being set by the system, I throw an exception if the user tries to update them.
      Once the request headers are set, I can send my message. The implementation of the Send method in its synchronous mode requires three steps: get the request stream to write into, write the stream, and get the response (see Figure 8).
      Getting the response in its XML format requires the implementation of both the GetResponseXML and the GetReponseStream methods. First you need to get the response in its raw format—System.IO.Stream—then format it to its XML format. The implementation of the GetResponseStream method is very short. All you have to do is return the response object's GetResponseStream method.
public System.IO.Stream GetResponseStream()
{
  if(lgReadyState==eReadyState.COMPLETED)
    return lgResponse.GetResponseStream()
  else
    throw new InvalidOperationException("Getting response
      stream is forbidden at current ReadyState");
}
      Now that I have the response stream, I need to convert it to XML format. So I'll use the XMLTextReader class that enables me to parse text input into a sequence of XML tokens. All that's left to do is load the reader into an XMLDocument and I've got an XMLDocument containing the response XML (see Figure 9).
      The Web client is now ready to work in its minimal implementation. I can open a request, set its headers, send the message, and receive the response in an XML format, all in synchronous mode for now.
      Now let's implement the rest of the satellite methods for the synchronous working mode: GetAllResponseHeaders, GetReadyState, GetResponseBody, GetResponseHeader, GetResponseText, GetStatus, and GetStatusText. Except for GetResponseBody and GetResponseText, the implementation for these methods is pretty straightforward: check the ReadyState and, if the response is in the right state, return the response's headers and status, otherwise throw exceptions if it's not (see Figure 10). Implementing the GetResponseText and GetResponseBody methods is similar to the GetResponseXML implementation. In GetResponseText I will use the GetResponseStream to get the response stream and then translate it to a string format using the StreamReader class. StreamReader implements a TextReader that reads characters from a byte stream in a particular encoding and returns its results as a string (see Figure 11). In the GetResponseBody implementation I will use the BinaryReader class, which allows me to read strings and primitive data types from a stream and into a byte array.
      Now my Web client is ready to work in synchronous mode, so let's take a look at the asynchronous mode implementation.

Implementing the Asynchronous Operations

      Implementing the asynchronous mode is a bit different from the synchronous one, mainly in the flow of the request/response process. As opposed to the synchronous mode, where each method call returns only after it's done with its operation, the asynchronous method calls return immediately and trigger asynchronous callback functions when they're complete. The HttpWebRequest and HttpWebResponse classes allow this programming model by exposing the BeginGetRequestStream, EndGetRequestStream, BeginGetResponse, and EndGetResponse methods. All of these methods allow you to set a callback function to be called when the operation is complete. The modifications to my Web client are a bit more than significant, but will require me to rewrite only the Send method (see Figure 12).
      Unlike the synchronous Send method implementation in which I completed the request/response process in a single call, in the asynchronous mode I just call the BeginGetRequestStream. This method begins an asynchronous request for a Stream that the application can use to write data, and provides it with a callback function—ReqCallback. The IAsynchResult argument in my callback function wraps the asynchronous result—in this case, the request object. Once the callback function is called, I can invoke the EndGetRequestStream method to get the request stream and send my message. To implement the asynchronous version of the GetResponse method, I first invoke the BeginGetResponse method, providing it with another callback function similar to the BeginGetRequestStream invocation process, which in my Web client is the RespCallback function. Once the callback function is triggered, I can invoke the EndGetResponse and trigger the OnReadyStatusChange event I declared previously as a public event for my class.
      To complete my Web client, I still need to implement the Abort method, which is relevant only to this invocation model. The Abort method cancels the request and sets the ReadyState back to UNINITIALIZED (zero):
public void Abort()
{
    lgRequest = null;
    lgReadyState = eReadyState.UNINITIALIZED;
}
      My Web client is now fully implemented in both synchronous and asynchronous working modes.

Testing 1, 2, 3

      As you recall, I decided to write the Web client in order to query the UDDI repository. The test application is intended to do exactly that. Creating the interface for the test app is easy. I've used a standard C# Windows form and dropped some controls on top of it (see Figure 13).

Figure 13 UDDI Query
Figure 13 UDDI Query

      The UDDI Node combobox holds the URI for the UDDI node to query. Since the UDDI repository is made up of three replicated repositories (Microsoft, IBM, and Ariba), one of several URIs can be selected. (For more information about the UDDI initiative see http://uddi.microsoft.com). The Query textbox holds a UDDI API-specific query. The UDDI provides a strict XML schema for querying the repository and for the data returned. (The API description is beyond the scope of this article.) The Response textbox holds the returned XML data in its raw format. I've added radio buttons to enable selection of the synchronous or asynchronous invocation modes. Finally, the proxy setting exposes only the proxy's address and port.
      The code behind this form goes through five steps: it creates the XMLHttpRequest object, invokes the Open method with the desired arguments, sets additional headers, invokes the Send method, and when it's done, calls the GetResponseXML method. Let's drill down into the code.
      First I need to declare and initialize the XMLHttpRequest object. Because I want this object's scope to be the form, I will declare it at class level:
private XMLHttpRequest httpRequest;
      The next step is to attach an event handler to the XMLHttp-Request OnReadyStateChange event. I will do that as part of the form's constructor (see Figure 14) and will attach a new void sub OnReadyStatusChange to be called whenever the event is triggered.
      Now the Web client is ready to use. But before I can send messages I need to take care of the proxy, if there is one. In case the "Use a proxy server" checkbox is checked, I will set the assigned properties instead of the default ones, as you can see here:
private void setProxy()
{
int port = Convert.ToInt32(txtProxyPort.Text);
WebProxy proxyObject = new WebProxy(txtProxyAddress.Text, port);
GlobalProxySelection.Select = proxyObject;
}
The DefaultControlObject object stores the proxy settings for the default proxy used by HttpWebRequest instances to contact Internet sites beyond the local network.
      I can now invoke the Open method with the given arguments, set the additional request headers, and send a message. All these actions are shown in Figure 15.
      As I've mentioned, the message I'm sending here is a UDDI API-specific SOAP message, so except for the query itself as it appears in the Query textbox I need to wrap it with a SOAP envelope, as you can see in the following lines of code:
private string getMessage()
{
    StringBuilder bld = new StringBuilder(null);

    bld.Append("<?xml version='1.0' encoding='UTF-8?>
    <Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/'>
    <Body>");
    bld.Append(txtUDDIQuery.Text);
    bld.Append("</Body></Envelope>");

    return bld.ToString();
}
I also need to set SOAP-specific request headers, as you saw in Figure 15.
      Once the message is sent, there are two ways to get the response, depending on the operating mode selected (synchronous or asynchronous). The implementation stays the same, though. If synchronous working mode is selected, getting the response is done as part of the send procedure, and since I can predict the program flow, it's safe to put it right after I invoke the Send method. If asynchronous mode is selected, I use the same code, but from the OnReadyStateChange event handler implementation. In both cases, I set the result into the Response textbox.

Conclusion

      Taking a look at what I've done here in a few lines of code demonstrates the power of programming with the .NET Framework. What is even more amazing is that I could have easily gotten the same functionality and results writing the XMLHttpRequest object using Visual Basic® .NET. My guess is that throughout the article you had a little voice in your head telling you: "It's pretty easy, what's the big deal?" The fact that I could get the functionality I required makes it a big deal! The .NET Framework provides all the tools you could possibly need to get there, and to get there fast without any compromises.
For related articles see:
http://msdn.microsoft.com/msdnmag/ issues/0400/cutting/cutting0400.asp
http://msdn.microsoft.com/library/en-us/dnuddi/html/progguide.asp
http://msdn.microsoft.com/msdnmag/issues/01/01/xml/xml.asp
Sharp New Language: C# Offers the Power of C++ and Simplicity of Visual Basic

Avi Ben-Menahem is a technology development consultant at Microsoft Israel. You can reach him at avibm@microsoft.com.

From the September 2001 issue of MSDN Magazine.


Show:
© 2014 Microsoft