Design Considerations for Cross Page Post Backs in ASP.NET 2.0

Monday, July 25, 2005

In ASP.NET we’ve become accustomed to web forms posting back only to themselves. Since this behavior tends to create big balls of mud, we’ve looked for ways to push data between forms.

In ASP.NET 2.0, there are three approaches: Response.Redirect, Server.Transfer (improved in 2.0), and the new 2.0 cross page postback feature. Each approach has pros, cons, and subtle nuances. I’m attempting a thorough examination of how to plan your attack in this post.

Imagine a web form requiring a user to enter a parameter into a TextBox control. An excerpt might look like the following.

  <asp:TextBox ID="Parameter" runat="server"/>

 

  <asp:RequiredFieldValidator ID="ParameterValidator"

       runat="server" ControlToValidate="Parameter"

       EnableClientScript="False" ErrorMessage="Required Field"/>

Note EnableClientScript=false in the validator. This will simulate a client with scripting turned off (a scenario we should always be prepared to handle), and leads to some interesting behaviors later in some examples that follow.

Response.Redirect

One way to get this parameter to a second form would be the Response.Redirect approach. We could add a button to the form with the following event handler.

protected void Redirect_Click(object sender, EventArgs e)

{

  if (Page.IsValid)

  {

    Response.Redirect(

        "destination.aspx?parameter=" +

        Server.UrlEncode(TextBox1.Text)

      );

  }

}

This parameter is easy to pick up in the destination web form.

protected void Page_Load(object sender, EventArgs e)

{

  string parameter = Request["parameter"];

  if(!String.IsNullOrEmpty(parameter))

  {

      // party on the parameter       

  }

}

Some people will crib about Response.Redirect needing a round trip to the client, but it is a simple approach. However, the length of the query string is limited, and we may not want the parameter displaying in the destination’s URL. Query strings also tend to lead to “magic string literals” in the source code, but even if we took the time to define a const string variable for both the sender and receiver forms to use, the interface between the two is weak and prone to break.

Server.Transfer

An alternate approach, one that improves in 2.0, is to use Server.Transfer. I previously mentioned Server.Transfer in criticism of the ASP.NET 2.0 compilation model, but like a blind man with an elephant I hadn’t quite felt the full elephant. This approach has some great improvements in 2.0. The code for the sender might look like the following.

protected void Transfer_Click(object sender, EventArgs e)

{

  if (Page.IsValid)

  {

    Server.Transfer("destination.aspx");

  }

}

The receiving web form has several options to fetch data from the first web form. All of these options involve a new property on the Page class in 2.0: PreviousPage. PreviousPage represents the originating page in transfer and cross page post back operations. Pulling data in the destination page could look like the following.

protected void Page_Load(object sender, EventArgs e)

{

  if (PreviousPage != null)

  {

    TextBox textBox = PreviousPage.FindControl("Parameter")

                        as TextBox;

 

    if (textBox != null)

    {

      string parameter = textBox.Text;

      Parameter.Text = parameter;

    }

 

  }   

}

The problem I have with the approach is that FindControl is easily breakable. All someone has to do is modify change an ID or push the TextBox inside a naming container, and FindControl will return null. You might be thinking about exposing the parameter as a property of the original form, and that’s what I’m thinking, too.

public string ParameterText

{

  get { return Parameter.Text; }

}

Now we have a different problem. You might think we could just cast PreviousPage to the type of the originating form, but you have to be careful with the new compilation model in ASP.NET 2.0. Each form might compile into a different assembly. Instead of casting we will take advantage of the new compilation model and the @ PreviousPage directive. All we need in the destination form is the following:

<%@ PreviousPageType VirtualPath="~/Default.aspx"  %>

The directive will give us a strongly typed PreviousPage property. In other words, instead of retuning a System.Web.UI.Page reference, PreviousPage will return the type of the first web form, like ASP.Default_aspx. This makes the job easy.

protected void Page_Load(object sender, EventArgs e)

{

  if (PreviousPage != null)

  {

    string parameter = PreviousPage.ParameterText;

    if(!string.IsNullOrEmpty(parameter))

    {

        // party!

       Parameter.Text = parameter;

    }

  }

}

As simple as this appears to be, we still have problem. We’ve tied these two forms together. What if we wanted to make the second form a transfer destination for different pages in the application? What if we just didn’t feel comfortable referencing one web form from another? Let’s put the following into the App_Code (or a referenced class library).

using System;

 

public interface IParameterForm

{

    string ParameterText

    {

        get;

    }

}

Now all we need to do is include IParameterForm in the derivation list for our original page class. We can remove the @ PreviousPage directive from the second form and use the following code instead.

protected void Page_Load(object sender, EventArgs e)

