Cutting Edge

Form-based Programming in ASP.NET

Dino Esposito

Code download available at:CuttingEdge0305.exe(117 KB)

Contents

Reasons for a Single-form Model
The HtmlForm Class
Working with Forms in ASP.NET
Multiple Form Tags
Using User Controls
Using a Custom Form Control
Popup Forms
A Quick Look at Mobile Controls

One of the most common snags that ASP developers encounter when they first approach ASP.NET is that managed Web applications must be written according to a single-form interface model. I'll call this model SFI. Don't panic because you've never heard of SFI. I invented it for the sake of convenience to align with other acronyms that describe similar programming models—the single-document interface (SDI) and the multiple-document interface (MDI). I hope this will make the concept a little easier to grasp as you read through this column.

In the SFI model each page always posts to itself by default using the POST HTTP method. The HTTP method and the target frame of the post can be programmatically adjusted using ad hoc form properties—Method and Target. The final destination of the postback can't be changed in any way. In HTML and ASP programming, the form element features the Action property that programmers use to redirect to another URL after clicking. In ASP.NET, server-side forms are rendered using a class named HtmlForm. This class doesn't provide the familiar Action property, nor does it supply an alternative property. As a result, the SFI model is so entrenched in ASP.NET that there's no way around it.

In this column, I'll review programming forms in ASP.NET and discuss ways to implement a multi-form programming model. In doing so, I'll also take a look at the details of the multi-form model available for ASP.NET mobile controls.

Reasons for a Single-form Model

Technically speaking, in ASP.NET a kind of multi-form interface (MFI—again, my acronym) is still possible but requires a number of tricks. By design, an ASP.NET page can host exactly one server form. As I'll demonstrate later, though, you can add any number of HTML <form> tags without the runat attribute.

If you place two or more server forms in a page, you won't get any error at compile time. The request for the page is therefore processed as usual using the code built in the Page base class. The temporary class for the page is generated, compiled, and finally loaded. The ASP.NET runtime begins processing the page request and regularly fires the Init, Load, and PreRender events. All these events are handled seamlessly. After the events are handled, the page enters its rendering phase.

The class that represents the page calls the Render method on all the individual controls included in the page. So the first <form> block is successfully processed and rendered. After that, the page object sets an internal flag to note that one form object has been processed for the page. Can you guess what happens next?

When a <form runat="server"> block is found, the page first verifies that no similar block has already been processed. If the aforementioned internal flag is set, an HttpException is thrown. More precisely, the page loops through its child controls and asks them to render. HtmlForm classes are no exception. So the following methods are called in sequence:

Page.RenderControl 
••• 
HtmlForm.Render 
HtmlForm.RenderChildren

RenderChildren is a protected virtual method defined on the class HtmlForm. It renders the form's child controls within two calls to internal methods on the Page object, as you can see in the following pseudocode:

protected virtual void RenderChildren(HtmlTextWriter writer) {
    Page.OnFormRender(writer, ClientID);
    base.RenderChildren(writer);
    Page.OnFormPostRender(writer, ClientID);
}

OnFormRender and OnFormPostRender are internal methods of the Page class that control the rendering of the forms. In particular, the first method sets a Boolean variable that is responsible for throwing the exception (see Figure 1). The following pseudocode illustrates the structure of the OnFormRender method:

void OnFormRender(HtmlTextWriter writer, string formID) {
    if (serverFormCreated) 
      throw new HttpException("Multiple_forms_not_allowed");
    serverFormCreated = true;
    •••
}

Figure 1 Error

Figure 1** Error **

What does this mean to you? As of ASP.NET 1.1, there's no compile-time control on multiple server-side forms. You could include as many server-side forms as you need in a Web Forms page, as long as you ensure that no more than one is visible at any time. In practice, if you set the Visible attribute of additional server-side forms to false, the page will render just fine. Of course, you can control the Visible attribute of forms programmatically and decide which one to show based on run-time conditions. Incidentally, a similar approach is comparable to that of ASP.NET mobile forms in which multiple server-side forms can be defined but only one form at a time is visible. The code in Figure 2 shows a trivial ASP.NET page with multiple, but mutually exclusive, forms.

