Creating the Presentation with User Controls
Most likely the best piece of advice you can get is to create your ASP.NET user interfaces with user controls. Referred to as control compositing , you can design a user control precisely as you would a Web page. The only difference is that user controls can be reorganized and reused on one or many Web pages. An excellent example of this technique is the very dynamic IBuySpy portal application available for download from Microsoft at http://www.asp.net. The portal refers to its user controls as portal modules . Each module shares a common base class, and the portal can be updated after the portal application has been deployed with an administrative login.
I have used a similar technique with great success on a couple of projects and am employing this device as a reimplementation of http://www.softconcepts.com in ASP.NET after eight years as an HTML-based Web site. Some of the examples I will show you next demonstrate the compositing technique recommended by Microsoft and promoted as a good object-oriented approach in general.
Creating a Basic User Control Layout
Earlier I demonstrated how you can use an HTML table to manage the layout of a Web page. This technique is not entirely new; it's more in the category of "an oldie but a goody." I first used the technique when I realized in the mid-1990s that not every Web browser supported frames, but I wanted the compartmentalization of frames . More recently we used HTML tables on a project because some developers weren't happy with the control they had over layout, finding screen creation a bit tedious . Because user controls are essentially designable like Web pages we can employ the same HTML table technique to manage layout of user controls.
The header control in Figure 15.7 is comprised of several user controls. I like to think of layering controls and compositing like hammered steel . Individually each control is easy to build; together they create a strong and resilient end result. The links across the top form the first part of the header control. Since this is a complete concept by itself, I created the links as a separate user control, a table with one data cell (Listing 15.7).
Listing 15.7 Defining the Table for the Links User Control
[View full width]
<%@ Control Language="c#" AutoEventWireup="false" Codebehind="Links.ascx.cs" Inherits="softconcepts.Controls.Links" TargetSchema="http://schemas.microsoft.com/ intellisense/ie5" enableViewState="False"%> <TABLE> <TR> <TD> </TD> </TR> </TABLE>
The first statement is the @ Control directive. Importantly, this directive associates the code-behind module with the visual part of the control. The table can be easily created by dropping an HTML table onto a new user control. The end result looks like a box on a user control.
The control directive information is coded for us if we use the designer to add the HTML table. We only need to modify this data if we code it manually or want to change something.
Creating Navigation Links
The next step is creating the actual navigation links. I could hard-code each link as individual cells with hyperlinks , but that would not be especially dynamic, and there is an easier way. By using an ASP.NET DataList control for the navigation links, I can reuse the Links control and dynamically change the content by binding a data source to the DataList .
To create links similar to those shown in Figure 15.7, follow these steps.
Listing 15.8 Creating a Dynamic Runtime View for the DataList Links
1: <%@ Control Language="vb" AutoEventWireup="false" 2: Codebehind="Links.ascx.vb" Inherits="softconcepts.Controls.Links" 3: TargetSchema=http://schemas.microsoft.com/intellisense/ie5 4: enableViewState="False"%> 5: <TABLE id="Table1" cellSpacing="0" cellPadding="0" 6: width="100%" border="0"> 7: <TR> 8: <TD class="HeaderLinks" noWrap> 9: <asp:datalist id="DataList1" 10: RepeatDirection="Horizontal" runat="server"> 11: <ItemStyle Wrap="False"></ItemStyle> 12: <ItemTemplate> 13: 14: <asp:HyperLink id=HyperLink1 15: runat="server" 16: ToolTip='<%# DataBinder.Eval(Container.DataItem, 17: "Description") %>' 18: NavigateUrl='<%# DataBinder.Eval(Container.DataItem, 19: "Url") + "?LinkID=" + 20: DataBinder.Eval(Container.DataItem, "ID") %>' 21: CssClass="HeaderLinks"> 22: <%# DataBinder.Eval(Container.DataItem, "Name") %> 23: </asp:HyperLink> 24: <span class="HeaderLinks"></span> 25: </ItemTemplate> 26: </asp:datalist> 27: </TD> 28: </TR> 29: </TABLE>
The new information is comprised of the attributes on the <TABLE> and cell <TD> tags and the code between the <TD></TD> tags. (You can see this by comparing Listing 15.8 to Listing 15.7.)
I used the style HeaderLinks wherever styles were used. This style comes from the softconcepts.css style sheet we discussed earlier in the chapter. Most of the remaining modifications are contained in the <ItemTemplate> tag. This tag was added when I followed the numbered steps above.
Block script ( <%# %> ) blocks were used to bind the ToolTip property, the NavigateUrl property, and the display text values for the HyperLink control. For example, ToolTip is dynamically derived in lines 16 and 17 by using a script block and the DataBinder class's shared method Eval . The DataBinder class plays an intermediary role between a control that can be bound to data and the data itself.
ToolTip='<%# DataBinder.Eval(Container.DataItem, "Description") %>'
Understood in the simplest sense, the preceding excerpt means that HyperLink.ToolTip equals some derived value. DataBinder.Eval needs an object. The object is generically represented by the containing object's DataItem property. The fragment is understood to mean that there will be a hyperlink for each object in the data source, and that object will have a property named Description . Thus you should anticipate that the DataList control is bound to a DataSource object containing objects that have a Description property. The same approach is used for the NavigateUrl property, displayed text, and LinkID . To satisfy the DataBinder.Eval statements used, we would need a data source that contains objects with Description , ID , Name , and URL properties.
Listing 15.9 provides an example of implementing the Links class as a strongly typed collection.
Listing 15.9 Implementing the Links Class as a Strongly Typed Collection
Public Class Links Inherits System.Collections.ReadOnlyCollectionBase Public Sub New() End Sub Public Shared Function HeaderLinks() As Links Dim Links As Links = New Links() Links.Add(New Link("Softconcepts home page", _ "Home", 1, "default.aspx")) Links.Add(New Link("About softconcepts", _ "About", "2", "About.aspx")) Return Links End Function Default Public ReadOnly Property Item( _ ByVal Index As Long) As Link Get Return CType(Innerlist(Index), Link) End Get End Property Public Function Add(ByVal Value As Link) As Long Return InnerList.Add(Value) End Function End Class Public Class Link Private FDescription As String Private FID As String Private FName As String Private FUrl As String Public Sub New(ByVal Description As String, _ ByVal Name As String, ByVal ID As String, ByVal Url As String) Me.FDescription = Description Me.FName = Name Me.FID = ID Me.FUrl = Url End Sub Public ReadOnly Property Description() As String Get Return FDescription End Get End Property Public ReadOnly Property ID() As String Get Return FID End Get End Property Public ReadOnly Property Name() As String Get Return FName End Get End Property Public ReadOnly Property Url() As String Get Return FUrl End Get End Property End Class
Listing 15.9 implements a strongly typed collection named Links . Links contains instances of the Link class, also defined in Listing 15.9. (For more information on strongly typed collections, refer to Chapter 14, which demonstrates and describes typed collections as return types for Web Services.) I could have implemented the links as a table in a database. Either way the code that consumes the link information is the same.
As you can see in the listingin the shared function HeaderLinks I implemented a factory method that creates an instance of the header links. There are only two links in the shared method. We just need to define the complete complement of Link objects and bind the return result to the DataList control. The DataBinder code in Listing 15.8 does the rest. The easiest way to bind a Link object to the data list is demonstrated in the Page_Load event handler for the Links.ascx user control (Listing 15.10). You can bind any object that implements IEnumerable , as does System.Collections.ReadOnlyCollectionBase (from which the Links class inherits).
Listing 15.10 Binding the Links Object
Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load DataList1.DataSource = Links.HeaderLinks DataList1.DataBind() End Sub
Adding a User Control to a Page
Adding the Links.ascx user control to a page is the easiest part of this whole process. All you have to do is drag the control from the Solution Explorer onto the page or another user control. In keeping with our concept of compositing, I actually implemented a Links user control and a Header user control. I wanted to reuse the Links control in another context, too: hyperlinks at the bottom of the page. Figure 15.12 shows the Header control as a work in-progress with the Links.ascx user control in place.
Figure 15.12. The Links.ascx user control as part of the composite Header user control.
Loading User Controls Dynamically
By creating user controls you have a lot of flexibility in how you orchestrate your user interface. You also have a lot of flexibility in when you orchestrate your user interface. A good friend of mine, Geoff Caylor, demonstrated this by using the CodeDOM and dynamically loaded controls.
The basic idea was to use the CodeDOM to generate some basic template classes that knew how to work with this particular dynamic page generator. Then, based on some state information in the application, these controls were loaded into a page at runtime. (A similar technique is used to make the IBuySpy portal application dynamic and flexible even after it has been deployed.) All we need to make this work is the LoadControl method, a control or user control, and somewhere to load the control into. The upcoming listing demonstrates how to load the Links.ascx user control into the Header.ascx user control at runtime instead of design time. But first we must do some preparation work.
Suppose we want to load the Links.ascx control into the top data cell of the HTML table at runtime. We need an object to refer to. By default, HTML controls don't have a reference in the code-behind file. However, if you make them server controls, you can refer to them in the code-behind file. This is actually very easy to dofollow these steps.
The last step adds a protected field named LinksPane in the code-behind file, and you will be able to refer to the HTML table cell with code at runtime. Listing 15.11 shows the code for dynamically loading the Links.ascx control into the Header control's LinksPane HTML table cell.
Listing 15.11 Loading Links.ascx at Runtime
1: Public MustInherit Class Header 2: Inherits System.Web.UI.UserControl 3: Protected WithEvents LinksPane As _ 4: System.Web.UI.HtmlControls.HtmlTableCell 5: 6: [ Web Form Designer generated code ] 7: 8: Private Sub Page_Load(ByVal sender As System.Object, _ 9: ByVal e As System.EventArgs) Handles MyBase.Load 10: 11: LinksPane.Controls.Add(LoadControl("Links.ascx")) 12: 13: End Sub 14: 15: End Class
The System.Web.UI.HtmlControls.HtmlTableCell control can be referred to in the code-behind file if we indicate that it is a server control. Line 11 loads the Links.ascx control and adds it to the LinksPane.Controls collection. The result is that the control appears in the header as if we had added it at design time. The benefit is that we could load something else if we desired, affording us a tremendous amount of flexibility.
You could borrow the concept of loading controls dynamically and generate an entire Web page at runtime. The pragmatics of doing this in a production application need some examination, but Listing 15.12 shows some code that creates a test page for the Links class defined in Listing 15.9. The test page can be used to ensure that the Links and Link classes are working correctly. Figure 15.14 shows the results of running the code in Listing 15.12.
Listing 15.12 Creating a Test Page for the Links Class
1: Public Class TestPage 2: Inherits System.Web.UI.Page 3: 4: [ Web Form Designer generated code ] 5: 6: Private Sub Page_Load(ByVal sender As System.Object, _ 7: ByVal e As System.EventArgs) Handles MyBase.Load 8: 9: Dim DataGrid As DataGrid = New DataGrid() 10: Controls.Add(DataGrid) 11: DataGrid.Attributes.Add("width", "100%") 12: DataGrid.DataSource = Links.HeaderLinks 13: DataGrid.DataBind() 14: 15: 16: End Sub 17: 18: End Class
Figure 15.14. A dynamically created and bound DataGrid control.
The DataGrid control is created in line 9. There is nothing to prevent us from using the same technique to create individual controls like text boxes and labels or using LoadControl if we want to use user controls. The DataGrid control is added to the form's Controls collection. An attribute is set dynamically, to show an example of this technique in context. Finally, the DataGrid control's DataSource is provided, and the DataBind method is called. If we had added the DataGrid control at design time, we would have needed only the code in lines 12 and 13.
I like to use generated forms when I want to quickly test new objects. A generated form plays the role of scaffold , permitting me to focus on the object representing the business rulesin this case, Links without spending a lot of time on a user interface for testing.
Converting a Web Page into a User Control
In the simplest sense a user control is a Web page. You design user controls like Web pages. You load user controls by referring to the filename, and they contain the same kinds of things as Web pages. User controls contain other controls and are associated with code-behind files. As a matter of fact, if you create or have a page that you want to convert to a control, you can do so by stripping just a few of the tags out of the HTML and making modest modifications to the code-behind file. As an example, let's make a copy of the TestPage.aspx page and convert it to a user control. Follow the steps below. (The related listings are interspersed among the steps.)
Note that the only real revisions to the class were to the Class header and the Inherits clause. A UserControl class is defined as abstract; this is the meaning of the MustInherit modifier. The class now inherits from System.Web.UI.UserControl instead of System.Web.UI.Page . That's all you need to do.
If you want to convert a user control to a Web page, you can perform the same steps in reverse, or you could simply place the user control on a Web page. My guess is that more often than not you will want to convert a page into a control to promote reusability and to facilitate composite pages. ASP.NET applications that employ control compositing are likely to have as many, if not more, user controls than pages.