Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1201 Chapter 26: User and Server Controls If (c1.GetType() Is GetType(WebUserControl)) Then ’This control is not participating in OutputCache CType(c1, WebUserControl).ID = "myWebUserControl1" CType(c1, WebUserControl).Text = "My users controls text" ElseIf (c1.GetType() Is GetType(PartialCachingControl) And _ ((CType(c1, PartialCachingControl)).CachedControl IsNot Nothing)) Then ’The control is participating in output cache, but has expired Dim myWebUserControl as WebUserControl = _ CType((CType(c1, PartialCachingControl).CachedControl), _ WebUserControl) myWebUserControl.ID = "myWebUserControl1" myWebUserControl.Text = "My users controls text" End If End Sub </script> C# <script runat="server"> void Page_Load(object sender, EventArgs e) { Control myForm = Page.FindControl("Form1"); Control c1 = LoadControl("WebUserControl.ascx"); myForm.Controls.Add(c1); if (c1 is WebUserControl) { //This control is not participating in OutputCache ((WebUserControl)c1).ID = "myWebUserControl1"; ((WebUserControl)c1).Text = "My users controls text"; } else if ((c1 is PartialCachingControl) && ((PartialCachingControl)c1).CachedControl != null) { //The control is participating in output cache, but has expired WebUserControl myWebUserControl = ((WebUserControl)((PartialCachingControl)c1).CachedControl); myWebUserControl.ID = "myWebUserControl1"; myWebUserControl.Text = "My users controls text"; } } </script> The sample demonstrates how you can test to see what type the LoadControl returns and set properties based on the type. For more information on caching, check out Chapter 23. Finally, in all the previous samples that demonstrate dynamically adding user controls, the user controls have been added during the Page_Load event. But there may be times when you want to add the control based on other events, such as a button’s Click event or the SelectedIndexChanged event of a DropDownList control. Using these events to add user controls dynamically presents challenges. Specifically, because the events may not be raised each time a page postback occurs, you need to create a way to track when a user control has been added so that it can be re-added to the Web page as additional postbacks occur. 1201 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1202 Chapter 26: User and Server Controls A simple way to do this is to use the ASP.NET session to track when the user control is added to the Web page. Listing 26-9 demonstrates this. Listing 26-9: Tracking added user controls across postbacks VB <%@ Page Language="VB" %> <script runat="server"> Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) ’Make sure the control has not already been added to the page If ((Session("WebUserControlAdded") Is Nothing) Or _ (Not CBool(Session("WebUserControlAdded")))) Then Dim myForm As Control = Page.FindControl("Form1") Dim c1 As Control = LoadControl("WebUserControl.ascx") myForm.Controls.Add(c1) Session("WebUserControlAdded") = True End If End Sub Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) ’Check to see if the control should be added to the page If ((Session("WebUserControlAdded") IsNot Nothing) And _ (CBool(Session("WebUserControlAdded")))) Then Dim myForm As Control = Page.FindControl("Form1") Dim c1 As Control = LoadControl("WebUserControl.ascx") myForm.Controls.Add(c1) End If End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" /> <asp:Button ID="Button2" runat="server" Text="Button" /> </div> </form> </body> </html> C# <%@ Page Language="C#" %> <script runat="server"> protected void Button1_Click(object sender, EventArgs e) 1202 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1203 Chapter 26: User and Server Controls { //Make sure the control has not already been added to the page if ((Session["WebUserControlAdded"] == null) || (!(bool)Session["WebUserControlAdded"])) { Control myForm = Page.FindControl("Form1"); Control c1 = LoadControl("WebUserControl.ascx"); myForm.Controls.Add(c1); Session["WebUserControlAdded"] = true; } } protected void Page_Load(object sender, EventArgs e) { //Check to see if the control should be added to the page if ((Session["WebUserControlAdded"] != null) && ((bool)Session["WebUserControlAdded"])) { Control myForm = Page.FindControl("Form1"); Control c1 = LoadControl("WebUserControl.ascx"); myForm.Controls.Add(c1); } } </script> This sample used a simple Session variable to track whether the user control has been added to the page. When the Button1 Click event fires, the session variable is set to True , indicating that the user control has been added. Then, each time the page performs a postback, the Page_Load event checks to see if the session variable is set to True , and if so, it re-adds the control to the page. Server Controls The power to create server controls in ASP.NET is one of the greatest tools you can have as an ASP.NET developer. Creating your own custom server controls and extending existing controls are actually both quite easy. In ASP.NET 3.5, all controls are derived from two basic classes: System.Web.UI.WebControls .WebControl or System.Web.UI.ScriptControl . Classes derived from the WebControl class have the basic functionality required to participate in the Page framework. These classes include most of the common functionality needed to create controls that render a visual HTML representation and provide support for many of the basic styling elements such as Font, Height, and Width. Because the WebControl class derives from the Control class, the controls derived from it have the basic functionality to be a designable control, meaning they can be added to the Visual Studio Toolbox, dragged onto the page designer, and have their properties and events displayed in the Property Browser. Controls derived from the ScriptControl class build on the functionality that the WebControl class provides by including additional features designed to make working with client-side script libraries easier. The class tests to ensure that a ScriptManager control is present in the hosting page during the controls’ PreRender stage, and also ensures that derived controls call the the proper ScriptManager methods during the Render event. 1203 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1204 Chapter 26: User and Server Controls WebControl Project Setup This section demonstrates just how easy it is to create custom server controls by creating a very simple server control that derives from the WebControl class. In order to create a new server control, you create a new ASP.NET Server Controlproject. You can use this project to demonstrate concepts throughout the rest of this chapter. In Visual Studio, choose File ➪ New Project to open the New Project dialog box. From the Project Types tree, open either the Visual Basic or Visual C# nodes and select the Web node. Figure 26-2 shows the New Project dialog with a Visual C# ASP.NET Server Control project template selected. Figure 26-2 When you click OK in the New Project dialog box, Visual Studio creates a new ASP.NET Server Con- trol project for you. Notice that the project includes a template class that contains a very simple server control. Listing 26-10 shows the code for this template class. Listing 26-10: The Visual Studio ASP.NET Server Control class template VB Imports System Imports System.Collections.Generic Imports System.ComponentModelImports System.Text Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls 1204 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1205 Chapter 26: User and Server Controls <DefaultProperty("Text"), _ ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")> _ Public Class ServerControl1 Inherits WebControl <Bindable(True), Category("Appearance"), DefaultValue(""), Localizable(True)> _ Property Text() As String Get Dim s As String = CStr(ViewState("Text")) If s Is Nothing Then Return "[" + Me.ID + "]" Else Return s End If End Get Set(ByVal Value As String) ViewState("Text") = Value End Set End Property Protected Overrides Sub RenderContents(ByVal output As HtmlTextWriter) output.Write(Text) End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web.UI; using System.Web.UI.WebControls; namespace ServerControl1 { [DefaultProperty("Text")] [ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] public class ServerControl1 : WebControl { [Bindable(true)] [Category("Appearance")] [DefaultValue("")] [Localizable(true)] public string Text { get { String s = (String)ViewState["Text"]; return ((s == null) ? "[" + this.ID + "]" : s); } 1205 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1206 Chapter 26: User and Server Controls set { ViewState["Text"] = value; } } protected override void RenderContents(HtmlTextWriter output) { output.Write(Text); } } } This template class creates a basic server control that exposes one property called Text and renders the value of that property to the screen. Notice that you override the RenderContents method of the control and write the value of the Text property to the pages output stream. We talk more about rendering output later in the chapter. Note that creating a server control project is not the only way to create an ASP.NET server control. Visual Studio 2008 also provides a basic ASP.NET Server Control file template that you can add to either an existing ASP.NET Server Control project, or to any other standard class library project. This template however differs slightly from the template used to create the default server control included in the ASP.NET Server control project. It uses a different filename scheme, and includes slightly different code in the default Text propertys getter. Now, take this class and use it in a sample Web application by adding a new Web Project to the existing solution. The default Web page, created by Visual Studio, serves as a test page for the server control samplesinthischapter. Visual Studio 2008 will automatically add any controls contained in projects in the open Solution to the Toolbox for you. To see this, simply build the Solution and then open the default Web page of the Web Project you just added. The Toolbox should contain a new section called WebControlLibrary1 .Components, and the new server control should be listed in this section (see Figure 26-3). Now, all you have to do is drag the control onto the Web Form, and the control’s assembly is automati- cally added to the Web project for you. When you drag the control from the Toolbox onto the designer surface, the control adds itself to the Web page. Listing 26-11 shows you what the Web page source code looks like after you have added the control. Listing 26-11: Adding a Web Control Library to a Web page <%@ Register Assembly="MyServerControl" Namespace="ServerControl1" TagPrefix="cc1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Adding a Custom Web Control</title> </head> 1206 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1207 Chapter 26: User and Server Controls <body> <form id="form1" runat="server"> <div> <cc1:ServerControl1 ID="ServerControl1" runat="server" /> </div> </form> </body> </html> Figure 26-3 After you drag the control onto the Web form, take a look at its properties in the Properties Window. Figure 26-4 shows the properties of your custom control. Notice that in addition to the Text property you defined in the control, the control has all the basic properties of a visual control, including various styling and behavior properties. The properties are exposed because the control was derived from the WebControl class. The control also inherits the base events exposed by WebControl . Make sure the control is working by entering a value for the Text property and viewing the page in a browser. Figure 26-5 shows what the page looks like if you set the Text property to "Hello World!" . 1207 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1208 Chapter 26: User and Server Controls Figure 26-4 As expected, the control has rendered the value of the Text property to the Web page. If you view the HTML source of this sample, you will see that not only has ASP.NET added the value of the Text property to the HTML markup, but it has surrounded the text with a < SPAN > block. If you look at the code for the WebControl class’s render method, you can see why. 1208 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1209 Chapter 26: User and Server Controls protected internal override void Render(HtmlTextWriter writer) { this.RenderBeginTag(writer); this.RenderContents(writer); this.RenderEndTag(writer); } Figure 26-5 You can see that, by default, the Render method, which calls the RenderContents method, also includes the RenderBeginTag and RenderEndTag methods, causing the Span tags to be added. If you have provided an ID value for your control, then the Span tag will also, by default, render an ID attribute. Having the Span tags can sometimes be problematic, so if you want to prevent this in ASP.NET, you simply override the RenderMethod and call the RenderContents method directly. VB Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Me.RenderContents(writer) End Sub C# protected override void Render(HtmlTextWriter writer) { this.RenderContents(writer); } This prevents ASP.NET from automatically adding the Span tags. The samples in this section demonstrate just how easy it is to create a simple server control. Of course, this control does not have much functionality and lacks many of the features of a server control. The following section shows how you can use attributes to enhance this server control to make it more useful and user-friendly. Control Attributes A key enhancement to the design-time experience for users utilizing server controls is achieved by adding attributes to the class level and to the control’s classes and properties. Attributes define much of how 1209 Evjen c26.tex V2 - 01/28/2008 3:48pm Page 1210 Chapter 26: User and Server Controls the control behaves at design time in Visual Studio. For instance, when you look at the default control template from the previous section (Listing 26-6), notice that attributes have been applied to both the Class and to the Text property. In this section, you study these attributes and how they affect the behav- ior of the control. Class Attributes Class attributes generally control how the server control behaves in the Visual Studio Toolbox and when placed on the design surface. The class attributes can be divided into three basic categories: attributes that help the Visual Studio designer know how to render the control at design time, attributes that help you tell ASP.NET how to render nested controls, and attributes that tell Visual Studio how to display the control in the Toolbox. The following table describes some of these attributes. Attribute Description Designer Indicates the designer class this control should use to render a design-time view of the control on the Visual Studio design surface TypeConverter Specifies what type to use as a converter for the object DefaultEvent Indicates the default event created when the user double-clicks the control on the Visual Studio design surface DefaultProperty Indicates the default property for the control ControlBuilder Specifies a ControlBuilder class for building a custom control in the ASP.NET control parser ParseChildren Indicates whether XML elements nested within the server controls tags will be treated as properties or as child controls TagPrefix Indicates the text the control is prefixed with in the Web page HTML Property/Event Attributes Property attributes are used to control a number of different aspects of server controls. You can use attributes to control how your properties and events behave in the Visual Studio Property Browser. You can also use attributes to control how properties and events are serialized at design time. The following table describes some of the property and event attributes you can use. Obviously, the class and property/event attribute tables present a lot of information upfront. You already saw a demonstration of some of these attributes in Listing 26-1; now, as you go through the rest of the chapter, you will spend time working with most of the attributes listed in the tables. Control Rendering Now that that you have seen the large number of options you have for working with a server control at design-time, look at what you need to know to manage how your server control renders its HTML at runtime. 1210 . controls in ASP. NET is one of the greatest tools you can have as an ASP. NET developer. Creating your own custom server controls and extending existing controls are actually both quite easy. In ASP. NET. text" ElseIf (c1.GetType() Is GetType(PartialCachingControl) And _ ((CType(c1, PartialCachingControl)).CachedControl IsNot Nothing)) Then ’The control is participating in output cache, but has expired Dim. HtmlTextWriter) output.Write(Text) End Sub End Class C# using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Web.UI; using System.Web.UI.WebControls; namespace