Figure 2 Multiple Forms

<%@ Page Language="C#" %>
<script runat="server"></script>
<html>
    <body>
        <form runat="server" id="First"> First form </form>
        <form runat="server" id="Second" visible="false"> Second form </form>
        <form runat="server" id="Third" visible="false"> Third form </form>
    </body>
</html>

The single-form model is a key element for the implementation of the ASP.NET state maintenance feature. Since the page always posts to itself, the runtime finds it easy to retrieve and restore the state saved during the last access. While there are other ways to save and restore the state of a page, using a single-form model is by far the simplest and most effective of all solutions.

In general, renouncing multiple forms is not a big sacrifice. Multiple forms are sometimes useful, but probably not as frequently as you may think. A scenario in which you would find it helpful to use multiple forms is when you implement form-based functionality in a large number of pages—in search and login dialogs, for example. I'll return to this later in the column. Before going any further, though, a review of the HtmlForm class is in order.

The HtmlForm Class

The HtmlForm class inherits from HtmlContainerControl and implements the IAttributeAccessor interface. The base class provides HtmlForm with the ability to contain child controls. This capability is shared with other HTML control classes, such as HtmlTable, which is characterized by child elements and a closing tag. The IAttributeAccessor interface defines two methods, GetAttribute and SetAttribute, which are used to read attribute values out of the opening tag.

The HtmlForm class provides programmatic access to the HTML <form> element on the server through the set of properties listed in Figure 3. The form must have a unique name. If the programmer doesn't assign the name, ASP.NET generates one using a built-in algorithm. The default name follows the pattern _ctlX, in which X is a unique integer—typically the ordinal position of the control in the page. The programmer can set the form's identifier either using the ID or the Name property. If both are set, the ID attribute takes precedence. Based on the value of ID, ASP.NET determines the values of UniqueID and ClientID. UniqueID is a fully qualified string based on the naming container of the form. ClientID, on the other hand, is used to identify a control for client-side operations, such as JavaScript functions. It is worth noting, however, that this logic applies to all server controls, not just forms.

Figure 3 HTMLForm Properties

Property Description
Attributes Returns a name/value collection with all the attributes declared on the tag
ClientID Returns the value of UniqueID
Controls Gets a collection object that represents the child controls of the form
Disabled Gets or sets a value indicating whether the form is disabled; match the disabled HTML attribute
EnableViewState Rarely used at this level; gets or sets a value indicating whether the state of child controls should be persisted
Enctype Gets or sets the encoding type; match the enctype HTML attribute
ID Gets or sets the programmatic identifier of the form
InnerHtml Gets or sets the markup content found between the opening and closing tags of the form
InnerText Gets or sets the text between the opening and closing tags of the form
Method Indicates how a browser posts form data to the server; the default value is POST; can be set to GET if needed
Name Returns the value of UniqueID
NamingContainer Gets a reference to the form's naming container, typically the host page
Page Gets a reference to the host page
Parent Gets a reference to the parent object; the parent object is typically, but not necessarily, the page
Site Returns null
Style Gets a collection of all cascading style sheet properties applied to the form
TagName Returns "form"
Target Gets or sets the name of the frame or window to render the HTML generated for the page
TemplateSourceDirectory Gets the virtual directory of the page that contains the form
UniqueID Gets the unique, fully qualified name of the form
Visible If false, the form is not rendered to HTML

The parent object of the form is the innermost container control with the runat attribute. If such a control doesn't exist, the page object itself is set as the parent. Typical containers for the server form are the <table> and <div> elements, if they're marked runat=server.

By default, the Method property is set to POST. The value of the property can be modified programmatically. If the form is posted through the GET method, all of the form data is passed on the URL's query string. However, if you choose the GET method remember that the limited size of a GET request can truncate your data if you're not careful.

Figure 4 lists the methods available on the HtmlForm class. All of these methods are inherited from the base System.Web.UI.Control class. You should note that the FindControl method only searches in the form's naming container. Controls belonging to an inner naming container (for example, a user control or a DataGrid control) are not found.

