The UserControl class is the base class for all user controls that you create. User controls have their own file extension: .ascx is associated with user controls, just as .aspx is associated with Web pages. User controls can be created with any text editor, and of course Microsoft Visual Studio offers good support for creating user controls. Another important authoring option is to create and test as a Web page, and then convert the page into a user control. I will leave text editor authoring as an exercise for the brave user, and the following sections will explain how to create a user control in Visual Studio directly, cache a user control, and create a new page that can be converted into a user control. Along the way, I will show you how to use user control caching, another significant advantage to using user controls.
Creating a user control in Visual Studio is much like creating any other new item. In a new Web site named CustomControls, right-click the Web site in Solution Explorer, and then select Add New Item from the context menu. In the Add New Item dialog box, select Web User Control from the installed templates, as shown in Figure 6-1. The default name is WebUserControl.ascx. Click Add, and the new user control is added to your solution.
Figure 6-1: Adding a user control in Visual Studio
The Source view of the user control in Visual Studio is shown in Figure 6-2. Note that I added a line break so that all attributes are visible in the screen shot.
Figure 6-2: A new user control in Source view
The user control has an @ Control directive that is very similar to the @ Page directive of a Web Form. And that is it. There is nothing else in the markup for the user control (unlike a Web Form, which includes lots of default markup). The markup included in the default page of a Web Form is unnecessary for user controls because user controls are added to an existing page; the HTML and body tags (which can only exist once per page) are part of that existing page rather than the user control.
Switch to Design view, and you see a totally blank screen. Just as with a Web Form, you can drag and drop other controls onto the page, and you can use Source view to add markup to the user control. In this example, I dropped a Label control onto the page. I also wanted to modify the label whenever the page was loaded. As with Web Forms, you can double-click the user control in Design view outside of any control to see the source code of the user control. When you do so, the source code for the user control looks like the following code.
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class WebUserControl : System.Web.UI.UserControl { protected void Page_Load(object sender, EventArgs e) { // Control initialization code here... } }
The most important thing about this default code is that the WebUserControl class inherits from System.Web.UI.UserControl. The UserControl class provides essentially the same properties as the Page class. If required, the user control based class can also access the current page by referencing the Page property of the UserControl class.
To allow the user control to do something useful, I added a single line of code to the Page_Load event handler.
this.Label1.Text = DateTime.Now.ToLongTimeString();
This code sets the text of the label to the current time. In Visual Studio, go to the Default.aspx page that Visual Studio created by default. From Solution Explorer, drag a copy of the user control onto the page, and the page appears as shown in Figure 6-3.
Figure 6-3: The UserControl Tasks menu after dropping a user control onto a form
The UserControl Tasks menu allows you to edit the user control or refresh the contents. By editing the user control, you can modify properties of controls contained in the user control. For instance, you can click the label to modify the properties of the label. Refreshing the contents causes the page display to be refreshed with the latest version of the control.
After you drag a copy of the user control onto the Default.aspx page, the markup is as shown in Listing 6-1.
Listing 6-1: Default.aspx after a User Control Is Added
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <%@ Register src="/books/4/401/1/html/2/WebUserControl.ascx" TagName="WebUserControl" TagPrefix="uc1" %> <!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>Default Page</title> </head> <body> <form runat="server"> <div> <uc1:WebUserControl runat="server" /> </div> </form> </body> </html>
The bulk of the page is the default markup created by Visual Studio. The two highlighted lines are added as a result of dragging and dropping the user control onto the form. The first highlighted line registers the user control on the page. The attributes of the @ Register directive are described in Table 6-1.
Attribute | Description |
---|---|
Src | The location (relative or absolute) of the user control |
TagName | The alias to associate with the user control |
TagPrefix | The alias to associate with the namespace for the user control |
Several other attributes can be used with the @ Register directive. However, these are generally associated with custom server controls, which will be covered later in this chapter.
Recall that when you add a TextBox control to a Web Form, asp:TextBox is the tag that marks the text box control in the markup. Your own controls are referenced by tags in the format TagPrefix:TagName. The TagPrefix specified in the @ Register directive is used as a namespace to ensure that controls are named uniquely. Thus, an instance of the new user control is declared in the source of a page with the following markup.
<uc1:WebUserControl runat="server" />
When you run the Default.aspx page, it looks something like the page shown in Figure 6-4; of course, the time will be different.
Figure 6-4: Default.aspx running in the browser, showing output from the WebUserControl user control
What is the fastest way to get data from a database? This is a bit of a trick question. The fastest way to get data out of a database is to remember it from the last time you got it from the database, and just display it again without a new database request. This is the idea behind caching. The user control created in the last section is a very simple control that is easy to render. Imagine, however, that you had a much more complex control that took some time to render, either because of some lengthy general processing or because it had to access a database. In many cases, caching the user control and rendering it only every minute or so can provide a significant performance and scalability advantage in a very busy application.
User controls (and entire pages in ASP.NET 2.0, for that matter) can be cached by using the @ OutputCache directive. Any arbitrary object can also be cached. The cache is application-scoped storage, meaning that all users of an application share the same cache. The @ OutputCache directive is placed at the top of the .ascx file. For example, adding the following directive to a user control will cause the output of the user control to be cached for 60 seconds.
<%@ OutputCache Duration="60" VaryByParam="none" %>
In this example, the Duration attribute is the amount of time for which the item should be cached, in seconds. The VaryByParam attribute is set to none to indicate that the caching should not depend on any parameters. Table 6-2 shows all possible values for the @ OutputCache directive.
Attribute | Description |
---|---|
Duration | The amount of time, in seconds, that the page or user control will be cached. This is a required attribute. |
Location | The location where the item will be cached. This attribute is not supported for user controls. Allowed values are Any, meaning that the output cache can be located on the browser client, a proxy server, or any server participating in the request or on the server where the request was processed; Client, meaning that the output cache can be located on the browser client where the request originated; Downstream, meaning that the output cache can be located on any HTTP 1.1 cache capable device other than the origin server; None, meaning that no caching is enabled; Server, meaning that the output cache can be located on the Web server where the request was processed; or ServerAndClient, meaning that items can be cached only on the Web server or the requesting browser client. The default value is Any. |
Shared | A Boolean value that indicates whether user control output can be shared for multiple pages. If the user control does special processing on a page-by-page basis, this should be set to false. The default value is false. |
VaryByCustom | Any text that represents custom output caching requirements. If the string "browser" is specified, the cache is varied by browser name and major browser version. If some other string is specified, you must override the HttpApplication.GetVaryByCustomString method in the Global.asax file for your application. |
VaryByHeader | A semicolon-delimited list of HTTP headers used to vary the output cache. This attribute is not supported for user controls. A copy of the output is cached for each unique combination of the headers specified. This setting enables caching not only in the ASP.NET cache, but also in all HTTP 1.1 caches. |
VaryByParam | A semicolon-delimited list of strings used to vary the cache. The strings represent query string values sent with a GET method attribute or a parameter sent using the POST method. Possible values are none, meaning that no parameters are used to create unique copies in cache; *, meaning that all parameters are used to create unique copies in cache; or a list of specific parameters. VaryByParam must be specified in @ OutputCache directives in ASP.NET pages, and in user controls as well, unless VaryByControl is specified. |
VaryByControl | A semicolon-delimited list of strings used to vary the cache. The strings represent the IDs of ASP.NET server controls. This attribute is not supported on ASP.NET pages, and it is required on user controls, unless VaryByParam is specified. |
Note | Although you set the duration for the cached page or user control, it is not absolutely certain that the object will be held in cache for the amount of time specified. The advantage of the cache is that if the server requires more memory for additional load processing, the system can release some cached objects. ASP.NET keeps track of when cached objects are used; when the server requires more memory, it searches for the object in cache for which the last use is furthest in the past. These infrequently used objects are called "least recently used." This applies specifically to the ASP.NET cache and not to other caches in proxy servers or browser clients. |
Why are all these attributes needed? Often, you will cache a user control that retrieves specific data. Using the BikeBlog database example from the last chapter, you might create a user control to display a specific row in the BlogEntry table. The BlogEntry row that is displayed might depend on a parameter passed into the page on the URL (for instance, ShowBlogEntry .aspx?BlogEntryID=2), or you might have a DropDownList control that specifies the BlogEntry row. Using the VaryByParameter or VaryByControl parameters, respectively, would allow these scenarios to work correctly.
To test the effect of caching, I created a new user control named CachedWebUserControl.ascx and a page named CacheTest.aspx. CachedWebUserControl.ascx and CachedWebUserControl.ascx.cs are shown in Listings 6-2a and 6-2b, and CacheTest.aspx and CacheTest .aspx.cs are shown in Listings 6-3a and 6-3b.
Listing 6-2a: CachedWebUserControl.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CachedWebUserControl.ascx.cs" Inherits="CachedWebUserControl" %> <%@ OutputCache Duration="60" VaryByParam="none" %> <asp:Label runat="server" Text="Label"></asp:Label>
Listing 6-2b: CachedWebUserControl.ascx.cs
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class CachedWebUserControl : System.Web.UI.UserControl { protected void Page_Load(object sender, EventArgs e) { this.Label1.Text = string.Format( "The Time in the User Control is: {0}", DateTime.Now.ToLongTimeString()); } }
Listing 6-3a: CacheTest.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CacheTest.aspx.cs" Inherits="CacheTest" %> <%@ Register src="/books/4/401/1/html/2/CachedWebUserControl.ascx" TagName="CachedWebUserControl" TagPrefix="uc1" %> <!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>Cache Test/title> </head> <body> <form runat="server"> <div> <asp:Label runat="server" Text="Label"></asp:Label><br /> <uc1:CachedWebUserControl runat="server"> </uc1:CachedWebUserControl> </div> </form> </body> </html>
Listing 6-3b: CacheTest.aspx.cs
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class CacheTest : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { this.Label1.Text = string.Format( "The Time in the Web Page is: {0}", DateTime.Now.ToLongTimeString()); } }
CachedWebUserControl.ascx is extremely simple, containing just the @ Control directive, an @ OutputCache directive, and the label tag. CachedWebUserControl.ascx.cs contains a Page_Load event handler that simply sets the text of the label on the user control, clearly identifying itself as coming from the user control and placing the time in the text.
CacheTest.aspx is also a simple page. In addition to the boilerplate markup generated by Visual Studio, it contains a Label control and an instance of the CachedWebUserControl user control. The code in CacheTest.ascx.cs is very similar to the code in CachedWebUserControl.ascx.cs. It sets the text of the Label control on the page, clearly identifying it as coming from the page, and it places the time in the label text.
When initially run, the page looks something like Figure 6-5, with the same time value displayed in the Web page and the user control.
Figure 6-5: CacheTest.aspx running in the browser, showing output from both the page and the WebUserControl user control
When you refresh the page, you see something similar to Figure 6-6, with the time in the Web page updated, but the time in the user control the same as the initial post, assuming that you clicked Refresh in less than 60 seconds (the cache duration). This shows that the user control is, indeed, being cached.
Figure 6-6: CacheTest.aspx running in the browser after refreshing the page, showing the effect of caching the user control
Caching involves some important implications. First, when a user control (or a page) is cached, the events associated with the page (such as the Page_Load event) will not be called when the item is requested and retrieved from the cache. Also, users might receive slightly outdated information in some circumstances in which caching is used. For instance, if you cache a user control that displays data from a database, it is possible that, for at least the duration of the cache, users might see old information. In some cases, this can be a reasonable tradeoff if the data isn't very dynamic. In cases in which this is not acceptable, you should not enable caching, or you should consider using the new ASP.NET cache dependency SqlCacheDependency class, and programmatically control caching of your user control. The SqlCacheDependency class is available, in slightly different forms, for SQL Server 7.0 and later. See the MSDN documentation for details about the SqlCacheDependency class. Other cache dependency controls are similarly documented. You can also read Programming Microsoft ASP.NET 2.0 Core Reference by Dino Esposito (Microsoft Press, 2005) for a more complete description of ASP.NET caching and cache controls.
If you are creating a complex user control, testing it in a live page might be more difficult than debugging a Web Form. Fortunately, there is a reasonably well-documented way to create a new Web Form, debug it, and then convert it to a user control.
Recall from the Bike Blog example in Chapter 5 that the listing of BlogEntry rows was done in a GridView control. GridView controls are reasonable options in many cases, but in this particular case, a richer user interface might be desirable.
Note | There is nothing in the following example that, strictly speaking, could not be done in a GridView control by using some complex templating. However, the technique is broadly applicable and an excellent methodology for creating complex user interfaces that are more easily debugged. |
Figure 6-7 shows a page designed to display an individual BlogEntry row. Note that for the page, the BlogEntryID is passed in on the URL.
Figure 6-7: DisplayBlogEntry.aspx displaying a single blog entry
The markup in DisplayBlogEntry.aspx is fairly straightforward, although it contains several previously covered features. The source for the page is shown in Listing 6-4.
Listing 6-4: DisplayBlogEntry.aspx, Which Displays a Single Blog Entry Row and Acts as a Base for a User Control
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="DisplayBlogEntry.aspx.cs" Inherits="DisplayBlogEntry" %> <!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>Display Blog Entry</title> <link href="StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body> <form runat="server"> <div> <table width=100%> <tr> <td align=right > <asp:Label runat="server" Text="When:"></asp:Label></td> <td> <asp:Label runat="server"></asp:Label></td> </tr> <tr> <td align=right > <asp:Label runat="server" Text="Subject:"></asp:Label></td> <td> <asp:Label runat="server"></asp:Label></td> </tr> <tr> <td align=right > <asp:Label runat="server" Text="Weather:"></asp:Label></td> <td> <asp:Label runat="server"></asp:Label></td> </tr> <tr> <td align=right valign="top"> <asp:Label runat="server" Text="Message:"></asp:Label></td> <td> <asp:Label runat="server"></asp:Label></td> </tr> </table> </div> </form> </body> </html>
The changes I made to the default page that Visual Studio created begin with the inclusion of a style sheet. In the body of the page, there is also a table set to 100 percent of the width of whatever contains it (in this case the enclosing form, which will fill the browser window). Each row has two columns. The left column is aligned right, with the style class set to "leftcol". The right column has a label, with the ID set to a reasonable value and no text value set. StyleSheet.css is shown in Listing 6-5.
Listing 6-5: StyleSheet.css, Used to Style DisplayBlogEntry.aspx
body { font-family:Verdana; } textarea { font-family:Verdana; font-size:12px; } .leftcol { width:100px; background-color:Blue; color:Yellow; border-style:outset; border-color:Blue; }
The first style sets the font family of the page to Verdana. The next style sets the font family and the font size for any text area control to also be Verdana with a font size of 12. The final style is applied to the left column of the table; the class of everything in the left column is set to "leftcol". The width of the left column is set to 100 pixels. Initially, I allowed the width to be set by the browser. However, when the page is converted to a user control, and multiple instances of the user control are displayed one on top of another, the column width of all instances should be identical (100 pixels was an arbitrary value that was both visually appealing and contained the column text that I wanted to display). The background-color and color attributes are set to contrasting values, again to present an interesting user interface. Finally, the border style and border color are set to values that ensure that the labels will stand out, as you can see in Figure 6-7.
The code file, DisplayBlogEntry.aspx.cs, is shown in Listing 6-6.
Listing 6-6: DisplayBlogEntry.aspx.cs
using System; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class DisplayBlogEntry : System.Web.UI.Page { public int BlogEntryID { get { return ViewState["BlogEntryID"] == null ? 0 : (int)ViewState["BlogEntryID"]; } set { ViewState["BlogEntryID"] = value; } } protected void Page_Load(object sender, EventArgs e) { // This is code that will go away when we convert // to a user control... if (this.IsPostBack == false) { int _BlogEntryID; int.TryParse(Request["BlogEntryID"], out _BlogEntryID); BlogEntryID = _BlogEntryID; } // This code will be used in the final user control... if (this.IsPostBack == false) { SqlDataReader dr = this.SelectBlogEntryDR(BlogEntryID); try { if (dr.Read()) { string Weather = string.Empty; Weather = dr["WeatherCondition"].ToString() + ", Wind " + dr["WindStrength"].ToString(); if ( dr["WindStrength"].ToString(). IndexOf("Still") < 0) { Weather += " Out of the " + dr["WindDirection"].ToString(); } this.lblWhen.Text = ((DateTime)dr["DateEntered"]).ToString(); this.lblSubject.Text = dr["Subject"].ToString(); this.lblMessage.Text = dr["Message"].ToString(); this.lblWeather.Text = Weather; } } finally { dr.Close(); } } } private SqlDataReader SelectBlogEntryDR(int BlogEntryID) { string spName = "spSelectBlogEntryAndWeather"; SqlDataReader dr = null; SqlConnection cn = new SqlConnection( ConfigurationManager.ConnectionStrings[ "BikeBlogConnectionString"].ConnectionString); cn.Open(); try { SqlCommand cmd = new SqlCommand(spName, cn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@BlogEntryID", BlogEntryID); dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); } catch { // Close the connection in case of error... cn.Close(); } return dr; } }
The using lines are defaults supplied by Visual Studio, except for the using line for System.Data.SqlClient. An integer parameter named BlogEntryID is declared and persisted into view state, as we've seen in earlier chapters.
The Page_Load event handler contains two if statements. The first is required, because to test the user control as a Web page, we must pass the BlogEntryID argument on the URL. This code will completely disappear when the page is converted to a user control. The second if statement binds the data to the labels in the user control and closes the DataReader object. This if statement also contains some code to properly display weather conditions in what should seem like normal English.
The SelectBlogEntryDR function is practically identical to the version used in Chapter 5 in the BetterEdit.aspx example. This version calls a different stored procedure, however, which adds descriptions of the weather columns in the BlogEntry table based on BlogEntryID. The stored procedure selects a single Blog Entry row as well as the descriptions of the weather columns from that row. The descriptions are stored in other tables and returned as a single returned result set.
The steps to convert a Web page to a user control are:
Remove all <html>, <body>, and <form> elements from the markup file.
Change the @ Page directive to an @ Control directive. Note that this could require changes to attributes supported on a page, but not in a control. For instance, the Buffer attribute is supported in the @ Page directive but not in the @ Control directive.
Include a className attribute in the @ Control directive. This allows the control to be strongly typed when it is added to a page or other server control programmatically.
Give the control a name that reflects how you plan to use it, and then change the extension of the markup file from .aspx to .ascx.
Rename the code file (change the name to match the markup file, and change .aspx.cs to .ascx.cs), and then change the class it is based on from System.Web.UI.Page to System.Web.UI.UserControl.
After I had DisplayBlogEntry.aspx working, the conversion was straightforward. First, I removed everything from DisplayBlogEntry.aspx except the markup inside the div tag. Then I changed the directive at the top of the page. Here is the original.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="DisplayBlogEntry.aspx.cs" Inherits="DisplayBlogEntry" %>
Here is the new directive.
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="DisplayBlogEntryUC.ascx.cs" Inherits="DisplayBlogEntryUC" %>
Because I kept the original file in the solution, I renamed the file DisplayBlogEntryUC.ascx, and I changed the Inherits attribute and the name of the source file to reflect this change, so that the Inherits attribute matched the name of the class in the code-behind file. The rest of the markup remained the same.
The C# source code file, DisplayBlogEntryUC.ascx.cs, changed a little more than the markup did. The code is shown in Listing 6-7.
Listing 6-7: DisplayBlogEntryUC.ascx.cs
using System; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; /// <summary> /// Summary description for DisplayBlogEntryUC /// </summary> public partial class DisplayBlogEntryUC : System.Web.UI.UserControl { public int BlogEntryID { get { return ViewState["BlogEntryID"] == null ? 0 : (int)ViewState["BlogEntryID"]; } set { ViewState["BlogEntryID"] = value; SetData(); } } public DisplayBlogEntryUC() { // // TODO: Add constructor logic here // } protected void Page_Load(object sender, EventArgs e) { // This code will be used in the final version of // the user control... if (this.IsPostBack == false) { } } private void SetData() { SqlDataReader dr = this.SelectBlogEntryDR(BlogEntryID); try { if (dr.Read()) { string Weather = string.Empty; Weather = dr["WeatherCondition"].ToString() + ", Wind " + dr["WindStrength"].ToString(); if (dr["WindStrength"].ToString().IndexOf("Still") < 0) { Weather += " Out of the " + dr["WindDirection"].ToString(); } this.lblWhen.Text = ((DateTime)dr["DateEntered"]).ToString(); this.lblSubject.Text = dr["Subject"].ToString(); this.lblMessage.Text = dr["Message"].ToString(); this.lblWeather.Text = Weather; } } finally { dr.Close(); } } private SqlDataReader SelectBlogEntryDR(int BlogEntryID) { string spName = "spSelectBlogEntryAndWeather"; SqlDataReader dr = null; SqlConnection cn = new SqlConnection( ConfigurationManager.ConnectionStrings[ "BikeBlogConnectionString"].ConnectionString); cn.Open(); try { SqlCommand cmd = new SqlCommand(spName, cn); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@BlogEntryID", BlogEntryID); dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); } catch { // Close the connection in case of error... cn.Close(); } return dr; } }
The first change to DisplayBlogEntryUC.ascx.cs was a change in the base class. Because the file was now a user control, it had to inherit from System.Web.UI.UserControl rather than System.Web.UI.Page. The next change required was to create a new way to set the BlogEntryID. When I created the page, the BlogEntryID was passed in on the URL. Because there could be many instances of the user control on a page, passing in the BlogEntryID on the URL is not an acceptable option. Fortunately, there is an easy alternative. Because the user control (like the page from which it is converted) has a property named BlogEntryID, the BlogEntryID property appears in the Properties window when an instance of the user control is dropped onto a form. Figure 6-8 shows the user control dropped onto a test Web Form (TestUserControl.aspx) and the Properties window showing the BlogEntryID property set to 2.
Figure 6-8: TestUserControl.aspx in Design view, showing the BlogEntryID property
A nice thing about user control properties is that, in addition to setting them in the Properties window, you can also set them in the markup. For instance, in TestUserControl.aspx, the markup for the user control is as shown here.
<uc1:DisplayBlogEntryUC runat="server" BlogEntry />
Of course, property editing goes both ways. That is, if the BlogEntryID property is changed in the page markup, it is also changed in the Properties window.
Look back at Listing 6-6, showing DisplayBlogEntry.aspx.cs. The BlogEntryID property is set in the Page_Load event based on the value passed in on the URL. A much better solution for providing the BlogEntryID to possibly many controls on the page is to take the code from the section of the Page_Load event and create a new method, in this case named SetData. The SetData method is called in the set method of the BlogEntryID property. In this way, whenever the BlogEntryID property is set, the data associated with the ID is also retrieved from the database.
To show the user control in a more realistic setting, I created a new page in the CustomControls Web site named UserControlInRepeater.aspx. When run, the page appears as shown in Figure 6-9.
Figure 6-9: UserControlInRepeater.aspx running in the browser
UserControlInRepeater.aspx contains a single repeater and a single associated SqlDataSource control, as shown in Listing 6-8.
Listing 6-8: UserControlInRepeater.aspx.cs
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UserControlInRepeater.aspx.cs" Inherits="UserControlInRepeater" %> <%@ Register src="/books/4/401/1/html/2/DisplayBlogEntryUC.ascx" TagName="DisplayBlogEntryUC" TagPrefix="uc1" %> <!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>User Control In Repeater</title> <link href="StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body> <form runat="server"> <div> <asp:Repeater runat="server" DataSource> <ItemTemplate> <uc1:DisplayBlogEntryUC runat="server" BlogEntryID= '<%# Bind("BlogEntryID")%>' /><br /> </ItemTemplate> </asp:Repeater> <asp:SqlDataSource runat="server" ConnectionString= "<%$ ConnectionStrings:BikeBlogConnectionString %>" ProviderName="System.Data.SqlClient" SelectCommand="SELECT [BlogEntryID] FROM [BlogEntry] ORDER BY [DateEntered] DESC"> </asp:SqlDataSource> </div> </form> </body> </html>
In addition to the boilerplate text supplied by Visual Studio, I added the @ Register tag toward the top of the page. Next, I changed the title from the default supplied by Visual Studio. I dragged and dropped a repeater control from the Toolbox onto the form. I also dragged a SqlDataSource control onto the form and configured it. The SQL statement used selects only the BlogEntryID column from the BlogEntry table, and then it places the BlogEntryID values in descending order by DateEntered. To make the data source easily identifiable, I gave it a meaningful name dsBlogEntryList.
Note | I could just as easily have used a stored procedure to retrieve the BlogEntryID column for each of the blog entries; however, retrieving a single column seemed to be one of the cases in which a simple SQL statement was reasonable. |
After the SqlDataSource control was configured, I set the DataSourceID property of the repeater to dsBlogEntryList so that I could access all the columns from the data source in the repeater in this case, just the BlogEntryID column. To add the user control, I switched to Source view and added the following item template.
<ItemTemplate> <uc1:DisplayBlogEntryUC runat="server" BlogEntryID= '<%# Bind("BlogEntryID")%>' /><br /> </ItemTemplate>
Here's how the item template works. For each item in the data source, an instance of the DisplayBlogEntryUC user control will be instantiated, and the BlogEntryID will be set to the BlogEntryID in the data source. I could have added an alternate item template to alter the background on every other item to emphasize the transitions from one record to the next. However, I simply placed a break tag (<br />) at the end of the item template to add a line break between items.
User controls are often used for other, simpler uses. For instance, a menu, a header, or a footer might be made into a user control to be shared on many pages. Truthfully, with the advent of convenient Master Pages (described in Chapter 3, "Web Form Layout"), user controls are less likely to be used in such situations. There are still many reasons to use a user control. Whenever you need to use some bit of markup or functionality on multiple pages, a user control is a possible solution.