Programmer to ProgrammerTM  
Wrox Press Ltd  
   
  Search ASPToday Living Book ASPToday Living Book
Index Full Text
 
ASPToday Home
 
 
Home HOME
Site Map SITE MAP
Index INDEX
Full-text search SEARCH
Forum FORUM
Feedback FEEDBACK
Advertise with us ADVERTISE
Subscribe SUBSCRIBE
Bullet LOG OFF
                         
      The ASPToday Article
July 20, 2001
      Previous article -
July 19, 2001
  Next article -
July 23, 2001
 
   
   
   
Create a custom IHttpHandler to allow ASP.NET pages to communicate with each other   Andy Elmhorst  
by Andy Elmhorst
 
CATEGORY:  .NET Framework  
ARTICLE TYPE: Cutting Edge Reader Comments
   
    ABSTRACT  
 
Article Rating
 
   Useful
  
   Innovative
  
   Informative
  
 23 responses

One of the questions that experienced web developers typically ask

when learning the ASP.NET framework is this: When I

want to transfer control to a new page from the current page, how do

I create an instance of the next page and pass it the state of the current request? To accomplish this, the default mechanism provided by the ASP.NET framework is to redirect the browser to the next page using the Page.Navigate() method. This approach to page navigation can be the cause of numerous undocumented dependencies between ASP.NET pages, their possible query string arguments and items stored

in the Session. It also does not allow type checking to be done at

compile time on the state information passed between the pages. In this article, Andy Elmhorst shows us that it is possible for ASP.NET

pages to communicate elegantly with each other as real objects. In

the process, the application will save a round trip to the browser. As a result, the application will consume less server, client, and network resources.

   
                   
    Article Discussion   Rate this article   Related Links   Index Entries  
   
 
    ARTICLE

Introduction

An ASP.NET page is an object that can render itself as an HTML document containing an HTML form. When the user posts the form back to the server, the ASP.NET page does the work of capturing the form field values and placing them back into the server-side controls that generated the values. These controls can validate themselves and raise events when their values change. After the results of the form post back have been validated and processed appropriately, the application logic built into the page will typically send the user on to another ASP.NET page.

For some of the statements I made above, there are exceptions, as there are to any good rule of thumb. There are many cases where passing data in the query string is a good approach. I am just saying that ASP.NET has a serious lack of support for server-side multi-page communication.

In this article we will extend the ASP .NET framework to allow for direct communication between ASP.NET pages when control needs to pass from one to the next. As an end result, our pages will be able to communicate with each other directly as objects. This will result in less code and less hits on the server to accomplish the same task. Does it sound too good to be true? It's not. Read on.

Examining the default ASP.NET page lifecycle.

To begin with, let's look a little under the covers at how ASP.NET works to handle a request for an ASP.NET page. When the ASP.NET runtime receives a new web request for an ASP.NET page, it handles the request by creating an instance of a class called System.Web.UI.PageHandlerFactory . The implementation of this class is to return a compiled instance of an ASP.NET page, which is a class that inherits from System.Web.UI.Page.

Under the covers, the PageHandlerFactory class relies on another class, called PageParser, to do all of the work for it. PageParser is the class that brings a lot of the new performance enhancements to ASP.NET. Upon the first request for an ASP.NET page, PageParser rips through the .aspx file, compiles it into a real .NET class and caches the compiled instance for future requests. Here is a basic collaboration diagram illustrating the interaction between the ASP.NET objects for a typical request.

Creating the IHttpHandler Factory

Our first step will be to override the default page creation process that is provided by ASP.NET for pages. To do this, we must create a class the implements the IHttpHandlerFactory interface. The IHttpHandlerFactory interface contains only two methods that we must implement. The first method, GetHandler(), is called at the beginning of an HTTP Request. This method must return an object that implements the IHttpHandler interface. At the end of the HTTP Request, a second method, called ReleaseHandler() is called to allow the IHttpHandlerfactory to do any cleanup work that needs to be done at that time.

Our implementation of the IHttpHandlerFactory interface will not be a whole lot different than the default ASP.NET IHttpHandlerFactory. We will create a class called Framework.PageFactory. The only interface method that we will provide behavior for is the GetHandler() method.

The GetHandler() method must return an object that implements the IHttpHandler interface. In our implementation, we will simply delegate the creation of our custom IHttpHandler to a static method in a separate class called BaseController. The reasons for this will become clear later when we examine the implementation of the BaseController class. Here is the code for our IhttpHandlerFactory