Figure 4 HTMLForm Methods

Method Description
DataBind Calls the DataBind method on all controls within the form
Dispose Performs the final cleanup before the form is freed
FindControl Retrieves and returns the child control that matches the given ID
HasControls Indicates whether the form contains any child controls
RenderControl Outputs the HTML code for the form; if tracing is enabled, caches tracing information to be rendered later at the end of the page
ResolveUrl Ensures that the specified URL is absolute; reads the base URL from the TemplateSourceDirectory property

The HtmlForm class is not sealed and thus can be further inherited. It also features a bunch of protected, overridable methods that you could exploit to customize the overall behavior.

Working with Forms in ASP.NET

Some pages need to have multiple logical forms. A logical form is not necessarily what you think of when you think of an HTML form. It is simply a logically related group of input controls whose contents can be submitted all at once to a piece of code that can handle it—a group of controls that, taken together, act like a form. Using a <form> tag to unite these controls is the simplest way to make a logical form. ASP.NET doesn't let you use multiple server-side <form> tags. However, you can use alternative ways to implement a logical form.

For example, consider a login page designated for restricted areas of a Web site. Registered users just type in their user name and password and connect. Unregistered users need to fill out a form before they're given the parameters to connect. How do you code this? Let's review a few options.

Figure 5 HTML Forms Posting

Figure 5** HTML Forms Posting **

In ASP, you can use two HTML forms, each posting to a different page. The schema is roughly outlined in Figure 5. This solution is impracticable in ASP.NET if you're not using plain HTML tags (tags without the runat attribute) and an ASP programming style. (More on this in a moment.) The solution depicted in Figure 6 is better suited to ASP.NET.

Figure 6 Better Posting Solution

Figure 6** Better Posting Solution **

One physical <form> tag contains multiple logical forms. All logical forms post to the same page and hide any unneeded user interface element. A unique page is involved and contains a single <form> tag. Within the opening and closing tags of the form, there can be two or more groups of logically related controls. Each group can post back either using a classic submit button or a hyperlink bound to some script code—what is known as a link button in ASP.NET. Although the various groups are not forms in the technical sense of the word, they do behave like HTML forms. All groups post to the same page, but the ASP.NET infrastructure guarantees both state maintenance and the execution of a piece of code specific to the group that caused the post. Each link button is associated with a server-side method that runs when the page posts back after the user clicks.

Functionally speaking, the mechanism is in no way different from the ASP implementation. Furthermore, the programming model enjoys a higher abstraction layer and handy features such as view state and server-side control programming.

How can you update the user interface of the HTML being generated? ASP.NET server controls can be hidden from view or restored programmatically. Controls with the Visible property set to false are ignored by the ASP.NET parser and no HTML code will ever be generated for them. Looking at Figure 6, you can design your login page to contain three blocks of HTML code: the login form, the registration form, and the restricted area. The first time you show the page—that is, when IsPostBack is set to false—the login and register blocks are displayed while the restricted area remains invisible and is completely ignored in the rendering phase. Next, the user clicks to either log in or register. The page posts back to execute code. When registering the new user, or checking the credentials of a known user, you simply toggle off the visibility of the forms and toggle on that of the restricted area.

The code in Figure 7 contains a form with two logical subforms, one with a button and one with the typical login fields. Both are wrapped by a server-side <div> panel. When the page displays the first time, only the first pseudo-form is visible. When the button is clicked, the RunDlg block is hidden and the other, named Login, is displayed. Keep in mind that when the Visible attribute is set to false, no code is generated for controls.

Figure 7 Two Logical Subforms

<%@ Page Language="C#" %>
<script runat="server"> 
void OnShowLogin(object sender, EventArgs e) { 
  RunDlg.Visible = false; 
  Login.Visible = true; 
} 
void OnLogIn(object sender, EventArgs e) { 
  RunDlg.Visible = true; 
  Login.Visible = false; 
}
</script>
<html>
    <body>
        <form runat="server">
            <div runat="server" id="RunDlg"> Click here to login. 
                <br/>
                <asp:button runat="server" text="Login" 
                  onclick="OnShowLogin" />
            </div>
            <div runat="server" id="Login" visible="false">
                <hr/>
                <b>Login</b>
                <br/>
                <asp:textbox runat="server" id="UserName" />
                <br/>
                <asp:textbox runat="server" id="Pswd" 
                  textmode="password" />
                <br/>
                <asp:button runat="server" text="Log in" 
                  onclick="OnLogIn" />
                <hr/>
            </div>
        </form>
    </body>
