Programming with Forms

 

Programming with Forms

One of the most common snags ASP developers face when they first approach the ASP.NET lifestyle is the fact that managed Web applications support the single-form interface (SFI) model.

Note 

If you've never heard anyone use the SFI acronym, there's no reason for you to panic. It's an acronym I've purposely created to mimic other more popular acronyms that, although used in different contexts, describe similar programming models the single-document interface (SDI) and its opposite, the multiple-document interface (MDI).

In the SFI model, each page always posts to itself and doesn't supply any hook for developers to set the final destination of the postback. What in HTML and ASP programming was the Action property of the form is simply not defined on the ASP.NET HtmlForm class. As a result, the SFI model is a built-in feature so deeply integrated with the ASP.NET platform that you have only two choices: take it, or code the old ASP way without server forms. Note that in ASP.NET 2.0, posting data to different pages is possible, but the implementation of the feature passes through some new capabilities of button controls. Forms work in ASP.NET 2.0 the same way they do in ASP.NET 1.x.

Unlike the action URL, the HTTP method and the target frame of the post can be programmatically adjusted using ad hoc HtmlForm properties Method and Target.

The HtmlForm Class

The HtmlForm class inherits from HtmlContainerControl, which provides the form with the capability of containing child controls. This capability is shared with other HTML control classes, such as HtmlTable, characterized by child elements and a closing tag.

Properties of the HtmlForm Class

The HtmlForm class provides programmatic access to the HTML <form> element on the server through the set of properties shown in Table 5-1. Note that the table includes only a few of the properties HtmlForm inherits from the root class Control.

Table 5-1: Form Properties

Property

Description

Attributes

Inherited from Control, gets a name/value collection with all the attributes declared on the tag.

ClientID

Inherited from Control, gets the value of UniqueID.

Controls

Inherited from Control, gets a collection object that represents the child controls of the form.

DefaultButton

String property, gets or sets the button control to display as the default button on the form. Not available in ASP.NET 1.x.

DefaultFocus

String property, gets or sets the button control to give input focus when the form is displayed. Not available in ASP.NET 1.x.

Disabled

Gets or sets a value indicating whether the form is disabled. Matches the disabled HTML attribute.

EncType

Gets or sets the encoding type. Matches the enctype HTML attribute.

ID

Inherited from Control, gets or sets the programmatic identifier of the form.

InnerHtml

Inherited from HtmlContainerControl, gets or sets the markup content found between the opening and closing tags of the form.

InnerText

Inherited from HtmlContainerControl, gets or sets the text between the opening and closing tags of the form.

Method

Gets or sets a value that indicates how a browser posts form data to the server. The default value is POST. Can be set to GET if needed.

Name

Gets the value of UniqueID.

Style

Gets a collection of all cascading style sheet (CSS) properties applied to the form.

SubmitDisabledControls

Indicates whether to force controls disabled on the client to submit their values, allowing them to preserve their values after the page posts back to the server. False by default. Not available in ASP.NET 1.x.

TagName

Returns "form".

Target

Gets or sets the name of the frame or window to render the HTML generated for the page.

UniqueID

Inherited from Control, gets the unique, fully qualified name of the form.

Visible

Gets or sets a value that indicates whether the form is rendered. If false, the form is not rendered to HTML.

The form must have a unique name. If the programmer doesn't assign the name, ASP.NET generates one by using a built-in algorithm. The programmer can set the form's identifier by using either the ID or Name property. If both are set, the ID attribute takes precedence. (Note, though, that any reliance on the Name attribute compromises the XHTML compliance of the page.)

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

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 form data is passed on the URL's query string. However, if you choose the GET method, make sure the limited size of a GET request (2 KB) does not affect the integrity of your application or raise security issues.

Methods of the HtmlForm Class

Table 5-2 lists the methods available on the HtmlForm class that you'll be using most often. All the methods listed in the table are inherited from the base System.Web.UI.Control class.

Table 5-2: Form Methods

Method

Description

ApplyStyleSheetSkin

Applies the style properties defined in the page style sheet. Not available in ASP.NET 1.x.