namespace Framework
{
  using System;
  using System.Web.UI;
  using System.Web;
  
  public class PageFactory : IHttpHandlerFactory
  {
    public IHttpHandler GetHandler(HttpContext Context,
    string URL,
    string Path,
    string ObjectName)
    {
      return BaseController.getViewObject( Path, ObjectName , Context );
    }
    public void ReleaseHandler(IHttpHandler Handler){}
  }
}

To map any incoming requests for ASP.NET pages in our web application to our custom handler, we must modify the configuration file for our application. This file is called config.web and is located in our web application root directory. (Note that it has been publicly reported that this file will be named web.config as of the Beta2 release of .NET.)

We will add the following section to the web application configuration file to tell the framework to use our PageFactory class for any request involving a file with a .aspx extension in our application.

<configuration>
  <httphandlers>
    <add verb="*" path="*.aspx" type="Framework.PageFactory,Framework" />
  </httphandlers>
</configuration>

I have chosen to override the default ASP.NET page file extension ( .aspx) for the purposes of this example. Really the only reason I did so is that it made editing of the pages easier in Visual Studio .NET. However, we could have just as easily mapped our own custom file extension for pages that we want to have this behavior. Optionally, we could have created a special subfolder and mapped requests in that subfolder to our handler as well. The ASP.NET framework gives us many options in this regard.

Creating the IHttpHandler

Creating the factory class is only the first step to our solution. Our PageFactory needs to return an object that implements the IHttpHandler interface. We could create our own implementation here if that were our intended goal. However, our goal is to retain all of the standard behavior of an ASP.NET page. Therefore we will return a class that inherits from System.Web.UI.Page, the default handler for ASP.NET pages.

System.Web.UI.Page already implements IHttpHandler and will handle all of the standard page processing and rendering tasks for us. We will simply inherit from it to add our new desired functionality. Our IHttpHandler class that inherits from Page will be called BaseController

Saving the name of the current post back handler

One issue with switching the current ASP.NET page without telling the browser to redirect to a new page is that the browser will continue posting any web forms back to a URL that contains the filename of the original ASP.NET page. To compensate for this, we will store a hidden form field in our web form that contains the name of the page that should be the post back handler for the current form.

The hidden form field will be set in the Page_Load event of BaseController . The ASP.NET page parser generates a unique class name for each compiled instance of an ASP.NET page. It generates the name of the class by taking the filename for the page and replacing the period with an underscore. For an ASP.NET page called Default.aspx, the page parser creates a compiled class instance with a type name of Default_aspx. We are taking only the first part of the type name, minus the _aspx and storing it in our hidden form field. This is all the information we need to know to find and create an instance of the correct page to handle the post back.

protected void Page_Load( object sender, EventArgs e )
{
  string a_ObjectName = this.GetType().Name;
  a_ObjectName = a_ObjectName.Substring ( 0, a_ObjectName.Length - 5 );
  this.RegisterHiddenField( LAST_VIEW_OBJECT, a_ObjectName );
}

Creating the instance of the handler

As seen in the code listing for the PageFactory class, it calls a static method called GetViewObject() in our BaseController class. The code for that method is listed below. The code first checks to see if the hidden form field containing the name of the post back handler is filled in. If it is not, it simply returns the actual page that the browser has requested.

public static IHttpHandler getViewObject(
                             string Path, 
                             string ObjectName, 
                             HttpContext Context )
{
   string a_CurrentView = Context.Request.Form[LAST_VIEW_OBJECT];
   if( a_CurrentView == null )
   {
     // No previous view object registered, return the specified one
     a_CurrentView = ObjectName;
   }
   else
   {
     a_CurrentView += ".aspx";
     a_CurrentView = Context.Server.MapPath( a_CurrentView );
   }
   return BaseController.getPage( Path, a_CurrentView, Context );
}

Getting our compiled page instance

Note that the above code delegates the actual creation of the page to a helper method called getPage(). Now that we know the filename of the ASP.NET page that we want to handle the current request, we need to create an instance of the compiled version of that page. Luckily, the framework already provides this functionality in the PageParser class.

private static IHttpHandler getPage( 
string Path, 
string ObjectName , 
HttpContext Context )
{
  IHttpHandler a_Page;
  try
  {
    a_Page = PageParser.GetCompiledPageInstance( 
                        Path , 
                        ObjectName , 
                        Context )
  }
  catch(Exception e)
  {   
    // TODO: Create a global error page for the application.
    // if an exception occurs here, create an instance of the error
    // page, pass it the exception, and return it.
  }

  return a_Page;
}

