After the name of the host is resolved the client and server can start communicating. The server creates a socket and listens for incoming clients, the client connects to a server, and then the client and server can send and receive data. In Chapter 4 when we discuss sockets you will see all the action that is behind the scenes. Here we use the WebRequest and WebResponse classes where all socket issues are handled in the implementation of these classes; consequently, these classes are very simple to use.
In this small code example we create a WebRequest object with the static method WebRequest.Create(). The Create() method accepts a Uri object, or a URI string. The GetResponse() method returns a WebResponse object and connects to the server to return some data. To read the data that is returned from the server we use a StreamReader to read line-by-line.
Uri uri = new Uri("http://www.wrox.com");
WebRequest request = WebRequest.Create(uri);
WebResponse response = request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
response.Close();
reader.Close();
Now that we've seen a simple application using WebRequest and WebResponse objects let's look at these classes in detail.
WebRequest and WebResponse
The base classes for requests and responses to servers are WebRequest and WebResponse.
First let's look at the WebRequest class.
WebRequest Static Methods
Description
Create()
CreateDefault()
The WebRequest class has no public constructor. Instances can be created using the static methods Create() and CreateDefault() instead. These methods do not really create a new object of type WebRequest, but a new object of a class that derives from WebRequest, such as HttpWebRequest or FileWebRequest.
RegisterPrefix()
With RegisterPrefix() we can register a class to handle a specific protocol. Objects of this class will be created with the WebRequest.Create() method. This mechanism is called pluggable protocols, and is described later in this chapter.
WebRequest Instance Methods
Description
GetRequestStream()
GetRequestStream() returns a stream object that can be used to send some data to the server.
BeginGetRequestStream()
EndGetRequestStream()
Getting access to the request stream in an asynchronous way is done with BeginGetRequestStream() and EndGetRequestStream().
GetResponse()
GetResponse() returns a WebResponse object that can be used to read the data that is received from the server.
BeginGetResponse()
EndGetResponse()
Similar to the request stream we have asynchronous methods to get the response stream.
Abort()
If an asynchronous method is started with a BeginXX() method, it can be stopped prematurely with the Abort() method.
WebRequest Properties
Description
RequestUri
RequestUri is a read-only property that returns the URI that is associated with the WebRequest. The URI can be set in the static Create() method of this class.
Method
The Method property is used to get or set a method for a specific request. The HttpWebRequest supports the HTTP methods GET, POST, HEAD, and so on.
Headers
Depending on the protocol used, different header information can be passed to and received from the server. The protocol header information is contained within a WebHeaderCollection that can be accessed with the Header property.
ContentType
ContentLength
The type of data sent to the server is defined with the ContentType property. We can have any different type of data as long as it can be represented in a byte array. The content type usually defines the MIME type of the data, such as image/jpeg, image/gif, text/html, or text/xml.
Credentials
If the server requires user authentication, the user credentials can be set with the Credentials property.
PreAuthenticate
For protocols that support preauthentication, the property PreAuthenticate can be set to true. By default, a web browser first tries to access the page of a web site without authentication. If the web site requires authentication, the server returns that access is denied for non-authenticated users. The next request done by the client is with authentication information. This additional round-trip can be avoided by setting the property PreAuthenticate to true.
Proxy
With the Proxy property we can set the web proxy that is used for this request.
ConnectionGroupName
With the ConnectionGroupName property we can define the connection pool that should be used with this WebRequest object.
Timeout
The Timeout property defines the time in milliseconds we wait for a response from the server. The default value is 100,000 milliseconds. If the server doesn't respond within the timeout value, a WebException is thrown.
The WebResponse class is used to read data from the server. An object of this class is returned with the GetResponse() method as we have seen with the WebRequest class.
WebResponse Methods
Description
GetResponseStream()
GetResponseStream() returns a stream object that is used to read the response from the server. We discussed stream objects in Chapter 2.
Close()
If the response object is no longer needed it should be closed with the Close() method.
WebResponse Properties
Description
ResponseUri
With the ResponseUri property we can read the URI that is associated with the response object. This can be the same as the URI of the WebRequest object, but can be different if the server redirected the request to another resource.
Headers
The Headers property returns a WebHeaderCollection that includes the protocol-specific header information returned from the server.
ContentType
ContentLength
Similar to the WebRequest class, we have ContentType and ContentLength properties. Here these properties define the nature of the data that is returned from the server.
Now that we know the properties and methods of the WebRequest and WebResponse classes, let's look at some of the features of these classes.
Pluggable Protocols
The WebRequest class is abstract, so the WebRequest.Create() method cannot create an object of type WebRequest-an object of a class that is derived from WebRequest is created instead. Passing an HTTP request to the WebRequest.Create() method creates an HttpWebRequest object. We will discuss the HttpWebRequest class with detailed information about the HTTP protocol in Chapter 8. Passing a request with the file scheme creates a FileWebRequest object.
The http, https, and file schemes are predefined within the .NET configuration file machine.config as shown below. You can find this configuration file in the directory
Adding a configuration file entry allows us to extend the protocols used by the WebRequest class, or we can extend them programmatically.
To support a different protocol from http, https, and the file scheme, a new class that derives from WebRequest must be created, such as FtpWebRequest for the FTP protocol. This class must override methods and properties of the base class to implement protocol-specific behavior. In addition to that, a factory class that creates objects of the FtpWebRequest class must be defined. Such a factory class that is used by WebRequest must implement the IWebRequestCreate interface. Let's call this class FtpWebRequestCreator. An instance of this class must be registered for the ftp scheme with the WebRequest class:
WebRequest.RegisterPrefix("ftp", new FtpWebRequestCreator());
If the ftp scheme is now used with the WebRequest.Create() method, a new instance of FtpWebRequest is created and returned:
FtpWebRequest request =
(FtpWebRequest)WebRequest.Create("ftp://ftp.microsoft.com");
This request object can now be used to copy files from and to the FTP server similar to the request objects we are going to use in the next section. Here we are not going to implement the FtpWebRequest class, but you can do it yourself after reading Chapters
FileWebRequest and FileWebResponse
Reading and writing files locally or by using file shares is not very different from reading and writing files from web servers. To read and write files we can use the FileWebRequest and FileWebResponse classes. However, many methods and properties that are defined in the base classes WebRequest and WebResponse are not used with the derived classes, and the MSDN documentation just lists them as 'reserved for future use'.
To demonstrate how FileWebRequest and FileWebResponse can be used, I'm creating a simple Windows Forms application where the name of a file that should be opened can be entered in a text box, and then the file is opened and displayed in a multi-line text box (the big whitespace in the picture below). The file opened can then be saved with a different filename.
To make the code easier to read here are the controls that are used with this application.
Control Type
Name
TextBox
textFileOpen
TextBox
textFileSave
Button
buttonOpenFile
Button
buttonSaveFile
TextBox
textData
Reading from Files
The Click handler of the Open button opens the file and writes the content of the file to the multi-line text box. We pass the filename to the WebRequest.Create() method. It is not necessary to prefix the filename with the schema file://. The WebRequest class creates a Uri object and uses the AbsolutePath property of the Uri class. As we have seen earlier, the Uri class automatically prefixes the filename with the correct schema. So passing a filename the WebRequest class creates a FileWebRequest object, and we can successfully cast it. The GetResponse() method returns a FileWebResponse that we use immediately to get a Stream object with the method GetResponseStream(). The StreamReader class is used to read the stream with simple methods. We read the complete file data into a string and pass the string to the Text property of the multi-line textbox textData.
private void buttonOpenFile_Click(object sender, System.EventArgse)
{
string fileName = textFileOpen.Text;
FileWebRequest request =
(FileWebRequest)WebRequest.Create(fileName);
Stream stream = request.GetResponse().GetResponseStream();
StreamReader reader = new StreamReader(stream);
textData.Text = reader.ReadToEnd();
reader.Close();
}
Starting the application we can enter a filename, and the file is opened and displayed in the multi-line text box:
There's just a simple change necessary and we can read files from both the file system and the Web. If we enter an HTTP request into the current program, it will result in an invalid cast exception because we cast the object that is returned from WebRequest.Create() to the class FileWebRequest. Changing the program so that it does not cast to a FileWebRequest we can use any scheme:
string fileName = textFileOpen.Text;
WebRequest request = WebRequest.Create(fileName);
Stream stream = request.GetResponse().GetResponseStream();
Important If we don't use specific methods or properties of the derived class, it is not necessary to cast the WebRequest object that is returned from the method WebRequest.Create() to the specific class!
With .NET v1 the class FileWebRequest has no methods or properties that are specific to this class and they are not defined in the base class WebRequest. However, the HttpWebRequest class has some specific methods and properties as dealt with in Chapter 8.
Writing to Files
To write the data back to a file we implement a Click handler for the Save button. As before, we create a WebRequest object passing the filename. Now we use a StreamWriter instead of a StreamReader. There's one other important change. To make the stream writable, the Method property must be set to "PUT". The default method is "GET" specifying that the stream is read-only.
private void OnFileSave(object sender, System.EventArgs e)
{
string fileName = textFileSave.Text;
WebRequest request = WebRequest.Create(filename);
request.Method = "PUT";
Stream stream = request.GetRequestStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(textData.Text);
writer.Close();
}
Now we have seen how the classes WebRequest and WebResponse can be used-but there are some further features of these classes that we will also take a look at.
Connection Pooling
The default number of connections that can be opened to the server at one time is defined in the configuration file machine.config, as can be seen below. With the default configuration we have a maximum of two simultaneous connections to the same host.
We can override the settings not only by adding entries to the configuration file but also programmatically for specific requests using the ServicePoint and ServicePointManager classes. We can create multiple connection pools and use them by their name with the ConnectionGroupName property. This is useful if we do multiple requests to the same server simultaneously. These classes are discussed in Chapter