</html>

Using this approach, you have a single page that contains a few logical forms and only one form is displayed at any time. It goes without saying that this approach can be extended a little further to execute code on an external page. So far, I assumed that the code to process the contents of the logical form was embedded in the host page. However, nothing really prevents you from transferring the control to another page, thus simulating an HTML form. For a better implementation of the solution that is shown in Figure 7, you can take advantage of user controls in order to wrap the contents of server-side <div> blocks.

In general, if it seems that there's no other way to code than using multiple forms, then you should probably rethink the code. By abstracting things a little bit, you can have an equally effective ASP.NET page with a single form and a more object-oriented design. In ASP.NET, think in terms of classes and functionality rather than in terms of HTML forms. However, especially if you're engaged in the migration of a complex Web site (such as a portal), resorting to multiple forms might be the fastest way to be not only on time but also on budget.

In many cases, you can make better use of the power of ASP.NET by redesigning the application so that you don't need multiple HTML forms. If the forms just point to completely different pages (search pages or mail pages) you can consider extensions to the ASP.NET model to simulate multiple forms, as in ASP, but without losing the state maintenance facilities of ASP.NET.

Multiple Form Tags

Having multiple HTML forms in an ASP.NET page is not a problem as long as you don't require automatic state maintenance. Even though multiple forms are employed in an ASP.NET page, only one of them can have the runat attribute set to server. Subsequently, ASP.NET can only provide view state management and postback events for the controls contained on that form. Let's consider the code for a Web Forms page shown in Figure 8.

Figure 8 Web Forms Page

<!-- This is test.aspx -->
<html>
    <body>
        <form runat="server">
            <asp:textbox runat="server" id="theTextBox" />
            <asp:button runat="server" text="Post back" />
        </form>
        <form method="post" action="another.asp">
            <input type="text" name="AnotherField" />
            <input type="submit" id="submit1" value="another.asp(x)" />
        </form>
        <form method="post" action="test.aspx">
            <input type="text" name="ThirdField" />
            <input type="submit" id="submit2" value="This page" />
        </form>
    </body>
</html>

As you can see, the second and the third <form> tags are not marked with runat="server", meaning that the ASP.NET runtime will treat them as plain text to send out as is. When you click on the button named submit1, the browser navigates to a page named another.aspx. It is a brand new ASP.NET page that knows nothing about the main page's controls. You can still retrieve values from the referrer page, but only using the old ASP programming style. You retrieve only the values from the form that caused the post.

<%@ Page Language="C#" %>
<script runat="server"> 
void Page_Load(object sender, EventArgs e) { 
  Response.Write(Request.Form["SecondField"]); 
}
</script>

ASP.NET ignores the controls defined outside the first server-side form of the page. When you click on the submit button associated with the server form, the page posts back but the content of any other textboxes is cleared. The same side effect occurs when you submit form's data using the third button. It is the submit button of an HTML form that posts to the same page—test.aspx. Posting to the current page is exactly what ASP.NET does, but why doesn't it work as usual if you command the post from within a client-side HTML form?

It is simply this: the HtmlForm class working on the server generates any needed HTML code for a form but also runs some code to set view state information for the controls in the form. That's the importance of playing by the rules of ASP.NET.

Is there a way to use multiple server-side forms in ASP.NET applications? As mentioned earlier, the built-in code of ASP.NET pages raises an exception if more than one server-side form is rendered—regardless of whether you created it programmatically or declaratively. A possible workaround entails the creation of server components that are logically equivalent to forms. Such components are all contained within a single server-side form, but provide you with the subset of functionality that allows them to gather and submit some input data to an external page. The simplest way to create such embeddable components is through user controls.