Allowing our ASP.NET pages to communicate

Now that we've hooked up all of the necessary plumbing, we can add additional functionality to our base page, BaseController, to allow inheritors to create instances of other ASP.NET pages and communicate with them as real objects. One piece of functionality that we can provide is a means for child classes to create instances of other pages. We will do that by providing a method called createInstanceOf(). This method encapsulates the process required to call the getPage() method, which we discussed in the previous section.

protected IHttpHandler createInstanceOf( string ObjectName )
{
 return BaseController.getPage( 
               this.Context.Request.Path ,
               this.Context.Request.MapPath(ObjectName),
               Context );
} 
 

If a child class wishes to transfer control to another page, we will allow them to by providing a virtual method called getNextPage() that a child class can override. If the inheritor chooses to override this method and return a different page, control will transfer to the new page. If not, the current page will render itself as normal. We will examine the getNextPage() method signature and walk through an example of overriding it as we start adding real ASP.NET pages to our web application in the following section.

Our BaseController class calls getNextPage() late in the page generation cycle. It does so by overriding the Render() method of the page object, which is a perfect place to intercept the cycle and allow another ASP.NET page to step in and take over. Here is the first part of the Render() method.

protected override void Render( HtmlTextWriter writer )
{
  IHttpHandler aHandler = this.getNextPage();
  if( aHandler == null )
  {
    base.Render ( writer );
  }

If we do transfer control to a new page, a couple of housekeeping tasks must be done to ensure that the state of the new page is set up appropriately. Here, in the remainder of the Render() method, we will set a flag in the new page that we are passing the request to so that the appropriate state clearing logic is run.

  else
  {
    if( aHandler is BaseController )
    {
      ( ( BaseController )aHandler ).overrideLoadState = true;
    }
    aHandler.ProcessRequest ( this.Context );
}

The current Request object contains a lot of form information that was posted back to the current page. If we transfer the current request to a new page, we need to ensure that the next page does not attempt to find matching controls and attempt to give them state from the currently posted form. To accomplish this, we will need to override the LoadPageStateFromPersistenceMedium() method.

protected override object LoadPageStateFromPersistenceMedium()
{
  if( overrideLoadState )
  {
    State.Clear();
    return null;
  }
  return base.LoadPageStateFromPersistenceMedium();
}

We also need to ensure the IsPostBack property of the new page is appropriately set to false. We do this by overriding the DeterminePostBackMode() method of Page.

protected override NameValueCollection DeterminePostBackMode()
{
  if(overrideLoadState)
  {
    return null;
  }
  else
  {
    return base.DeterminePostBackMode();
  } 

Adding ASP.NET pages to the web application

The code in the previous section might seem a little daunting to someone who is just learning ASP.NET. The good news is that most of the coding is done. Adding ASP.NET pages to our application will not be difficult. We just need to ensure that our page's inherits from the BaseController class, which will do almost all of the work for us.

For our sample application, we will create two ASP.NET pages. The first one will contain a simple registration form. When this registration form has been properly filled out, it will transfer control to a confirmation page that will echo the information back to the user. We will accomplish this without redirecting the user's browser or storing any information in the URL or in the Session object. The first page will communicate directly with the second page and pass it the information.

Creating the registration form

Our registration form is a simple page with the filename subscribe.aspx. It contains fields for entry of a subscriber's first name, last name, and email address. It also contains a button to post the form back to the server.

In order for our new functionality to work, we must specify that our pages inherit from BaseController. We do this by changing their code-behind classes so that they inherit from BaseController instead of from Page. The following class diagram shows the inheritance hierarchy for our subscribe. aspx page. Note that when the PageParser rips through subscribe.aspx, it will create a class called ASP.Subscribe_ASPX.

Creating the confirmation screen

When the user has filled out the form in subscribe.aspx and clicked the button to post it to the server, we will pass the state directly to the confirmation page. The confirmation page will be called confirm.aspx. Again, the functionality of this page will be basic. It will contain a couple of ASP.NET label controls that will be used to confirm the fields entered back to the user.

We will create an instance of the confirmation page and pass the information directly to it. Then, we will allow the confirm.aspx page to render itself back to the user. To accomplish the code-behind class for subscribe.aspx will override the getNextPage() method that we discussed earlier.

protected override IHttpHandler getNextPage()

{
    if(this.IsValid && this.IsPostBack)
    {
      Confirm  aConfirmationPage = 
             this.createInstanceOf("confirm.aspx") as Confirm;
      Subscription aSubscription = new Subscription();
      aSubscription.EmailAddress = txtEmail.Text;
      aSubscription.FirstName = txtFirstName.Text;
      aSubscription.LastName = txtLastName.Text;
      aConfirmationPage.Subscription = aSubscription;
      return aConfirmationPage;
    }
    else
    {
      return null;
    }
}

In getNextPage() above, we check to make sure that we are in a valid state. If we are, we use the helper method, createInstance() defined in our parent class to create the instance of the Confirm page. We then create an instance of a Subscription class, fill in its values and pass it to the Confirm page. Our Confirm page instance then becomes the class responsible for rendering the next page for the user.

Imagine that! We have just passed a real object directly from one ASP.NET page to another! Obviously this is an overly simple example, but the basis of this code can be used to create applications with interactions between pages that make sense. And you will gain performance benefits in the process.

Using the Sample Code

Accompanying this article is a zipped file containing the small sample application. Included are the following files:

To setup the sample project, first download the source code. Then create a new application directory called "HttpHandler" in IIS and unzip the files to that directory. If you wish to experiment with this code, I have left a few unfinished pieces that you may implement.

Use the ASP.NET validation controls to validate the information submitted in the form. Note that the subscribe.aspx will not pass control to the confirmation page until the page itself is valid. The getPage() method of BaseController has a TODO. Create an error page that inherits from BaseController and can display any exceptions raised by the getPage() method. This page should have a setException() method that is called if an exception occurs. The error page can then log the error and display a user-friendly dialog to the user.

The sample code also comes with a Visual Studio.Net project that you can work with if you have Visual Studio. If you make any changes to the code-behind classes, you will need to recompile the sample application. If you are not using Visual Studio.Net to edit the code, you may recompile using the batch file, rebuild.bat. Conclusion

In this article, we have demonstrated an object-oriented approach to navigation between ASP.NET pages. We have also demonstrated that this approach is more scalable than redirecting the browser because it results in one less request/response loop between the browser and the server. The end result: a healthier, happier application.

Happy Coding!

Additional Links

ASP.NET First Principles ( http://dotnetdan.com/articles/aspnet/FirstPrinciples.htm )

ASP+ and Web Forms for Microsoft Visual Basic Users ( http://msdn.microsoft.com/library/techart/asppluswforms.htm )
 
 
   
  RATE THIS ARTICLE
  Please rate this article (1-5). Was this article...
 
 
Useful? No Yes, Very
 
Innovative? No Yes, Very
 
Informative? No Yes, Very
 
Brief Reader Comments?
Your Name:
(Optional)
 
  USEFUL LINKS
  Related Tasks:
 
 
   
 
 
       
  Search the ASPToday Living Book   ASPToday Living Book
 
  Index Full Text Advanced 
 
 
       
  Index Entries in this Article
 
  • ASP.NET page
  •  
  • config.web file
  •  
  • creating IHTTPHandler object
  •  
  • DeterminePostBackMode method
  •  
  • extending
  •  
  • GetHandler method
  •  
  • hidden form fields
  •  
  • HTTP Status codes
  •  
  • IHTTPHandler interface
  •  
  • IHTTPHandlerFactory interface
  •  
  • IsPostBack property
  •  
  • lifecycle
  •  
  • limitations
  •  
  • LoadPageStateFromPersistenceMedium method
  •  
  • Navigate methods
  •  
  • object oriented programming
  •  
  • Page Objects
  •  
  • page redirection mechanism, server-side
  •  
  • page redirection web application
  •  
  • Page.Navigate method
  •  
  • PageHandlerFactory class
  •  
  • PageParser class
  •  
  • ReleaseHandler method
  •  
  • Response.Redirect method
  •  
  • System.Web.UI namespace
  •  
  • web applications
  •  
     
     
    HOME | SITE MAP | INDEX | SEARCH | REFERENCE | FEEDBACK | ADVERTISE | SUBSCRIBE
    .NET Framework Components Data Access DNA 2000 E-commerce Performance
    Security Admin Site Design Scripting XML/Data Transfer Other Technologies

     
    ASPToday is brought to you by Wrox Press (http://www.wrox.com/). Please see our terms and conditions and privacy policy.
    ASPToday is optimised for Microsoft Internet Explorer 5 browsers.
    Please report any website problems to webmaster@asptoday.com. Copyright © 2001 Wrox Press. All Rights Reserved.