2. Writing Web Applications

THERE ARE MANY DIFFERENT TECHNIQUES that programmers use to write web applications. In this section, you will learn how to write web applications using ASP.NET WebHandler technology.

2.1. What is a WebHandler?

A WebHandler is a program written in C# that is designed to be executed by a web server.

Here is an example of a C# web application:

Figure 8.5. Hello.ashx

<%@ WebHandler Language="C#" Class="Hello" Debug="true" %>

using System;
using System.Web;

public class Hello: IHttpHandler
{
  // process incoming request
  public void ProcessRequest(HttpContext context) 
  {
    context.Response.Write(@"<html><body>
       <h2>Hello, World!</h2>
       Today is: " + DateTime.Now + @"
       </body></html>");
  }

  public bool IsReusable 
  {
    get { return false; }  
  }

}

As you look at the code, you should recognize the overall structure -- a class named Hello contains a method named ProcessRequest. But many details look foreign. Where's the Main() method? What's that IsReusable thing?

WebHandlers differ in several ways from standard console C# applications. WebHandlers are stored in files named with the extension .ashx, instead of the typical .cs extension. The first line is a special directive that tells the ASP.NET web server what language the WebHandler is written in (you can write WebHandlers in any .NET language), and also specifies the name of the WebHandler class (the name of the WebHandler class must be the same as the name specified by the Class="..." attribute of the WebHandler directive).

For now, ignore everything except the highlighted section -- the ProcessRequest() method. WebHandlers don't have a Main() method. Instead, their "main" method is named "ProcessRequest", and it must be defined as shown, with an HttpContext parameter. You can call the parameter anything you want; in my examples, I'll always use the name context for consistency.

This is a simple WebHandler that generates a simple HTML response document containing the current date and time. But note that it doesn't generate the document by calling Console.Write. That would print the response to the web server's console, which isn't what we want at all. We want the generated response to be transmitted back over the network to the web browser that requested it. To do so, we use the context.Response.Write() method. (More precisely, the context parameter contains a property named Response, which is an object that has a method named Write().)

Something else odd about the context.Response.Write() method call is that the message in parenthesis extends over several lines. Normally, the compiler does not permit multi-line string literals. However, if you look closely, you'll see that the string literal begins with an @ symbol. When you prefix a string literal with an @ symbol, the compiler allows line breaks inside the string. We could have written the statement without an @ symbol like this:

    context.Response.Write("<html><body>\n"); 
    context.Response.Write("<h2>Hello, World!</h2>\n");
    context.Response.Write("Today is: " + DateTime.Now + "\n");
    context.Response.Write("</body></html>");

As you can see, generating HTML responses this way would quickly become tedious. The examples in this chapter will use @ strings extensively, to improve clarity.

To see this WebHandler in action, create a new website named Hello using Visual Studio. Add a Generic Web Handler to named Hello.ashx to your website, then copy and paste the sample code above into the skeleton Hello.ashx file created for you by Visual Studio, and run the project. Visual Studio will start a test web server, and an icon will appear in your system tray:

Next, Visual Studio opens a new web browser window, and requests the URL:

http://localhost:3448/WebSite3/Hello.ashx

The browser sends its request to the test web server. The server activates the Hello WebHandler, and you see something like this:

A bit of explanation about the URL is in order. The server "localhost:3448" refers to the test web server running on your local computer. It's a real web server -- try pointing your browser to http://localhost:3448 and look at the files.

The path "/WebSite3/Hello.ashx" refers to the Hello.ashx WebHandler, located in the Visual Studio project named WebSite3. When the web server receives a request for "/Hello.ashx", instead of returning the contents of the file named "Hello.ashx", it runs the code in the Hello.ashx WebHandler's ProcessRequest() method. The data output by the ProcessRequest() method is sent to the web browser. If you right-click in the browser and choose "View Source," you'll see the exact output of the Hello WebHandler.

You can press your browser's Reload / Refresh button to reactivate the Hello WebHandler and get a more recent date/time. As long as the test web server is running, it will respond to browser requests. When you stop the test web server, by stopping it in Visual Studio, it can no longer respond to browser requests. The browser will display an error message if you try to activate the WebHandler when the test web server isn't running.

2.2. Processing User Input

Next, let's look at a WebHandler that receives input from the user.

Figure 8.6. Greeter.ashx

<%@ WebHandler Language="C#" Class="Greeter" Debug="true" %>

using System;
using System.Web;

public class Greeter : IHttpHandler
{

  public void ProcessRequest(HttpContext context) {
    HttpRequest request = context.Request;
    HttpResponse response = context.Response;

    string username = request["username"];
    string favfood = request["favfood"];

    response.Write(@"<html><body>
      <h2>Hello, " + username + @"!</h2>
      I like " + favfood + @", too!
      </body></html>");
  }

  public bool IsReusable 
  {
    get { return false; }  
  }

}

Are you wondering, "Where's the Console.ReadLine()?" Web applications don't get input the same way console applications do. Think back to the example at the beginning of this chapter. How does Google get input from the user to determine what pages to search for? Remember the URL you used:

http://www.google.com/search?q=Microsoft

That's how the Google search engine gets its input -- from the query string of the URL. And that's how your web applications will get their input. Notice this line in the Greeter WebHandler:

    string username = request["username"];
    string favfood = request["favfood"];

The expression request["username"] looks for a query string parameter named username, and returns its value. So, when you activate this WebHandler, you should do so like this:

http://localhost:3448/WebSite3/Greeter.ashx?username=Fred&favfood=Pizza

When you do so, the WebHandler will retrieve the value "Fred" from the query string and store it in the variable username, and the value "Pizza" will be stored in favfood.

By the way, you may notice that this WebHandler contains two lines at the top:

    HttpRequest request = context.Request;
    HttpResponse response = context.Response;

These lines establish aliases for the context.Request and context.Response properties, so that instead of typing "context.Response.Write(...)" to do output, you can simply type "response.Write(...)".

Note that this WebHandler will not work correctly if it is activated without the proper query parameter names. For example, if it is activated like this:

http://localhost:3448/Website3/Greeter.ashx?user=Fred&food=Pizza

or like this:

http://localhost:3448/Website3/Greeter.ashx

the WebHandler will not be able to retrieve any information from the query string -- in the first case, because the query parameter names don't match the names the WebHandler is expecting, and in the second case, because there are no query parameters. In both cases, this WebHandler will display blanks in the output, which will look odd. Let's look at a revised version of Greeter that detects when it is improperly activated:

Figure 8.7. Greeter2.ashx

<%@ WebHandler Language="C#" Class="Greeter2" Debug="true" %>

using System;
using System.Web;

public class Greeter2 : IHttpHandler
{

  public void ProcessRequest(HttpContext context) {
    HttpRequest request = context.Request;
    HttpResponse response = context.Response;

    string username = request["username"];
    string favfood = request["favfood"];

    response.Write("<html><body>\n");

    if (username != null && favfood != null) 
    {
      response.Write("<h2>Hello, " + username + "!</h2>");
      response.Write("I like " + favfood + ", too!");
    } 
    else 
    {
      response.Write("Please supply correct parameters.");
    }

    response.Write("</body></html>\n");
  }

  public bool IsReusable 
  {
    get { return false; }  
  }

}

Take a close look at the following line:

if (username != null && favfood != null)

When the WebHandler attempts to retrieve a value from the query string using the request["parameter-name"] expression, if no such parameter exists in the query string, the special value null is placed in the variable. The value null is not a string -- it is a value that indicates the absence of a string value. This if statement tests to see whether the WebHandler was activated with username and favfood parameters; if not, a warning message is displayed ("Please supply correct parameters").

2.3. Forms and WebHandlers

Let's create a nice user interface for our Greeter WebHandler by making an HTML form for users to use:

Figure 8.8. greeterform.html

<html>

<head>
  <title>Greeter</title>
</head>

<body>

<h2>Welcome to the Greeter</h2>

<form action="http://localhost:3448/WebSite3/Greeter.ashx">
  What's your name? <input type="text" name="username"><br>
  What's your favorite food? <input type="text" name="favfood"><br>
  <input type="submit" value="Greet">
</form>

</body>
</html>

Notice the following parts of the form:

  • The form's action attribute contains the URL of the Greeter WebHandler:

    <form action="http://localhost:3448/WebSite3/Greeter.ashx">
  • There are two text input areas for the user to enter a name and a favorite food. The input areas are named "username" and "favfood", so that when the user clicks the submit button, the browser will construct a query string with the parameter names required by the WebHandler.

When rendered in a browser, the form looks like this:

Figure 8.9. Greeter Form

Greeter Form

Let's say the user fills out the form, entering "Anna" as the name, and "Fried Chicken" as the favorite food. When the user clicks the Greet button, the browser will construct the following URL:

http://localhost:3448/Greeter.ashx?username=Anna&favfood=Fried+Chicken

The browser will then attempt to activate the WebHandler, send the request, and get a response. If the test web server is running, this should succeed, and the WebHandler should display the same greeting that it would have displayed if the user had activated it by typing the URL directly into the browser Address bar.

[Note]Note

By the way, if you look closely at the URL constructed by the browser, you'll notice that the part containing the favfood value ("favfood=Fried+Chicken") isn't quite what the user entered ("Fried Chicken"). You see, certain characters (such as space characters) are not permitted in a URL. When the user enters text containing spaces and other illegal characters, the browser automatically encodes the user's entry so that it can be transmitted in the query string (for example, spaces are converted to + characters). The good news is that when the WebHandler receives the value, the request["favfood"] expression automatically decodes the query parameter value back to the value the user typed in the form. This means the programmer writing the WebHandler doesn't have to take special steps to decode the value. This whole URL encoding/decoding process is invisible both to the user and to the WebHandler programmer (unless you happen to look closely at the query string), so it isn't something you normally need to think about unless you are manually constructing URL's to test a WebHandler, and you want to transmit a value that contains a space.

You might wonder what happens if the user doesn't enter a name or a favorite food. If the user clicks Greet without entering a name or food, the browser constructs a URL like this:

http://localhost:3448/Greeter.ashx?username=&favfood=

When the WebHandler gets the values for username and favfood, it gets an empty string (""). This is different than the example we looked at in the last section, where the user didn't provide any query parameters, or provided wrong query parameter names. Here's how the WebHandler might check to make sure the user entered a name and favorite food on the form:

    if (username != "" && favfood != "") 
    {
      response.Write("<h2>Hello, " + username + "!</h2>");
      response.Write("I like " + favfood + ", too!");
    } 
    else 
    {
      response.Write("Please back up and tell me your name and favorite food.");
    }

2.4. WebHandlers with a User Interface

Let's take our Greeter example a step further. We have a nice user interface in an HTML file, and we have a WebHandler that processes the user's input. Now, it's time to combine the two, and create a WebHandler that generates its own user interface.

Figure 8.10. Greeter3.ashx

<%@ WebHandler Language="C#" Class="Greeter3" Debug="true" %>

using System;
using System.Web;

class Greeter3 : IHttpHandler
{
  
  public void ProcessRequest(HttpContext context) {
    HttpRequest request = context.Request;
    HttpResponse response = context.Response;

    string username = request["username"];
    string favfood = request["favfood"];

    response.Write(@"<html>
      <head><title>Greeter</title></head>
      <body>");

    PrintForm(response);

    if (username != null && favfood != null) 
    {
      // user submitted the form
      if (username != "" && favfood != "") 
      {
        // user entered both a name and favorite food
        response.Write("<h2>Hello, " + username + "!</h2>");
        response.Write("I like " + favfood + ", too!");
      } 
      else 
      {
        // one of the form entries was blank
        response.Write("<b>Please enter both a name and your favorite food to get a greeting.</b>");
      }
    } 

    response.Write("</body></html>");
      
  }

  // Print form for user input
  void PrintForm(HttpResponse response) 
  {
    response.Write(@"<h2>Welcome to the Greeter</h2>  
      <form>
        What's your name? <input type='text' name='username'><br>
        What's your favorite food? <input type='text' name='favfood'><br>
        <input type='submit' value='Greet'>
      </form>");
  }
    
  public bool IsReusable {
      get { return false; }  
  }

}

There's a lot going on in this example. Let's tackle it by going through a hypothetical session. The user starts by pointing his browser to

http://localhost:3448/WebSite3/Greeter3.ashx

Here's what the WebHandler sends back:

Now, let's talk through exactly what happened when the WebHandler was activated by that URL. After getting the query parameters and starting its response with a few response.Write() statements, it called

    PrintForm(response);

to print the form. The PrintForm() method simply prints the form using response.Write(). Those statements could have been placed in the ProcessRequest() method, but for the sake of code readability, it helps to print large chunks of HTML in separate methods. Notice how the response parameter is passed to the printForm() method to allow it to print the form. Notice, too, how the tag attribute values use single quotes (') instead of double quotes("):

    response.Write("What's your name? <input type='text' name='username'><br>\n");

(It's easier to print single quotes in C# than to print double quotes.)

After the form is printed, the WebHandler checked to see if the appropriate query parameters were submitted:

    if (username != null && favfood != null) 

Since the user directly activated the WebHandler without supplying query parameters, the values are both null, and the application does no further processing.

Next, suppose the user enters "Alice" and "Peanuts" into the form, and clicks the Greet button, causing the browser to construct the following URL to activate the WebHandler:

http://localhost:3448/WebSite3/Greeter3.ashx?username=Alice&favfood=Peanuts

The WebHandler runs and displays:

Again, let's discuss the processing for this request.

  1. The WebHandler gets the request parameter values and calls PrintForm(). That's why you see the form redisplayed.

  2. The application tests the parameter values and finds that they are not null:

        if (username != null && favfood != null) 
    
  3. The application tests the parameter values and finds that they are not empty:

          if (username != "" && favfood != "")  
    
  4. The application displays a greeting message.

Finally, let's try entering a name, but no favorite food, and clicking Greet. The browser constructs the following URL:

http://localhost:3448/WebSite3/Greeter3.ashx?username=Alice&favfood=

The application runs and displays:

Tracing the processing, note the following:

  1. The WebHandler gets the request parameter values and calls printForm(). The form is redisplayed.

  2. The application tests the parameter values and finds that they are not null:

        if (username != null && favfood != null) 
    
  3. The application tests the parameter values and finds that, though username is not empty, favfood is empty:

          if (username != "" && favfood != "")  
    
  4. The application prompts the user to enter both a name and favorite food.

2.5. Exercises

  1. Modify the Greeter3 WebHandler so that, when the user enters a name and favorite food, the application does not redisplay the form, but displays only the greeting message.

  2. Modify the Greeter3 WebHandler so that, when the user enters a name, but leaves the favorite food blank, the application displays the user's name as the default value. In other words, when activated like this:

    http://localhost:3448/WebSite3/Greeter3.ashx?username=Alice&favfood=

    The application should generate a form that looks like this:

    To do this, you need to know how to supply a default value for a form field. Here's how:

    <input type="text" name="username" value="Alice">

    Notice the value attribute in this input tag. It provides an initial value for the text input box.