{

  if (PreviousPage != null)

  {

    IParameterForm form = PreviousPage as IParameterForm;

    if (form != null)

    {

      string parameter = form.ParameterText;

      if (!string.IsNullOrEmpty(parameter))

      {

        // party!!

      }

    }

  }

}

It requires a little more code to dig the parameter out, but we have decoupled the forms and gained some flexibility. Any number of pages can transfer into this destination page.

Server.Transfer does come with disadvantages. The most serious is that the URL in the browser does not change. The browser still believes it has posted back and received content for the first web form, so history and book-marking suffer. These issues are fixed with the new cross page postback feature.

Cross page Postbacks

A cross page postback all starts with by setting the PostBackUrl property of the Button control.

<asp:Button

      ID="CrossPagePost" runat="server"

      PostBackUrl="~/destination.aspx"

      Text="CrossPagePost" />

We do not need to respond to the ClickEvent for this button. When the user clicks the button, client side JavaScript will set our form’s action attribute to point to the destination page before posting.

In the destination form, a client side post back will give us a non-null PreviousPage property. We can even have a strongly typed PreviousPage property if we use a @ PreviousPage directive. For now, we will leave our destination page code almost as is.

protected void Page_Load(object sender, EventArgs e)

{

  if (PreviousPage != null &&

      PreviousPage.IsCrossPagePostBack)

  {

    IParameterForm form = PreviousPage as IParameterForm;

    if (form != null)

    {

      string parameter = form.ParameterText;

      if (!string.IsNullOrEmpty(parameter))

      {

        // party!!

      }

    }

  }

}

Notice we have an IsCrossPagePostBack property we can evaluate to determine if the request arrived as a result of cross page post back. A value of false means the PreviousPage did a Server.Transfer.

It’s also interesting to note the behavior of the PreviousPage property during a cross page post back. PreviousPage uses lazy evaluation, meaning no previous page will exist until the first time you touch the property. At that point the runtime will load a new instance of the PreviousPage web form and have it start to process the current request. This might sound odd at first, but we’ll see why the approach makes sense.

In order to extract data from the PreviousPage, the PreviousPage will need to be instantiated, load it’s ViewState, and respond to the typical events like Init and Load. With Server.Transfer this happens automatically, because the request arrives at the PreviousPage first, which then has a chance to restore data to it’s controls before handing off processing to the destination web form using Server.Transfer. With a cross page post back the request arrives at the destination web form, which then must ask the PreviousPage to execute in order to restore itself. The PreviousPage will execute all stages up to, but not including, the PreRender event, at which point control returns to the destination page.

Cross Page Post Backs and Validation

In the beginning of the article we foreshadowed a special situation arising with validation controls in a cross page post back scenario. If validation is working on the client, then the validation will prevent a post back until the user passes all the client side validation tests. However, we can’t always use validation on the client (some custom validation controls will only be able to validate servers-side), nor can we always trust the client to perform validation. Server side validation presents a problem for cross page post backs because our response will come from the destination web form instead of the original web form where the user failed validation.

Let’s try to make the scenario clear. Validation controls will run server-side when a cross page post back occurs, and the destination page inspects the PreviousPage.IsValid property. As we mentioned earlier, when we touch the PreviousPage property the runtime loads and executes the PreviousPage form, including the validation controls. We must check the IsValid property of our previous page to make sure the page passed all validation tests.

protected void Page_Load(object sender, EventArgs e)

{

  if (PreviousPage != null &&

      PreviousPage.IsCrossPagePostBack &&

      PreviousPage.IsValid)

  {

    IParameterForm form = PreviousPage as IParameterForm;

    if (form != null)

    {

      string parameter = form.ParameterText;

      if (!string.IsNullOrEmpty(parameter))

      {

        // party!!

      }

    }

  }

}

The hard question to answer is what do we do when the user failed the validation tests? There is no easy way to get them back to the original web form, with their input in tact, and with an explanation about why their validation tests failed (remember, validation isn’t working on the client in this case). With Server.Transfer this scenario was not a problem, as we could skip the Transfer when validation failed and let the validation controls display error messages to the user from the original web form.

The above question is one you’ll need to answer when deciding to use cross page post back on pages with validation controls. One answer might be to require a client to enable scripting for your site. You’ll still want to check PreviousPage.IsValid before accepting any input over the network, and spit out some informative error message for the bots and clients with no scripting enabled.

Conclusions

We’ve uncovered four rules of thumb in this article.

  • Avoid Response.Redirect if you have too many parameters for a query string, or don’t want parameters to appear in the URL.
  • Avoid Server.Transfer if you need to display a new URL in the browser for a web form, or need a reliable browser history for usability
  • Take care with cross page post backs and validation, particularly if you need to use server-side validation only.
  • Use an adapter pattern to decouple your web forms and allow a destination page to fetch parameters without knowing the type of the originating page.

By K. Scott Allen

Leave comments and questions about this article on my blog.

by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!