Using User Controls

The idea is that you create a user control for each logical form you need to display. The final structure of the page looks like this:

<form runat="server">
    <msdn:MyRegForm runat="server" 
      id="userRegForm" action="register.aspx" />
    <hr/>
    <msdn:MyLogForm runat="server" 
      id="userLogForm" action="login.aspx" />
</form>

In addition to managing child controls, the user controls also provide a basic set of features, such as the Action property. The following code snippet shows a sample form user control:

<%@ Control ClassName="RegisterForm" Inherits="MsdnMag.MyUserForm" %>
<b>Register</b>
<br />
<asp:textbox runat="server" id="RegUserName" />
<br />
<asp:textbox runat="server" id="RegPassword" textmode="password" />
<br />
<asp:button runat="server" text="Register" onclick="Submit" />

You don't need to add an Action property and a Submit method to each user control. You can simply derive user controls from a base class. In my sample code, I've called the base class MsdnMag.MyUserForm (see Figure 9).

Figure 9 MyUserForm Base Class

namespace MsdnMag {
    public class MyUserForm: System.Web.UI.UserControl {
        public string Action;
        protected virtual void Submit(object sender, System.EventArgs e) {
            foreach(object o in Page.Request.Form) {
                // NOTE that : is an implementation detail that could 
                // change in future implementations 
                string ctlName = o.ToString();
                if (!ctlName.StartsWith(UniqueID + ":")) continue;
                string[] parts = ctlName.Split(':');
                // Use the last part of the control name as the 
                // key for the Context collection. The post value 
                // is correctly retrieved using the full name 
                Context.Items[parts[parts.Length - 1]] = 
                  Page.Request.Form[ctlName];
            }
            Page.Server.Transfer(Action);
        }
    }
}

The base class provides a public property called Action and a Submit method that can be used as an event handler. When the user clicks on a submit button on a similar user control, the code extracts all the request form values that relate to the user control. A user control is a naming container, which means that the name of its child controls is prefixed by the ID of the user control. The foreach loop in Figure 9 walks its way through the page's Request object and selects all the values that belong to the user controls. Note that I am making assumptions on the : symbol, which actually is an implementation detail that might change in the future. These values are cached in the HttpContext object. HttpContext represents the context of the request being processed. It is a global object whose life spans the life of the request. After filling the Context.Items collection, the Submit method transfers the control to the page specified by the Action property.

How can this page retrieve the values of the caller form? Of course, it gets input data reading from the context of the request:

<html>
    <body> User 
        <b><%= Context.Items["RegUserName"] %>
        </b> has been registered. Password is 
        <b><%= Context.Items["RegPassword"] %>
        </b>. 
    </body>
</html>

In terms of programming style, you use Context.Items in much the same way you used Request.Form in ASP pages. Context is more efficient than Session because it doesn't persist any longer than the page instance. Context.Items is an effective, generic way of passing parameters to pages run using Server.Transfer or Server.Execute.

Looking at user controls from this perspective, one drawback quickly emerges. You must create and register a new .ascx file for each logical form you need to employ, like this:

<%@ Register TagPrefix="msdn" TagName="MyRegForm" src="register.ascx" %>
<%@ Register TagPrefix="msdn" TagName="MyLogForm" src="login.ascx" %>

On the other hand, you can't define the contents of a user control inline in the host page as if it were a <form> or a <div> tag. What about extending the ASP.NET control that represents a <div> tag to derive a new container control that exposes an Action property and a Submit method?

Using a Custom Form Control

The <div> tag has the built-in capability of parsing any child tags. By deriving a new class, you can acquire the same capability. The <div> tag is represented on the server through a generic class called HtmlGenericControl. This class is generic because it doesn't provide a programming interface tailor-made to the features of the <div> control. ASP.NET utilizes the HtmlGenericControl to render a handful of HTML tags when these are used with the runat attribute. Generic HTML controls include the <div>, <font>, <span>, and <body> elements. Figure 10 shows the source code of the MsdnMag.Form class that inherits from HtmlGenericControl. By default, the constructor of the HtmlGenericControl class generates a <span> tag. If you want it to create another HTML container, use the proper constructor overload:

