Building Smarter User Controls

User Controls are very common in today's ASP.NET websites, both in version 1.x as in version 2.0. Many sites use them to reuse content, like a menu, a header or a footer or even for partial page content, like a shop's product catalog.

However, User Controls can be used for more than simply displaying static, repeating content. With a bit of knowledge about how User Controls work, you can teach them a few tricks. By adding public properties to a User Control, they become much more powerful and easier to reuse.

In this article I'll show you how to build a User Control that displays information about an article, like a news item, similar to the Details section that you find at the top of most pages in my site.

Although I am using Visual Studio 2005 and ASP.NET 2.0 in this article, the same principles and code work for Visual Studio .NET 2002 / 2003 and ASP.NET 1.x as well.

Introduction

Before I show you how to build such a control, let's briefly consider the current functionality of the control. On each page on my website, you see one or more "Infopanels" in the left-hand bar. For the page you're viewing right now, the first Infopanel looks similar to this (note that the Modified date and Page views may be slightly different):

The Article Details Control
Figure 1 - The Article Details User Control

This info panel shows a number of properties of the article, like my name, the article's QuickDocId and the number of times it has been viewed. Instead of hard coding the Infopanel in a page, it has been wrapped inside a User Control called ArticleDetails. This User Control exposes public properties that allows you to set the values for the article properties like the author's name. These properties are then used to build up the control. You'll see how this works later.

When you design such functionality for your own site, you may be tempted to create this panel in the main page directly. After all, the content for the panel is closely related to the main page you're working with. However, abstracting the functionality away to a separate User Control is a good idea for the following reasons:

  • It's much easier to reuse the code in a different page that has similar behavior.
  • It's easier to focus on the functionality of the control, because it's not mixed with the host page's content.
  • It's easier to change the appearance of the Infopanel; you only need to modify it at one location, instead of skimming through all the pages that display this Info panel.

In the next section, I'll walk you through creating the control and giving it some external properties that are used to fill the labels for QuickDocId, Written by and so on.

Creating the Control Outline

  1. You should start by adding a new User Control to your website. To do this, right-click your website in Visual Web Developer and choose Add New Item. In the dialog that follows, click Web User Control, name the control ArticleDetails in the Name box and click Add. It doesn't matter what language you choose. Both VB.NET as C# will work fine.

  2. Switch to Design View and add an HTML table with 6 rows and two columns.

  3. Add labels to the first column of the table and set their text according to the labels in Figure 1; e.g. Details, Quick Doc Id, Written by and so on.

  4. Add labels to rows 2 through 6 of the second column, and give them useful names like lblQuickDocId, lblWrittenBy and so on and clear their Text property. You should end up with something like this:

    The Article Details Control in Design View in Visual Web Developer
    Figure 2 - The Article Details User Control in Design View in VWD

