Layers of ASP.NET Pages

Embeddable Web Forms

User controls represent another level of page reusability and turn out to be more employable because of their smaller granularity. With user controls, the reusability applies to input forms rather than to whole pages. User controls are also referred to as pagelets in some existing documentation. I ll be using both terms interchangeably.

A user control can be seen as an embeddable Web form. You define a user control in much the same way you create a Web Forms page. When you re finished with code and layout, you give the resulting file an ASCX extension. You can use the control with any ASP.NET page. Pages see the control as a programmable component and work with it in the same way they work with any other system Web control. Are you familiar with Dynamic HTML (DHTML) scriptlets? User controls are just their ASP.NET counterparts.

Writing User Controls

Like a Web Forms page, a user control has layout information and code blocks. The layout represents the user interface of the component say, the controls making the form. The internal code ties together the various constituent controls and implements their business logic. User controls can expose methods, properties, and events. The following code demonstrates a simple yet effective pagelet:

<%@ Control ClassName="LabelTextBox" Language="C#" %> <script runat="server"> public Label TheLabel { get {return lblTheLabel;} } public TextBox TheTextBox { get {return txtTheTextBox;} } </script> <asp:label runat="server" font-bold="true" style="font-family:verdana" /> <asp:textbox runat="server" backcolor="ivory" style="font-family:verdana" />

The user control defined by the preceding code is composed of a label and an accompanying text box control. Each control implements some style information and has a unique ID. In the code section, you find two public properties: one wrapping the label control and one wrapping the text box. These constituent controls are exposed as read-only, but callers can set any of their properties.

note

To expose any of the constituent controls as programmable objects, you must use a distinct public property. All controls in the pagelet s layout are inaccessible to external callers because of their class protection level. To work around this, you define a public property (preferably as read-only) for each control you want to expose. Make sure that properties have names different from the IDs of the controls they mirror. Otherwise you will get a compile-time error because of member names duplication.

Let s see how to use the pagelet in an ASP.NET page. To start off, you assign the new user control a tag and a namespace prefix within the hosting page. The @ Register directive lets you do this. The tag name and the prefix will be used to insert an instance of the user control in the page. Figure 5-5 shows the results.

<%@ Register TagPrefix="expo" TagName="LabelBox" src="/books/2/368/1/html/2/labelbox.ascx" %> <html> <title>Testing the LabelBox Pagelet</title> <body> <form runat="server"> <expo:labelbox runat="server" /> </form> </body> </html>

The LabelBox user control is made of a label and a text box control, both of which are programmable. You can access them as follows:

<script language="C#" runat="server"> public void Page_Load(Object sender, EventArgs e) { thePagelet.TheLabel.Text = "This is the label"; thePagelet.TheTextBox.Text = "This is the text"; } </script>

Figure 5-5

A page containing a simple user control.

Codewise, the <expo:labelbox> control works like a Web control but, in terms of structure and layout, it is more like an embedded and programmable form.

Programming User Controls Declaratively

When the public properties of a user control map to simple data types (for example, to strings and dates but not to controls or user-defined types), you can easily set the properties through the attributes of the tag that represents the control:

<expo:labelbox runat="server" Property="simplevalue" />

Let s enhance the user control like this:

<%@ Control Language="C#" %> <script runat="server"> public String LabelText=""; public String EditText=""; public Label TheLabel { get {return lblTheLabel;} } public TextBox TheTextBox { get {return txtTheTextBox;} } public void Page_Init(Object sender, EventArgs e) { txtTheTextBox.Text = EditText; lblTheLabel.Text = LabelText; }</script> <asp:label runat="server" font-bold="true" style="font-family:verdana" /> <asp:textbox runat="server" backcolor="ivory" style="font-family:verdana" />

Two more public properties are defined, LabelText and EditText. They were created to accept text for the label and the text box. The values read from these properties are used within the control s Page_Init event to initialize the Text property of both constituent controls. You can now set the desired text in the calling ASP.NET page through declarative attributes.

<expo:labelbox runat="server" LabelText="Label" EditText="Text" />

