Making WebRequest Calls

Making WebRequest Calls

The WebRequest class is the central object used for managing HTTP requests in ASP.NET AJAX. It provides a cross-browser compatible object for using the different underlying XMLHttpRequest implementations. Listing 6-3 (CallTimeWebRequest.aspx) is an .aspx page that does the equivalent of the request and response processing shown in Listing 6-2.

Listing 6-3
Image from book

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Networking</title>
<script type="text/javascript">

var xmlhttp;

function pageLoad() {
    var webRequest = new Sys.Net.WebRequest();
    webRequest.set_url("Time.aspx");
    webRequest.add_completed(completedHandler);
    webRequest.invoke();
}

function completedHandler(result, eventArgs) {
    if(result.get_responseAvailable()) {
        alert(result.get_statusText());
        alert(result.get_responseData());
    }    
}
</script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    </asp:ScriptManager>
    <div>    
    </div>
    </form>
</body>
</html>
Image from book

First, note that the code creates a Sys.Net.WebRequest object directly. You do not have to bother with checking the browser compatibility. You set the service address, add a completed callback function, and invoke the request. In the callback function, there is no need to check different integers against the status codes they represent. Instead, there is a check that the response is available, coupled with access to the data. This isn’t dramatically different from what is done in Listing 6-2 when using the XMLHttpRequest object directly; however, it is more straightforward and avoids your having to look at each and every state change in the underlying object.

The WebRequest object provides abstractions for much of what is typically handled by the browser. Many of the interactions between browser and server can be controlled through the WebRequest object. The HTTP protocol defines a set of headers that you may want to control. You also may find that you don’t want to accept the default behavior of waiting indefinitely for a server to respond once you have initiated a request.

Setting the HTTP Verb

HTTP requests include a verb for processing. Most of the time, you use either GET to query the server or POST to upload data. The HEAD verb is also used to gather information about the content type and to get caching information. The WebRequest object assumes a GET request unless you specify otherwise. A GET passes values in the query string, which seems as though it would be a bad idea from a security standpoint. In this case, however, the user won’t see the query string anyway. A POST is more appropriate if you have a lot of data to send to the server.

The WebRequest provides functions to retrieve and set the HTTP verb. In Debug mode, the class will check for a nonzero length verb, but it has no way to verify that the verb is supported by the server, so any nonzero length value is accepted.

Listing 6-4 shows the EchoABC.aspx page that will receive and process a POST from the code in Listing 6-5. Note that it looks for a value by name in the request’s form collection, since a POST doesn’t transfer data in the query string.

Listing 6-4
Image from book

<%@ Page Language="C#" %>
<script runat="server">
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    if(Request.Form["abc"] != null) {
        Response.Write(Server.HtmlEncode(Request.Form["abc"]));
    }
}
</script>
Image from book

The page in Listing 6-5 (SetVerb.aspx) sets the HTTP verb to POST and sets the body to a name-value pair. The key is checked in the form collection from Listing 6-4, and its result is returned.

Listing 6-5
Image from book

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Networking</title>
<script type="text/javascript">
function pageLoad() {
    var webRequest = new Sys.Net.WebRequest();
    webRequest.set_url('EchoABC.aspx');
    webRequest.set_httpVerb('POST');
    webRequest.set_body('abc=123');
    webRequest.add_completed(completedHandler);
    webRequest.invoke();
}

function completedHandler(result, eventArgs) {
    if(result.get_responseAvailable()) {
        alert(result.get_statusText());
        alert(result.get_responseData());
    }    
}
</script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    </asp:ScriptManager>
    <div>    
    </div>
    </form>
</body>
</html>
Image from book

Establishing Timeout Limits

A call to set_timeout on the WebRequest can impose a limit on how long to you will wait for the response. The result can then be checked to find out if the call was terminated before reaching the limit. The get_timedOut function returns true if the call did not complete in time. To ensure that the limit is reached, you put the thread to sleep for 10 seconds in the page being requested. This lets you test what happens in the client page when the server takes too long to respond.

System.Threading.Thread.Sleep(10000);

Listing 6-6 (Timeout.aspx) adds a call to set_timeout for five seconds and then discovers the timeout problem in the completion callback.

Listing 6-6
Image from book
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Networking</title>
<script type="text/javascript">
function pageLoad() {
    var webRequest = new Sys.Net.WebRequest();
    webRequest.set_url('Sleep.aspx');
    webRequest.set_httpVerb('POST');
    webRequest.set_body('abc=123');
    webRequest.set_timeout(5000);
    webRequest.add_completed(completedHandler);
    webRequest.invoke();
}

function completedHandler(result, eventArgs) {

    if(result.get_timedOut()) {
        alert('timed out');
    }
    if(result.get_responseAvailable()) {
        alert(result.get_statusText());
        alert(result.get_responseData());
    }    
}
</script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    </asp:ScriptManager>
    <div>    
    Making a WebRequest.
    </div>
    </form>