The next step is adding public properties to the control, so the contents of these labels can be filled with the values of these properties. Since every public property needs a private backing variable, you should start off with creating those and then let the refactoring functionality of Visual Studio create the properties for you. (Note, Refactoring is only available for C# and only then in a limited form in Visual Web Developer. For VB.NET, you can use the free Refactor! from Developer Express. Don't worry if you don't have a refactoring menu in Visual Studio; you can always type the code manually.)

  1. To add the backing variables, switch to the Code View window of the ArticleDetails control and add the following highlighted lines to the class declaration:
    C#
    public partial class ArticleDetails : System.Web.UI.UserControl
    {
      private string quickDocId = String.Empty;
      private string writtenBy = String.Empty;
      private DateTime posted = DateTime.MinValue;
      private int pageViews = 0;
      private string listenedTo = String.Empty;
    

    protected void Page_Load(object sender, EventArgs e) { } }
    VB.NET
    Partial Class ArticleDetails
      Inherits System.Web.UI.UserControl
    	
      Private _quickDocId As String = String.Empty
      Private _writtenBy As String = String.Empty
      Private _posted As DateTime = DateTime.MinValue
      Private _pageViews As Integer = 0
      Private _listenedTo As String = String.Empty
    

    End Class
  2. Next, when you are using C#, click the variable quickDocId in the code editor and choose Encapsulate Field from the Refactor menu in Visual Studio. You'll be offered to create a property called QuickDocId which is exactly what you want, so you can click OK and then Apply. You'll end up with the following property code:
    private string quickDocId = String.Empty;
    public string QuickDocId
    {
      get { return quickDocId; }
      set { quickDocId = value; }
    }
    
  3. If you are using Visual Basic. you'll need to use the Refactor! plug in, or manually create the property. If you type the code manually, simply type the word property in the text editor and then press Tab. (Sometime you need to press it twice). Visual Studio will add the boiler plate code for a property for you so all you have to do is fill in the blanks. Whatever method you choose, you should end up with code like this:
    Private _quickDocId As String
    
    
    Public Property QuickDocId() As String
      Get
        Return _quickDocId
      End Get
      Set(ByVal value As String)
        _quickDocId = value
      End Set
    End Property
    
  4. Repeat steps two and three for each of the private fields. Make sure you create a DateTime and an int (Integer in Visual Basic) property for the Posted and PageViews properties respectively.

The final step you need to perform in the control is to assign the value of these properties to the labels in the markup at run-time. An ideal location for this is inside the Load event of the page. Add the following to the code behind of the page:

C#
protected void Page_Load(object sender, EventArgs e)
{

  lblQuickDocId.Text = quickDocId;
  lblWrittenBy.Text = writtenBy;
  lblPosted.Text = posted.ToString();
  lblPageViews.Text = pageViews.ToString();
  lblListenedTo.Text = listenedTo;

}

VB.NET
Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles Me.Load

	lblQuickDocId.Text = _quickDocId
	lblWrittenBy.Text = _writtenBy
	lblPosted.Text = _posted.ToString()
	lblPageViews.Text = _pageViews.ToString()
	lblListenedTo.Text = _listenedTo

End Sub

This code fills in the various labels defined in the markup when the control is loaded at run-time.

The last step in the process is to create an ASPX page that hosts the control and sets its properties.

  1. Add a new page to your web project and call it Article.aspx.
  2. Switch to Design View and then drag the control ArticleDetails.ascx from the Solution Explorer onto the page. You'll end up with something like this:

    The Article Detils Control Hosted in a Page
    Figure 3 - The User Control ArticleDetails on the host page

    With the User Control on the page, you have three different ways to influence the properties of the ArticleDetails control: directly in the HTML markup, through the control's Property Grid or programmatically in the code behind of the page. Figure 4 shows how the Code Editor is able to display Intelli Sense for the custom properties:

    Intelli Sense Popping Up for the ListenedTo Property of the User Control
    Figure 4 - Intelli Sense Popping Up for the ListenedTo Property of the User Control

    Figure 5 shows the Property Grid for the control, with a few properties set:

    The Properties Grid for the ArticleDetails Control
    Figure 5 - The Property Grid for the ArticleDetails Control

    You can see that the properties are listed under the Data category in the Property Grid. You can assign properties to existing categories (like Behavior and Data) or to new ones you invent yourself by adding a Category attribute to the property, like this:

    C#
    
    [ Category("Data") ] public string ListenedTo { get { return listenedTo; } set { listenedTo = value; } } VB.NET
    <Category("Data")> _ Public Property ListenedTo() As String Get Return _listenedTo End Get Set(ByVal value As String) _listenedTo = value End Set End Property
    To make this work, you'll need to add a using or Imports statement for the System.ComponentModel namespace.

     

  3. Instead of hard-coding the values in the markup of the page, you'll often find yourself setting these kind of properties programmatically at run-time. For instance, you could have a News business object that exposes the relevant properties. The following code snippet shows how you can set the properties at run-time:

    C#
    protected void Page_Load(object sender, EventArgs e)
    {
    ArticleDetails1.QuickDocId = "167"; ArticleDetails1.WrittenBy = "Imar Spaanjaars"; ArticleDetails1.Posted = new DateTime(2006, 7, 23); ArticleDetails1.PageViews = 231; ArticleDetails1.ListenedTo = "Open Your Arms by Editors (Track 10 from the album: The Back Room)";
    } VB.NET Protected Sub Page_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load ArticleDetails1.QuickDocId = "167" ArticleDetails1.WrittenBy = "Imar Spaanjaars" ArticleDetails1.Posted = new DateTime(2006, 7, 23) ArticleDetails1.PageViews = 231 ArticleDetails1.ListenedTo = "Open Your Arms " & _ "by Editors (Track 10 from the album: The Back Room)" End Sub

     

  4. When you now save the page and and view it in your browser by pressing Ctrl+F5, you'll see the Article page appear, with the proper values displayed in the ArticleDetails control:

    The Host Page Showing the Contenrts of the User Control
    Figure 6 - The ASPX Page with the Contents of the User Control

Summary

As you have seen, building User Controls that can be influenced at run-time is pretty easy. All you need to do is add a number of publicly accessible properties to the User Control in its Code Behind file. These properties then automatically become available in the page hosting the User Control so you can set them declaratively or programmatically.

Download Files

Source Code for this Article http://Imar.Spaanjaars.Com/Downloads/Articles/SmartUserControls/SmartUserControls1.zip

Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Wednesday, September 27, 2006 1:08:39 PM Daisy said:
This article is really helpful for the beginners.......But I couldn't understand certain things...
1>how do you get the properties u added in the
Partial Class ArticleDetails
  Inherits System.Web.UI.UserControl
reflected in the property box....I tried it in the ASP.net 2003 (1.1)...but didn't get it in fact by default it's giving  this
Public Class CompHeader
    Inherits System.Web.UI.UserControl
When I'm attempting to change the public to partial, its giving error.

2>I used an image control in my user control. I couldn't set the ImageUrl using the property method. How to do it? I did it this way
Dim UC As UserControl
Dim imgUC As System.Web.UI.WebControls.Image
UC = Page.FindControl("CompHeader1")
imgUC = UC.FindControl("Image1")
imgUC.ImageUrl = fn.Value
On Wednesday, September 27, 2006 6:22:28 PM Imar Spaanjaars said:
Hi Daisy,

Sorry about that; design time support in User Controls and partial classes are both .NET 2.0 features. This isn't mentioned in the article.

And, if this results in a valid Image:

imgUC = UC.FindControl("Image1")

then you need to cast it to a true Image before you can access its ImageUrl property. Before you do that, imgUC is seen as a generic control and not a true image:

imgUC = CType(UC.FindControl("Image1"), Image)

Sorry about the confusion. I'll try to update the article as soon as possible.

Cheers,

Imar
On Friday, September 29, 2006 4:08:20 AM Daisy said:
Thanx for the fast reply Imar.
I meant to say that the method I wrote worked. It's ok but I think it would be better to do the same work using the user control's property. Can you help me with this? I'm writing the properties but not being able to use it properly. Please help if you can. I used the same method as you used.
Declared the property in the class defination as
Public Class CompHeader
    Inherits System.Web.UI.UserControl
Private property _mImgUrl As string

Public Property _mImgUrl() As String
  Get
    Return _mImgUrl
  End Get
  Set(ByVal value As String)
    __mImgUrl = value
  End Set
End Property

Then in the page load of the user control I'm doing
Image1.ImageUrl = me._mImgUrl

After this I'm confused about how to use it?
On Saturday, September 30, 2006 8:01:32 PM Imar Spaanjaars said:
Hi Daisy,

Your property looks weird. Try changing it to something like this:

Private _mImgUrl As string
Public Property ImageUrl() As String
  Get
    Return _mImgUrl
  End Get
  Set(ByVal value As String)
    _mImgUrl = value
  End Set
End Property

Then you can do this to set the property:

Image1.ImageUrl = Me.ImageUrl

If that doesn't help, please post your code at the Wrox P2P forum (http://p2p.wrox.com) as it's a bit easier to post code there than on this site.

Cheers,

Imar
On Wednesday, October 11, 2006 8:51:20 AM mini said:
I have the same problem with Daisy as well, because i load my user control dynamically (programmatically), any body can help us? thanks a lot!
On Wednesday, October 11, 2006 5:37:51 PM Imar Spaanjaars said:
Hi mini,

If you want to load controls dynamically in ASP.NET 2, add the following reference to the page

[%@ Reference VirtualPath="~/MyCustomUserControl.ascx" %]

(Replace [ and ] with XML style brackets)

Then you can cast the  control returned from LoadControl to your custom type and access its properties.

Hope this helps,

Imar
On Thursday, October 12, 2006 1:57:31 AM Mini said:
Thanks a lot, it works.
On Thursday, October 12, 2006 6:56:31 AM mini said:
by the way, is there any other way else beside using the LoadControl? thanks.
On Thursday, October 12, 2006 8:24:58 PM Imar Spaanjaars said:
Hi mini,

No, I don't think so. LoadControl is made to load controls.

What are you trying to accomplish??

Imar
On Friday, October 13, 2006 1:23:30 AM mini said:
Actually LoadControl works fine for me, just afraid its too expensive because I need to load my web user control dynamically for quiet a lot of times.
On Tuesday, November 07, 2006 6:55:48 PM dan said:
I just copied the sorce code to .net 2003 by creating a user control first in the project and as before when trying to reference properties from user controls in the aspx page that refrences the user control page I got this compile time error "ArticleDetails1 could not be found (are you missing a using directive or assembly reference ?) "
Bellow is part of code to declare or reference, I removed the namespace ArticlaN (name pf project) after failing first time tobe similar to code on site but still the same error.

<%@ Control Language="C#" AutoEventWireup="true" Codebehind="ArticleDetails.ascx.cs" Inherits="ArticleDetails" %>

<%@ Register Src="ArticleDetails.ascx" TagName="ArticleDetails" TagPrefix="uc1" %>

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="ArticleN.WebForm1" %>

Please advice, the user controls are a nice feature
On Tuesday, November 07, 2006 7:33:19 PM Imar Spaanjaars said:
Hi dan,

Try dragging the control from the Solution Explorer onto your page in *design view*. This will add the necessary tags at the top of the page.

Cheers,

Imar
On Tuesday, November 07, 2006 8:01:01 PM dan said:
Thanks I did just that but the same error.

The only different thing is that I took out this code it was not compatible with .net 2003
[
    Category("Data")
  ]

I must miss something because I addded user controls and  I can see them but cannot access their public data from aspx page after registering them with the page. The code ArticleDetails1.WrittenBy = "Imar Spaanjaars"; is in the code behind file for  aspx page

Thank you
On Tuesday, November 07, 2006 8:09:23 PM Imar Spaanjaars said:
Hi dan,

When you say "cannot access their public data from aspx page", do you mean you expect to see an Intelli Sense dropdown with the properties for the control?

If so, this won't work. This feature was added in VS 2005. You can still set them though, even without the Intelli Sense list.

Imar
On Tuesday, November 07, 2006 8:34:13 PM dan said:
Yes I can not see them and I cannot compile the project because of that error, they are not accessible. It is not the first time I had this problem.
If loading them usr = (ArticleDetails)LoadControl("ArticleDetails.ascx");
then works.
But not if I use the id of the user control ArticleDetails1 in the aspx.cs file
On Tuesday, November 07, 2006 8:39:27 PM Imar Spaanjaars said:
Hi dan,

How does the <%@ Register tag look like at the top of the page?

Imar
On Tuesday, November 07, 2006 8:47:31 PM dan said:
%@ Register TagPrefix="uc1" TagName="ArticleDetails" Src="ArticleDetails.ascx" %>

then

uc1:ArticleDetails id="ArticleDetails1" runat="server"></uc1:ArticleDetails

I took out < from tag to be able to post the message
On Tuesday, November 07, 2006 8:49:49 PM Imar Spaanjaars said:
Here's what I did to make it work:

1. Create a brand new web application, like http://local/WebApplication1

2. Add a User Control.

3. Give this UC a public property like TestName.

4. Open the WebForm1.aspx page and switch to Design View

5. From the Solution Explorer, drag the control on the page. You'll end up with this:

[%@ Register TagPrefix="uc1" TagName="ArticleDetails" Src="ArticleDetails.ascx" %]

and in the page:

[uc1:ArticleDetails id=ArticleDetails1 runat="server"]

Change the UC tag to this:

[uc1:ArticleDetails id=ArticleDetails1 runat="server" TestName="Something"]

Compile and run. This should work.

I think you're having problems with the namespace or the names of the classes.

Imar
On Tuesday, November 07, 2006 9:06:06 PM dan said:
yes that way it worked, but not in the .cs file
On Tuesday, November 07, 2006 9:15:25 PM Imar Spaanjaars said:
Ah, I see what you mean now. I thought you were having troubles with accessing the property in the markup.

VS.NET 2003 doesn't add a control declaration for you (VS 2005 does, though), so you'll need to add one manually.

At the top of the class, add something like this:

    protected ArticleDetails ArticleDetails1;

This is similar to how other controls like Buttons are declared. Now you can access the properties like this:

ArticleDetails1.TestName = "SomeValue";

Hope this helps,

Imar
On Tuesday, November 07, 2006 9:20:30 PM dan said:
Thank you Imar !

Best wishes
On Monday, December 25, 2006 4:51:26 AM hiren andharia said:
i want to make my own user control property like
DateFormat property that display the combobox and in that combobox i will add the name and select the name

that's all
On Monday, December 25, 2006 2:17:57 PM Imar Spaanjaars said:
Hi hiren,

What is it that you're asking? How to do this?

Doesn't this article provide enough pointers to get started?

Cheers,

Imar
On Tuesday, May 01, 2007 8:09:44 PM Gary said:
Hi Imar.  Great article.  Do you have any suggestions to passing things other than text.  
For example, I want to pass a boolean to the UC so that I can programmatically set images to on or off, depending on how I use the Web Control.

My web control will show an image in some places and not in some places.

Thanks!
On Tuesday, May 01, 2007 8:13:01 PM Imar Spaanjaars said:
Hi Gary,

Sure, just expose the propery as a boolean:

Public Property ShowImage() As Boolean
  Get
    Return _showImage
  End Get
  Set(ByVal value As Boolean)
    _ShowImage = value
  End Set
End Property

Hope this helps,

Imar
On Thursday, May 10, 2007 7:20:38 PM Yousaid said:
Greetings,
I was looking at this. I have something similar, but mine uses a DB to store the article properties becuase unlike  your site where multiple users  submit articles.
I noticed you did not use a datasource and hardcoded it. Any reasons,
cheers,
yousaid
On Thursday, May 10, 2007 10:15:17 PM Imar Spaanjaars said:
Hi Yousaid,

Maybe you misunderstood, or I didn't explain it thoroughly enough.

THis article is about the actual control, not about where the data comes from. The hard coded values are merely to show how Design Time support works and what you get.

As you may be able to see from the URL, every page in my site is dynamic and they all come from the database. Not only me, but other people can log in and post articles as well. I have a full Admin section where you can enter all these details, and then some .NET code fills the User Controls with the relevant data at run-time.

Imar
On Thursday, May 10, 2007 11:30:40 PM yousaid said:
Ahhhh,  I see. That's exactely what I thought you did. Ie, you only explained the high level and left out the  Db  portion.
My fault. Ok, one question, what field are you using for the DocID field?
Do you have db field for that or the  PK of the article?
Cheers,
yousaid
On Saturday, May 12, 2007 8:19:48 AM Imar Spaanjaars said:
Hi yousaid,

Not sure what you mean with "field are you using for the DocID field?"

Firt of all, what does it matter? But secondly, this confuses me: "Do you have db field for that or the  PK of the article?"

Isn't the "PK" of an article a db field when discussed in relation to databases?

Imar
On Friday, January 04, 2008 12:53:34 PM Oscar said:
Hi, i liked the article, very very good, i had search for something like this. I was wondering if you could make a article with a usercontrol but with textbox fields, drop down list, that shows the result into a page, just the opposite of what you show here in the article.

Thank you.
On Saturday, January 05, 2008 12:18:29 AM Imar Spaanjaars said:
Hi Oscar,

I suppose I could. However, it would take a while as I am currently busy finishing my new book.

What you want is not so difficult though; just create properties on the UC that you access in the containing page. Instead of using a private backing variable (the _quickDocId variable for the public QuickDocId property), just return (and set) the Text property of a TextBox in your user control.

Hope this helps a little.

Imar
On Thursday, December 18, 2008 9:48:08 AM rahul said:
very nice article.
On Friday, January 23, 2009 9:25:42 AM OnaBluePlanet said:
Hey guys did anyone  find out about the performance issues, difference among
#1# Normal aspx pages postbacks and dataposting.
#2# UpdatePanel Postback
#3# aspx using ascx and accessing data.

I there anyone who has done this.

Let me place a example here
1.suppose we are using simple ProductList Display.
2. After clicking a product the it shud display ProductDetails, here u need a productID to pull the data on the details page..

What in this scenario will b the best performer among the solutions.

OnaBluePlanet
On Friday, January 23, 2009 11:53:35 AM Imar Spaanjaars said:
Hi there,

I don't think you'll see a (big) difference between 1 and 3. Controls and data need to be loaded anyway. Using controls may result in slightly more HTML though.

Re: 2: It depends on how you implement it. AJAX can be faster, but I have seen enough situations where it's slower. However, it may still be a good choice if users perceive it as faster which is often the case.

But: why don't you try it out and let us know? The only way to tell for sure is by measuring it....

Imar

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.