What happens when the calling page also handles the Page_Load event? Any change entered at that time that affects the user control overrides the current state:

public void Page_Load(Object sender, EventArgs e) { thePagelet.TheLabel.Text = "This is the label"; thePagelet.TheTextBox.Text = "This is the text"; }

So with the user control we just created, the last word on the state of the constituent controls is up to the page s Page_Load event handler.

Coding ASP-Style Code Blocks

Unless you need server-side manipulation of the control, using HTML tags that are not marked with the runat attribute results in faster code because the HTTP run time does not have to process the tag. Typically you can apply this simple optimization rule to label controls, because, in the case where the label text doesn t change, using a label control simply does not make sense. Let s see how this consideration applies to pagelets.

When you don t plan to use any Label control property other than Text, you can replace the <asp:label> control with a plain old <span> tag to greater effect. In this case, how would you display the current text? You would use ASP-style code blocks. Replace the Label control in the preceding pagelet with the following:

<span style="font-family:verdana;font-weight:bold"> <%=LabelText%></span>

Loading Pagelets Programmatically

Just as you can programmatically create an instance of an ASP.NET server control, you can do the same with a pagelet. The pagelet s method that allows you to do this is LoadControl. A pagelet is dynamically wrapped in a class whose name is determined by the ClassName attribute of the @ Control directive. User controls that enable the programmatic creation of their instances include the following directive:

<%@ Control ClassName="MyControlClass" Language="C#" %>

The hosting page must reference the control through the @ Reference directive. It indicates via a declaration that a user control should be dynamically compiled and linked with the page.

<%@ Reference Control="Message.ascx" %>

The following code shows how to create a dynamic instance of one pagelet. The full source code for the PageletDynamicBinding.aspx application is available on the companion CD.

Message statusbar; public void Page_Load(Object sender, EventArgs e) { Control ctl = Page.LoadControl("Message.ascx"); statusbar = (Message) ctl; Page.Controls.Add(statusbar); } public void OnConnect(Object sender, EventArgs e) { statusbar.Color = "red"; statusbar.Text = "<b>User Connected: </b>" + lname.Text + ", " + fname.Text; }

The @ Reference directive allows you to use the user control s class name in the body of the page. If the reference is missing, you cannot employ strongly typed code. In other words, you can manipulate the pagelet only through the generic programming interface of the Control class. You can display the control in the page but you cannot code with it.

Preparing to Share User Controls

When you want to share your ASP.NET page functionality with one or more applications, you need to make a few moves to transform the page into a reusable and programmable user control.

First make sure that the pagelet has none of the following tags: <html>, <body>, or <form>. Because the layout of the pagelet will be merged with the layout of the hosting page, you want to avoid harmful and unnecessary nesting, which these tags can cause. Second, after all unnecessary HTML tags are removed, rename the file with an ASCX extension. This renaming is key to enabling a special treatment of the file. Finally, if the page you re converting into a pagelet contains the @ Page directive, change it to the @ Control directive.

The @ Control and @ Page directives are nearly identical. The only significant difference between them is that @ Control does not support the Trace attribute. If you need tracing, enable it at the page level and it will automatically be extended to any child control. The @ Control directive allows you to use different languages for the pagelet and the hosting page. For example, you can use Visual Basic in the pagelet and C# in the calling page. Using the ClassName attribute for controls is not strictly necessary but is highly recommended.

Creating User Controls in Code-Behind Files

You can use code-behind files to create user controls, not just ASP.NET pages. The technique for creating both is the same except for a few minor details. To bind the external file, you use the @ Control directive instead of @ Page.

<%@ Control Language="C#" Inherits="BWSLib.MyControl" src="/books/2/368/1/html/2/MyControl.cs" %>

As you can probably guess, the base class of code-behind user controls cannot be Page, as it is for ASP.NET pages. User controls will inherit from UserControl. Furthermore, when you declare the ID attribute for each server control in the layout, be sure ID matches the name of the instance that you create for it in the code-behind file. For example, suppose you have the following control in the ASCX file:

<asp:textbox runat="server"/>