</body>
</html> 
Image from book

Adding Custom Headers

In addition to being able to detect timeouts, control the verb, and set the contents, you can send custom HTTP headers along with the request. Listing 6-7 is the EchoHeaders.aspx page that simply echoes the HTTP headers included in a request. This server-side code creates a return page that shows exactly what is passed to it in the headers.

Listing 6-7
Image from book

<%@ Page Language="C#" %>
<script runat="server">
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    NameValueCollection headers = Request.Headers;
    foreach (string key in headers.Keys)
    {
        Response.Write(Server.HtmlEncode(key) +
            " = " +
            Server.HtmlEncode(headers[key]) +
            "<br />\r\n");
    }
}
</script>
Image from book

Requests to the page from Internet Explorer and Firefox are shown in Figure 6-1.

Image from book
Figure 6-1

The headers sent by the different browsers are not the same. Internet Explorer provides the versions of the .NET Framework that it supports in the headers, while Firefox does not. Both browsers accept gzip and deflate for encodings. The language header differs based on the selections made in the different browsers. (I don’t speak Japanese, but I have it in my list of preferred languages of both browsers for testing localization features.)

Listing 6-8 (CustomHeaders.aspx) makes a call to the EchoHeaders.aspx page while adding a few unique headers to the request.

Listing 6-8
Image from book
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Networking</title>
<script type="text/javascript">
function pageLoad() {
    var webRequest = new Sys.Net.WebRequest();
    webRequest.set_url('EchoHeaders.
    webRequest.get_headers()["Preferred-Publisher"] = "Wrox";
    webRequest.get_headers()["Preferred-Album"] = "Rift";
    webRequest.get_headers()["UA-CPU"] = "Altair MIPS";
    webRequest.add_completed(completedHandler);
    webRequest.invoke();
}

function completedHandler(result, eventArgs) {
    if(result.get_responseAvailable()) {
        $get("placeholder").innerHTML = result.get_responseData();
    }    
}
</script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    </asp:ScriptManager>
    <div id="placeholder">    
    </div>
    </form>
</body>
</html>
Image from book

The same requests with some additional headers from Internet Explorer and Firefox now show different results than the previous request. The Preferred-Publisher and Preferred-Album headers are now reported as part of the request. Although the preferred-publisher and preferred-album are displayed, in the test with IE, the UA-CPU header is the same as it was before the request that tried to modify it. This is because IE set this header after the page set its value, since this is a standard header that IE always sets itself. The WebRequest object allows you to add headers, but the browser itself still has the last say about what is actually transmitted to the server. Figure 6-2 shows the result of an additional set of requests from Internet Explorer and Firefox with the requests that include new additional headers.

Passing Extra Data

The WebRequest class will automatically pass data from the caller to the completion event handler function. Many times, the callback code can benefit from having more than is typically provided in normal operations. The ASP.NET AJAX userContext parameter provides a way to pass data from the caller to the callback without even sending it to the server, and this tells the event handler some extra context information about why or where the call was made. The Framework manages this extra information and includes it as part of the callback parameters automatically, so it looks as though this information came from the server. Listing 6-9 (UserContext.aspx) is a page that adds userContext data to the call to the EchoHeaders page.

Image from book
Figure 6-2
Listing 6-9
Image from book
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Networking</title>
<script type="text/javascript">
function pageLoad() {
    var webRequest = new Sys.Net.WebRequest();
    webRequest.set_url('EchoHeaders.aspx');
    webRequest.set_userContext('send this data to the callback');
    webRequest.add_completed(completedHandler);
    webRequest.invoke();
}

function completedHandler(result, eventArgs) {
    if(result.get_responseAvailable()) {

        var userContext = result.get_webRequest().get_userContext();
        $get('placeholder')
    }    
}
</script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    </asp:ScriptManager>
    <div id="placeholder" >    
    </div>
    </form>
</body>
</html>
Image from book

The WebRequest does not include the user context information in what is sent to the server. Instead, the data is stored locally and then made available during the callback. You can store complex JavaScript types without needing to worry about how they will be serialized for including in an HTTP request. This also keeps the request/response payload lighter.

Resolving the Full URL

In web development, it is often useful to make decisions based on what part of an application the user is accessing. The WebRequest provides a method for retrieving the full URL of a request that is about to be made. When setting up the request, you can specify a complete address for the URL or a relative path. The getResolvedUrl function will always yield the full path regardless of which form you provide:

alert(webRequest.getResolvedUrl());

It’s important to note that you should not drive security decisions based on the resolved URL in JavaScript running in the browser. The base URL can be spoofed through script running in the JavaScript console. It’s safe to assume that any JavaScript in your page might be modified by some kind of Trojan program or unauthorized process on the user’s machine, so you must take care in how much trust you place in that code. You should rely on server-side validation of every aspect in the client/server interaction in cases where you have security concerns.