public class Form : HtmlGenericControl, INamingContainer { 
  public Form(string tag) : base("div") {...}

Figure 10 MsdnMag.Form

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace MsdnMag {
    public class Form: HtmlGenericControl,
    INamingContainer {
        public Form(string tag): base("div") {}
        public string Action;
        public virtual void Submit(object sender, EventArgs e) {
            foreach(object o in Page.Request.Form) {
                string ctlName = o.ToString();
                if (!ctlName.StartsWith(UniqueID + ":")) continue;
                string[] parts = ctlName.Split(':');
                Context.Items[parts[1]] = Page.Request.Form[ctlName];
            }
            Page.Server.Transfer(Action);
        }
    }
}

When the user clicks on the submit button of the logical form, a predefined Submit method executes. It looks up all the input controls in the logical form and copies their values in the HTTP context. The INamingContainer interface is necessary to guarantee that all the controls belonging to the logical form, and no others, are loaded into the context. INamingContainer is a marker interface that means that no methods are to be implemented. However, a class that exposes this interface enjoys special treatment from the ASP.NET framework. In particular, the child controls are given an ID that is prefixed with the ID of the parent. As for user controls—the base UserControl class implements INamingContainer itself—you can check the prefix of the child controls referenced in the overall page's Request.Form collection and select only those contained in your logical form. Note that this is not the only way you can obtain the same information.

In order to register the new Form class with the page, you use a variant of the @Register directive, as shown here:

<%@ Register tagprefix="msdn" 
  Namespace="MsdnMag" Assembly="MyForm" %>

Since Form is a custom control, you must first compile it into an assembly and make it available within the ..\bin folder of the application's virtual directory and the application's virtual directory or the Global Assembly Cache (GAC). Figure 11 and Figure 12 show the source code and the output of a page that uses the techniques that have been examined so far.

Figure 11 New Source Code

<%@ Page ClassName="MultiFormPage" %><%@ Register TagPrefix="msdn" TagName="MyRegForm" src="register.ascx" %><%@ Register TagPrefix="msdn" TagName="MyLogForm" src="login.ascx" %><%@ Register tagprefix="msdn" Namespace="MsdnMag" Assembly="MyForm" %>
<script language="C#" runat="server"></script>
<html>
    <head>
        <title>Pro ASP.NET (Ch04)</title>
    </head>
    <body>
        <form runat="server">
            <msdn:MyRegForm runat="server"
              id="userRegForm" action="register.aspx" />
            <hr />
            <msdn:MyLogForm runat="server" 
              id="userLogForm" action="login.aspx" />
            <hr />
            <msdn:form id="myRegForm" runat="server" 
              action="register.aspx">
                <b>Register</b>
                <br />
                <asp:textbox runat="server" id="RegUserName" />
                <br />
                <asp:textbox runat="server" 
                  id="RegPassword" textmode="password" />
                <br />
                <asp:button runat="server" 
                  text="Register" onclick="myRegForm.Submit" />
            </msdn:form>
            <msdn:form id="myLogForm" runat="server" action="login.aspx">
                <b>Login</b>
                <br />
                <asp:textbox runat="server" id="LogUserName" />
                <br />
                <asp:textbox runat="server" 
                  id="LogPassword" textmode="password" />
                <br />
                <asp:button runat="server" 
                  text="Log in" onclick="myLogForm.Submit" />
            </msdn:form>
        </form>
    </body>
</html>

Figure 12 Output

Figure 12** Output **

Popup Forms

To top off the discussion about ASP.NET forms, let's analyze what happens with popup forms. The HTML 4.0 object model allows you to use the window.showModalDialog method to show a modal window with a Web page inside. ASP.NET pages can be used with popup windows, but with some caveats. If the page has read-only contents, then no special care need be taken. You just open the window using boilerplate client-side script code and let it go. By contrast, if the page shown through a modal window is interactive and posts back, some measures should be taken to ensure that all the generated output goes through the modal dialog until the user closes it.

As an alternative to client-side modal dialogs, to show logically modal forms you could resort to the trick I discussed earlier—building multiple forms using only ASP.NET code (see Figure 7). Let's drill down a bit.

To perform a modal operation—a blocking operation that prevents interaction with the main page until terminated—a Web page can resort to the following JavaScript code:

<script language="JavaScript"> 
function ShowPopup() { 
  window.showModalDialog("dialog.aspx", "Dialog", 
    "dialogWidth:200px;dialogHeight:300px;"); 
} 
    
</script>
<html>
    <body>
        <input type="button" onclick="ShowPopup()" value="Popup" />
    </body>
</html>

The showModalDialog method on the window object opens the specified page in a Windows® dialog box with the given title and styles. (For more information about the styling options of the dialog see the MSDN® documentation.) The previous code pops up a 200×300 pixel modal window filled with the HTML content generated by dialog.aspx. So far, so good.

Suppose now that the dialog.aspx page contains some links and buttons you can click. Again, think of a modal page you want to pop up for user registration and logging. The user enters some data and clicks; the page posts back, but the output is displayed in a new browser window. Any value you assign to the form's target value is useless. The behavior is by design, but is there a workaround? Yes, for the browsers that support it, use a wrapper inline frame. Instead of containing the code to show, dialog.aspx contains an <iframe> tag. Part of the HTML 4.0 specification, the <iframe> element allows authors to insert a frame within a block of text. The source document that goes in the frame is designated by the Src attribute:

<iframe src="Pages/multiforms.aspx" name="embeddedFrame" 
  width="100%" height="100%" />

Figure 13 shows an ASP.NET popup application in action.

Figure 13 ASP Popup

Figure 13** ASP Popup **

A Quick Look at Mobile Controls

ASP.NET mobile controls, an extension of ASP.NET desktop server controls, are aimed at building wireless applications. ASP.NET mobile controls are server controls that require the runat attribute to be set to server. Since they execute on the server, they can exploit the potential of the .NET Framework in spite of the modest amount of memory typically available on wireless devices. All mobile controls derive from the MobileControl class which, in turn, inherits from Control. Because of this relationship, both server and mobile controls share a common set of properties, methods, and events. Mobile Web Forms, on the other hand, descend from the MobilePage class. ASP.NET mobile controls include a couple of controls—Panel and Form—that act as containers of other controls. These controls are different in that the Form supports the posting of data, whereas the Panel represents a simple static group of related controls. Multiple panels can be nested and included in forms, but forms themselves cannot be nested.

The Form control represents the outermost container of controls in a mobile page. A page can contain multiple forms that you can address individually from within the application. This sets Mobile Web Forms apart from ASP.NET Web Forms in which only one server-side form is permitted. Mobile ASP.NET never combines multiple forms on a single screen, but uses them in a mutually exclusive way.

The Form control features common-use properties such as Action and Method. Action defaults to the empty string, which causes a postback to the same URL. A form fires events when activated and deactivated.

When multiple forms are active on the same page, you can move from one to another using a Link control. To make a Link control point to an internal form, you set the NavigateUrl property to the ID of the form prefixed with a # symbol, like this:

<mobile:Form id="Form2" runat="server">
    <mobile:Label runat="server" text="Second page of information" />
    <mobile:Link runat="server" NavigateUrl="#Form1" text="Back" />
</mobile:Form>

The ActiveForm property on the mobile page class gets and sets the currently active form. When a page is initially rendered, the first form in the page is automatically activated and displayed. On subsequent postbacks, another form might be brought to the foreground, either programmatically or as the result of user navigation. Implementing a similar model in ASP.NET is not particularly difficult, but probably requires that you inherit your pages from an application-specific base class whose interface is extended with properties and methods to handle the currently active form.

Send your questions and comments for Dino to cutting@microsoft.com.

Dino Espositois an instructor and consultant based in Rome, Italy. Author of Building Web Solutions with ASP.NET and ADO.NET and Applied XML Programming for .NET, both from Microsoft Press, he spends most of his time teaching classes on ASP.NET and speaking at conferences. Dino is currently writing Programming ASP.NET for Microsoft Press. Get in touch at dinoe@wintellect.com.