The code-behind file must have a declaration such as the following. The public qualifier is arbitrary, so you can change it.

public TextBox Name;

Creating a New DateBox Control

ASP.NET supplies a calendar control that I used in Chapter 4 to edit a date field. The main drawback of this control is that it requires a round-trip each time the user clicks it to select a day or a month. The Calendar control is certainly useful, especially to display a read-only date, but to enter or update a date you might want to employ a simpler and equally effective interface.

Writing a DateBox user control that lets the user type in a date is not particularly difficult. The layout of the DateBox control comprises three text boxes representing the month, day, and year, and they are separated with literals representing the date separator.

<asp:textbox runat="server" columns="2" maxlength="2" style="font-family:verdana;border:0px; " /> <%=Separator%> <asp:textbox runat="server" columns="2" maxlength="2" style="font-family:verdana;border:0px;" /> <%=Separator%> <asp:textbox runat="server" columns="4" maxlength="4" style="font-family:verdana;border:0px;" />

The text boxes use standard styles and expose background and foreground colors as programmable properties. Table 5-1 shows the full list of DateBox control s properties.

Table 5-1 Properties of the DateBox Control

Properties

Description

BackColor

Gets or sets the background color for the constituent text boxes.

ForeColor

Gets or sets the foreground color for the constituent text boxes.

SelectedDate

Gets or sets a string that represents a valid date.

Separator

Gets or sets the character used as the date separator.

The DateBox user control also fires the DateChanged event whenever the user changes the currently displayed date.

Property Accessors

Some of the properties in Table 5-1 are exposed by using public read-write members, so users can read and write them directly without the mediation of get and set accessors.

public String Separator = "/"; public String BackColor = "white"; public String ForeColor = "black";

Property accessors are small pieces of code that a control executes internally when a given property is read or set. The get accessor governs the retrieval of the current value of the property. The set accessor manages the way in which a given value is assigned to the property. When you omit the get accessor, the property becomes write-only. When you omit the set accessor, the property is read-only. As shown in the following code, the DateBox control uses accessors only for the SelectedDate property:

public String SelectedDate { get { return m_date.ToString("MM" + Separator + "dd" + Separator + "yyyy");} set { try {m_date = Convert.ToDateTime(value);} catch {} RefreshDate(); // Raise DateChanged event here } }

When the SelectedDate property is read, the currently set date is formatted into a mmddyyyy string with the current separator. When a user attempts to assign a value to SelectedDate, the input string is converted to a valid date object. Next the string gets stored internally in the m_date private member, which contains information about the current date as a DateTime object. The set accessor is responsible for refreshing the control s user interface to reflect the new date. Figure 5-6 shows the DateBox control in action.

Figure 5-6

The DateBox control in action.

Some Special Effects

Each text box in Figure 5-6 is borderless. I created this effect by using an explicit CSS style, but you can use the BorderSize property of the ASP.NET TextBox control to achieve the same look. Consider that not all browsers provide good support for CSS styles, so unless the user is using Internet Explorer 5 or a later version, he can expect the UI to appear different. The following figure shows how Figure 5-6 might look in other browsers.

HTML provides tooltips for several tags through the title attribute. Be aware that not all browsers, especially old browsers, support the title attribute. For example, IE 2 and Netscape browsers, prior to version 6, do not support it.

The width of the text boxes is expressed in characters. You set it using the Columns property on the TextBox control, which evaluates to the HTML Size attribute. If the browser-specific implementation of the text box allows you to indicate the font in use, the actual size of the textbox is calculated using the maximum width of the font s characters. So it happens that, say, ww fits perfectly in the text box width while ii leaves a lot of trailing blank space. To avoid that, use a fixed-size font like the Courier New which is also the only font accepted by text boxes in some downlevel browsers.

Notice in this figure that you can give a link button a special symbol such as the Down Arrow (C) character instead of normal text by using text strings rendered with the Webdings font family. The C character corresponds to a text string of "6". The A symbol corresponds to "q".

The A link button causes the control to update the internal state when the date is manually edited. The update properly formats the elements of the date (that is, it appends a leading 0, if needed) and refreshes the internal members, including the child calendar control.

A Child Calendar

When the user clicks the C button, a child Calendar control appears, letting the user select the new date. The Calendar control is a native part of the user control s layout but is initially hidden.

<asp:calendar runat="server" Visible="false" BackColor="white" ForeColor="black" Font-Name="verdana" Font-Size="9px" TitleStyle-BackColor="#33ddff" TitleStyle-ForeColor="black" TitleStyle-Font-Bold="True" SelectorStyle-BackColor="#99ccff" SelectedDayStyle-BackColor="Navy" SelectedDayStyle-Font-Bold="True" DayHeaderStyle-Font-Bold="True" OnSelectionChanged="SelectionChanged" />

The Calendar control exposes a SelectionChanged event that fires whenever the date changes. By hooking this event, the control updates the text boxes when the user selects a new date through the calendar. The calendar s visibility attribute is toggled on and off by clicking the Down Arrow (C) button. The Calendar and TextBox controls are placed on two distinct rows of an all-encompassing table, as shown in Figure 5-7.

Figure 5-7

The DateBox user control with the calendar open. The top picture shows what you see through IE 6.0. The bottom picture displays the output of an old version of Netscape Navigator.

note

The DateBox control does not validate any data entered by the user, which can result in run-time errors when values do not evaluate to dates. You can implement active error checking by using try/catch blocks. You can implement passive error checking that is, warning the user about the error by using validator controls.

Firing Events

In addition to defining properties, user controls can define methods and fire events. To define a method, you simply define a public procedure. Exposing an event is a bit trickier.

When exposing an event, the first question you should ask yourself is whether you plan to return event-specific data. The passing or not passing of data influences the signature of the event handler that both the control and its clients will be using to process the event. If you don t pass data, you can use the standard event handler EventHandler. In this case, a public event named DateChanged is declared as follows:

public event EventHandler DateChanged;

You also need a helper function that calls the user-defined event handlers from within the control. This normally takes the form of the following:

protected virtual void OnDateChanged(EventArgs e) { if (DateChanged != null) DateChanged(this, e); }

During the control s activity, when the situation described in the event occurs, the code invokes the user-defined handler as follows:

OnDateChanged(EventArgs.Empty);

In .NET, events are driven by delegate types a kind of type-safe function pointer. Delegates describe the signature of the functions you write to handle a certain event. The prototype shown here identifies the standard event handler.

public delegate void EventHandler(Object sender, EventArgs e);

Using Custom Event Arguments

An event such as DateChanged isn t very useful without passing made-to-measure data for example, the old date, the new date, and a Boolean flag denoting whether the data change occurred via typing on the keyboard or clicking the calendar buttons. When custom data is involved, you define an ad-hoc data structure that simply extends the base class EventArgs. Take a look at this example, which defines the DateChangedEventArgs class:

public sealed class DateChangedEventArgs : EventArgs { public bool FromCalendar; public DateTime OldDate; public DateTime NewDate; }

A new delegate type using this data structure is also needed. Let s call it DateChangedEventHandler.

public delegate void DateChangedEventHandler( Object sender, DateChangedEventArgs e);

Finally, the event exposed by the control must be declared as follows:

public event DateChangedEventHandler DateChanged;

The event is raised in two circumstances, namely in response to the calendar s SelectionChanged event and when the text boxes are refreshed to reflect a new date. The following code shows how I handle changes in the calendar, bubbling a slightly modified event up to the calling page:

private void SelectionChanged(Object sender, EventArgs e) { // Store the new date DateTime dtNew = myCalendar.SelectedDate; // Create and fill the argument's data structure DateChangedEventArgs dce = new DateChangedEventArgs(); dce.FromCalendar = true; dce.OldDate = m_date; dce.NewDate = dtNew; // Refresh the control's user interface m_date = dtNew; RefreshDate(); // Fire the event OnDateChanged(dce); }

Notice that the SelectionChanged event of the Calendar class does not use a custom structure for arguments. As you can see in the preceding code, SelectionChanged still relies on EventArgs. So when do you really need a custom structure? And what data should you pass using it?

A client page handling events can still access the control that raised the event and instantly read the value of public properties. So you should avoid passing as event arguments any information that is already available through public properties and methods. The typical data used as event arguments is any information that is potentially useful but not accessible otherwise, for example, information that is stored through private members, or volatile information that the control does not cache and expose in any way.

Applying these rules to the DateChanged event, you notice the caller has no way to know about how the change was brought about. So the FromCalendar field is really an added value. The same can be said for OldDate but not necessarily for NewDate, because the new date can be accessed at least as a string by using the DateBox control s SelectedDate property.

note

If your class raises multiple events, you can optimize the way they are stored by using event properties instead of event fields, as shown so far in this chapter. For event fields, the compiler generates one field per instance of an event delegate, which might not be acceptable for a large number of infrequently used events. Event properties consist of event declarations accompanied by event accessors.

public event DateChangedHandler DateChanged { add { Events.AddHandler(eventDateChanged, value); } remove { Events.RemoveHandler(eventDateChanged, value); } }

When multiple clients register to handle the same event, all their names will be stored in the eventDateChanged structure:

private static readonly object eventDateChanged = new object();

For more information and samples about event handling, check out the MSDN documentation.

Handling Events from Pages

The following code shows how to hook up the DateChanged event in a client ASP.NET page:

<expo:datebox runat="server" separator="-" selecteddate="9/14/2001" ondatechanged="Date_Changed" />

The event handler below writes a line describing how the date changed.

public void Date_Changed(Object sender, ASP.DateBox.DateChangedEventArgs e){ String msg; msg = String.Format("<br>Date has been changed from <b>{0:d}</b>" + "to <b>{1:d}</b> through <b>{2}</b>", e.OldDate, e.NewDate, (e.FromCalendar ?"the calendar." :"manual editing.")); info.Text += msg; }

You could omit the ASP namespace by importing it at the page level:

<%@ Import Namespace="ASP" %>

The actual signature of the page event handler deserves a few comments. In this sample application, I defined the DateChangedEventArgs class in the body of the DateBox user control the file DateBox.ascx which makes DateChangedEventArgs available only as a member of the DateBox class. If you try to use the following, perhaps more natural, signature, you get a compiling error:

public void Date_Changed(Object sender, DateChangedEventArgs e)

An exception is thrown because DateChangedEventArgs is not defined in or linked to the ASP.NET page. The error message hints that you missed an assembly. At this point you can do one of two things. You can place the class definition in a distinct module, compile it to an assembly, and reference the assembly in both the page and the user control. Alternatively, you can leave the class definition in the user control s body but find a way to let the page know about the class.

The ClassName attribute set in the @ Control directive assigns the name of DateBox to the user control. All user controls that is, all the ASP.NET elements referred to by using ASCX files belong to a namespace named ASP. The full name of the DateBox class, then, is ASP.DateBox. If the DateChangedEventArgs class is defined within ASP.DateBox, the only fully qualified name for DateChangedEventArgs is ASP.DateBox.DateChangedEventArgs. You cannot shorten this name by importing ASP.DateBox as a namespace. The reason is quite simple: ASP.DateBox is a class, not a namespace. Instead, you can import ASP as a namespace and resort to the slightly shorter class name of DateBox.DateChangedEventArgs. Figure 5-8 shows the final effect of the event handler in a sample page. The full source code for the DateBox.ascx and TestDateBox.aspx applications is available on the companion CD.

note

If you omit the ClassName attribute in the @ Control directive, the class name of the user control takes a standard form. If the filename is DateBox.ascx, the class name is DateBox_ascx. Consequently, the full name of the event class is ASP.DateBox_ascx.DateChanged EventArgs.

Figure 5-8

Intercepting the DateChanged event fired by the DateBox user control.



Building Web Solutions with ASP. NET and ADO. NET
Building Web Solutions with ASP.Net and ADO.NET
ISBN: 0735615780
EAN: 2147483647
Year: 2002
Pages: 75
Authors: Dino Esposito

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