DataBind

Calls the DataBind method on all child controls.

FindControl

Retrieves and returns the control that matches the specified ID.

Focus

Set input focus to a control. Not available in ASP.NET 1.x.

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.

It is important to note that the FindControl method searches only among the form's direct children. Controls belonging to an inner naming container, or that are a child of a form's child control, are not found.

Multiple Forms

As mentioned, the SFI model is the default in ASP.NET and plays a key role in the automatic view state management mechanism we described in Chapter 3. Generally speaking, the ASP.NET enforcement of the SFI model does not significantly limit the programming power, and all things considered, doing without multiple forms is not a big sacrifice. Some pages, though, would have a more consistent and natural design if they could define multiple logical forms. In this context, a logical form is a logically related group of input controls. For example, think of a page that provides some information to users but also needs to supply an additional form such as a search or a login box.

You can incorporate search and login capabilities in ad hoc classes and call those classes from within the page the user is displayed. This might or might not be the right way to factorize your code, though. Especially if you're porting some old code to ASP.NET, you might find easier to insulate login or search code in a dedicated page. Well, to take advantage of form- based login, how do you post input data to this page?

Using HTML Forms

As mentioned, ASP.NET prevents you from having multiple <form> tags flagged with the runat attribute. However, nothing prevents you from having one server-side <form> tag and multiple client HTML <form> elements in the body of the same Web form. Here's an example:

<body>     <table><tr><td>         <form  runat="server">         <h2>Ordinary contents for an ASP.NET page</h2>         </form>     </td>     <td>         <form method="post" action="search.aspx">             <table><tr>                 <td>Keyword</td>                 <td><input type="text"  name="Keyword" /></td>             </tr><tr>                 <td><input type="submit"  value="Search" /></td>             </tr></table>         </form>     </td>     </tr></table> </body> 

The page contains two forms, one of which is a classic HTML form devoid of the runat attribute and, as such, is completely ignored by ASP.NET. The markup served to the browser simply contains two <form> elements, each pointing to a different action URL.

This code works just fine but has a major drawback: you can't use the ASP.NET programming model to retrieve posted data in the action page of the client form. When writing search.aspx, in fact, you can't rely on view state to retrieve posted values. To know what's been posted, you must resort to the old-fashioned but still effective ASP model, as shown in the following code sample:

public partial class Search : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {         // Use the Request object to retrieve posted data         string textToSearch = Request.Form["Keyword"].ToString();         ...         // Use standard ASP.NET programming model to populate the page UI         KeywordBeingUsed.Text = textToSearch;     } } 

You use the protocol-specific collections of the Request object to retrieve posted data Form if POST is used and QueryString in case of GET. In addition, you have to use the name attribute to identify input elements. Overall, this is perhaps not a recommended approach, but it definitely works. Figure 5-1 shows the page in action.

image from book
Figure 5-1: A server form control and a client HTML form working together.

When the user clicks the search button, the search.aspx page is invoked. It receives only the values posted through the HTML form, and it uses them to proceed.

Multiple <form> Tags on a Page

The preceding code works because we have only one server form control at a time. If multiple server forms are declared in the same Web form, an exception is thrown. A little-known fact is that a Web form can actually contain as many server-side forms as needed as long as only one at a time is visible. For example, a page with three <form runat=server> tags is allowed, but only one form can actually be rendered. Given the dynamics of page rendering, an exception is thrown if more than one HtmlForm control attempts to render. By playing with the Visible property of the HtmlForm class, you can change the active server form during the page life-time. This trick doesn't really solve the problem of having multiple active forms, but it can be helpful sometimes.

Let's consider the following ASP.NET page:

<body>     <form  runat="server" visible="true">         <h1>Welcome</h1>         <asp:textbox runat="server"  />         <asp:button  runat="server" text="Step #1"             OnClick="Button1_Click" />     </form>     <form  runat="server" visible="false">         <h1>Step #1</h1>         <asp:textbox runat="server"  />         <asp:button  runat="server" text="Previous step"             OnClick="Button2_Click" />         <asp:button  runat="server" text="Step #2"             OnClick="Button3_Click" />     </form>     <form  runat="server" visible="false">         <h1>Finalizing</h1>         <asp:button  runat="server" text="Finish"             OnClick="Button4_Click" />     </form> </body> 

As you can see, all <form> tags are marked as runat, but only the first one is visible. Mutually exclusive forms are great at implementing wizards in ASP.NET 1.x. By toggling a form's visibility in button event handlers, you can obtain wizard-like behavior, as shown in Figure 5-2.

public partial class MultipleForms : System.Web.UI.Page {     protected void Page_Load(object sender, EventArgs e)     {         Title = "Welcome";     }     protected void Button1_Click(object sender, EventArgs e)     {         Title = "Step 1";         step0.Visible = false;         step1.Visible = true;     }     protected void Button2_Click(object sender, EventArgs e)     {         step0.Visible = true;         step1.Visible = false;     }     protected void Button3_Click(object sender, EventArgs e)     {         Title = "Finalizing";         step1.Visible = false;         step2.Visible = true;     }     protected void Button4_Click(object sender, EventArgs e)     {         Title = "Done";         step2.Visible = false;         Response.Write("<h1>Successfully done.</h1>");     } } 

image from book
Figure 5-2: Mutually exclusive forms used to implement wizards in ASP.NET 1.x.

Multiple View and Wizards in ASP.NET 2.0

If you're writing an ASP.NET 2.0 application, you don't need to resort to the preceding trick. You find two new controls MultiView and Wizard ready for the job. The MultiView control employs logic nearly identical to that of multiple exclusive forms, except that it relies on panels rather than full forms.

The MultiView control allows you to define multiple and mutually exclusive HTML panels. The control provides an application programming interface (API) for you to toggle the visibility of the various panels and ensure that exactly one is active and visible at a time. The MultiView control doesn't provide a built-in user interface. The Wizard control is just that a MultiView control plus some wizard-like predefined user interface (UI) blocks. We'll cover the Wizard control in great detail in the next chapter.

Cross-Page Postings

ASP.NET 2.0 offers a built-in mechanism to override the normal processing cycle and prevent the page from posting back to itself. Postbacks occur in one of two ways either through a submit button or via script. The client browser usually takes on any post conducted through a button and automatically points to the page that the action attribute of the posting form indicates. More flexibility is possible when the post occurs via script. In ASP.NET 2.0, you can configure certain page controls in particular, those that implement the IButtonControl interface to post to a different target page. This is referred to as cross-page posting.

Posting Data to Another Page

Authoring a Web page that can post data to another page requires only a couple of steps. First, you choose the controls that can cause postback and set their PostBackUrl property. A page can include one or more button controls and, generally, any combination of button controls and submit buttons. Notice that in this context a button control is any server control that implements IButtonControl. (We fully covered the IButtonControl interface in Chapter 4.) The following code snippet shows how to proceed:

<form runat="server">     <asp:textbox runat="server"  />     <asp:button runat="server"              Text="Click"             PostBackUrl="target.aspx" /> </form> 

When the PostBackUrl property is set, the ASP.NET runtime binds the corresponding HTML element of the button control to a new JavaScript function. Instead of using our old acquaintance __doPostback, it uses the new WebForm_DoPostBackWithOptions function. The button renders the following markup:

<input type="submit" name="buttonPost"      value="Click"     onclick="javascript:WebForm_DoPostBackWithOptions(         new WebForm_PostBackOptions("buttonPost", "",             false, "", "target.aspx", false, false))" /> 

As a result, when the user clicks the button, the current form posts its content to the specified target page. What about the view state? When the page contains a control that does cross-page posting, a new hidden field is also created: __PREVIOUSPAGE. The field contains the view state information to be used to serve the request. This view state information is transparently used in lieu of the original view state of the page being posted to.

You use the PreviousPage property to reference the posting page and all of its controls. Here's the code behind a sample target page that retrieves the content of a text box defined in the form:

protected void Page_Load(object sender, EventArgs e) {     // Retrieves posted data     TextBox txt = (TextBox) PreviousPage.FindControl("TextBox1");     ... } 

By using the PreviousPage property on the Page class, you can access any input control defined on the posting page. Access to input controls is weakly typed and occurs indirectly through the services of the FindControl method. The problem here lies in the fact that the target page doesn't know anything about the type of the posting page. PreviousPage is declared as a property of type Page and, as such, it can't provide access to members specific to a derived page class.

Furthermore, note that FindControl looks up controls only in the current naming container. If the control you are looking for lives inside another control (say, a template), you must first get a reference to the container, and then search the container to find the control. To avoid using FindControl altogether, a different approach is required.

The @PreviousPageType Directive

Let's say it up front. To retrieve values on the posting page, FindControl is your only safe option if you don't know in advance which page will be invoking your target. However, when using cross-page posting in the context of an application, the chances are good that you know exactly who will be calling the page and how. In this case, you can take advantage of the @PreviousPageType directive to cause the target page's PreviousPage property to be typed to the source page class.

In the target page, you add the following directive:

<%@ PreviousPageType VirtualPath="crosspostpage.aspx" %> 

The directive can accept either of two attributes-VirtualPath or TypeName. The former points to the URL of the posting page; the latter indicates the type of the calling page. The directive just shown makes the PreviousPage property on the target page class be of the same type as the page at the given path (or the specified type). This fact alone, though, is not sufficient to let you access input controls directly. In Chapter 2 and Chapter 3, we pointed out that each page class contains protected members that represent child controls; unfortunately, you can't call a protected member of a class from an external class. (Only derived classes can access protected members of the parent class.)

To work around the issue, you must add public properties in the caller page that expose any information you want posted pages to access. For example, imagine that crosspostpage.aspx contains a TextBox named _textBox1. To make it accessible from within a target page, you add the following code to the code-behind class:

public TextBox TextBox1 {     get { return _textBox1; } } 

The new TextBox1 property on the page class wraps and exposes the internal text-box control. In light of this code, the target page can now execute the following code:

Response.Write(PreviousPage.TextBox1.Text); 

Detecting Cross-Page Postings

Being the potential target of a cross-page call doesn't automatically make a target page a different kind of page all of a sudden. There's always the possibility that the target page is invoked on its own for example, via hyperlinking. When this happens, the PreviousPage property returns null and other postback-related properties, such as IsPostBack, assume the usual values.

If you have such a dual page, you should insert some extra code to discern the page behavior. The following example shows a page that allows only cross-page access:

if (PreviousPage == null) {     Response.Write("Sorry, that's the wrong way to invoke me.");     Response.End();     return; } 

The IsCrossPagePostBack property on the Page class deserves a bit of attention. The property returns true if the current page has called another ASP.NET page. It goes without saying that IsCrossPagePostBack on the target page always returns false. Therefore, the following code is not equivalent to the previous code:

if (!IsCrossPagePostBack) {     ... } 

To know whether the current page is being called from another page, you have to test the value of IsCrossPagePostBack on the page object returned by PreviousPage.

// PreviousPage is null in case of a normal request if (!PreviousPage.IsCrossPagePostBack) {     ... } 

However, this code will inevitably throw an exception if the page is invoked in a normal way (that is, from the address bar or via hyperlinking). In the end, the simplest and most effective way to see whether a page is being invoked through cross-page postbacks is by checking PreviousPage against null.

Dealing with Validation

What if the original page contains validators? Imagine a page with a text box whose value is to be posted to another page. You don't want the post to occur if the text box is empty. To obtain the preferred result, you add a RequiredFieldValidator control and bind it to the text box:

<asp:TextBox  runat="server"></asp:TextBox> <asp:RequiredFieldValidator  runat="server"      ControlToValidate="TextBox1" Text="*" /> <asp:Button  runat="server" Text="Apply request..."      OnClick="Button1_Click" PostBackUrl="targetpage.aspx" /> 

As expected, when you click the button the page won't post if the text box is empty; a red asterisk (plus an optional message) is displayed to mark the error. This is because by default button controls validate the input controls before proceeding with the post. Is that all, or is there more to dig out?

In most cases, the RequiredFieldValidator benefits the client-side capabilities of the browser. This means that, in the case of empty text boxes, the button doesn't even attempt to make the post. Let's work with a CustomValidator control, which instead requires that some server-side code be run to check the condition. Can you imagine the scenario? You're on, say, post.aspx and want to reach target.aspx; to make sure you post only under valid conditions, though, you first need a trip to post.aspx to perform some validation. Add this control, write the server validation handler, and put a breakpoint in its code:

<asp:CustomValidator  runat="server" Text="*"      ControlToValidate="TextBox1" OnServerValidate="ServerValidate" /> 

Debugging this sample page reveals that posting to another page is a two-step operation. First, a classic postback is made to run any server-side code registered with the original page (for example, server-side validation code or code associated with the click of the button). Next, the cross-page call is made to reach the desired page:

void ServerValidate(object source, ServerValidateEventArgs args) {     args.IsValid = false;     if (String.Equals(args.Value, "Dino"))         args.IsValid = true; } 

The preceding code sets the page's IsValid property to false if the text box contains anything other than "Dino." However, this fact alone doesn't prevent the transition to the target page. In other words, you could have invalid input data posted to the target page.

Fortunately, this issue has an easy workaround, as shown in the following code:

if (!PreviousPage.IsValid) {     Response.Write("Sorry, the original page contains invalid input.");     Response.End();     return; } 

In the target page, you test the IsValid property on the PreviousPage property and terminate the request in the case of a negative answer.

Redirecting Users to Another Page

In addition to the PostBackUrl property of button controls, ASP.NET provides another mechanism for transferring control and values from one page to another the Server.Transfer method.

The URL of the new page is not reflected by the browser's address bar because the transfer takes place entirely on the server. The following code shows how to use the method to direct a user to another page:

protected void Button1_Click(object sender, EventArgs e) {     Server.Transfer("targetpage.aspx"); } 

Note that all the code that might be following the call to Transfer in the page is never executed. In the end, Transfer is just a page-redirect method. However, it is particularly efficient for two reasons. First, no roundtrip to the client is requested as is the case, for example, with Response.Redirect. Second, the same HttpApplication that was serving the caller request is reused, thus limiting the impact on the ASP.NET infrastructure.

In ASP.NET 1.x, the spawned page can access the page object representing its caller by using the Handler property of the HTTP context, as follows:

Page caller = (Page) Context.Handler; 

Because Handler returns a valid instance of the referrer page object, the spawned page can access all of its properties and methods. It cannot directly access the controls because of the protection level, though. This programming model also works in ASP.NET 2.0.

In ASP.NET 2.0, things are simplified; using Handler is no longer necessary. You can use the same programming model of cross-page postings and rely on a non-null PreviousPage property and the @PreviousPageType directive for strongly typed access to input fields. How can a page detect whether it's being called through a server transfer or through a cross-page postback? In both cases, PreviousPage is not null but the IsCrossPagePostBack on the PreviousPage object is true for a cross-page posting and false in the case of a server transfer. (This and all other techniques related to form posting are demonstrated in great detail in the sample companion code.)

Important 

Passing values from one page to another is a task that can be accomplished in a variety of ways using cross-page posting, server transfer, HTML forms, or query strings. Which one is the most effective? In ASP.NET 2.0, cross-page posting and server transfer offer a familiar programming model but potentially move a significant chunk of data through the __PREVIOUSPAGE field. Whether this information is really needed depends on the characteristics of the target page. In many cases, the target page just needs to receive a few parameters to start working. If this is the case, HTML client forms might be more effective in terms of data being moved. HTML forms, though, require an ASP-like programming model.

 


Programming Microsoft ASP. Net 2.0 Core Reference
Programming Microsoft ASP.NET 2.0 Core Reference
ISBN: 0735621764
EAN: 2147483647
Year: 2004
Pages: 112
Authors: Dino Esposito
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net