Click here to Skip to main content
15,886,199 members
Articles / Web Development / ASP.NET

Strongly-typed LoadControl in ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.35/5 (14 votes)
1 Aug 2007CPOL5 min read 42.2K   24   6
A cleaner, more OO-way to load Web User Controls in ASP.NET.

Introduction

When it comes to loading user controls in ASP.NET, you are forced to do several steps that will make any OO-purist cringe. First, you have to call LoadControl, passing it the file path to the .ASCX file you want to load. Next, you have to cast that control to the type of control that it is. Then, you have to figure out which parameters are optional and which are required, and have to set each of them individually. This leads to code that looks like this:

C#
WebUserControl1 newUserControl = (WebUserControl1)LoadControl("WebUserControl1.ascx"); 
newUserControl.Item = item;
newUserControl.ItemFormat = itemFormat;
this.PlaceHolder.Add(newUserControl);

Here's an example from another article on CodeProject ...

C#
hdrCtl = LoadControl("./controls/SiteHeader.ascx");
if (hdrCtl != null)
{
    ((SiteHeader)hdrCtl).LeftLogoImgPath = "..\\images\\ps_logo.gif";
    ((SiteHeader)hdrCtl).RightLogoImgPath = "..\\images\\ps_name.gif";
    HeaderCtl.Controls.Add(hdrCtl);
}

There has to be a better way to do this, and in this article, I'll show you one way to improve on the situation ...

Background

Code such as that shown in the introduction has several issues:

  1. References to ./controls/SiteHeader.ascx are often scattered throughout the solution, making it hard to move a control to a different folder or to rename it.
  2. Since there is no constructor with a required set of parameters, you can end up with partially instantiated controls, leading to run-time errors when someone forgets to set the Item required by your control.
  3. It is hard to discover (using the IDE) what the required parameters are for the control.

Using the code

The solution to these issues is to create a strongly-typed, static factory method on each of your control classes that returns an instance of the control with all of the required parameters set on it. If there is more than one way to instantiate the control, then you can provide multiple static methods on your control class.

C#
/// <summary>
/// Factory method to create a ItemView control with an Item and an ItemFormat
/// </summary>
public static ItemView LoadThisControl (Page page, Item item, Itemformat itemFormat)
{
    ItemView result = (ItemView)page.LoadControl("~/controls/itemview.ascx");
    result.Item = item;
    result.ItemFormat = itemFormat;
    return result;
}

/// <summary>
/// Factory method to create a ItemView control
/// with just an Item, uses the default ItemFormat
/// </summary>
public static ItemView LoadThisControl (Page page, Item item)
{
    return LoadThisControl (page, item, Itemformat.Default)
}

With these static factory methods in place, you can now discover (using the IDE) how to load an ItemView control. Intellisense will show you that there are two calls you can make and that you must pass in an Item object. If you stick to using these methods, you will always get a properly instantiated control, and when you make changes to the required parameters, the compiler will point out all the places you need to go fix up.

Furthermore, the 'known' location of your control is now defined in just one place in your code, making it easy to relocate it.

A more complex factory

In your domain layer, you will often have a base class and then a set of derived classes; e.g., our base domain object might be an Item, and we have ItemImage and ItemVideo derived from it. When it comes time to load a control to display these items, you might need to load an ItemImageView or an ItemVideoView control, depending on the type of the item you want to display.

This leads to code with switch statements in it examining the type of object to decide which control to load, and as an OO-purist will tell you, switch statements nearly always mean that you aren't writing OO-code. There's only one thing worse than a switch statement, and that's a duplicated switch statement! So, if you find yourself writing the same switch statement multiple times to load the appropriate control, you know you are doing something wrong.

One approach would be to ask the domain object for the control to display itself, so you might have item.LoadViewControl which would return you the appropriate ItemView control with the Item already set on it. The problem with this approach is that now your domain layer knows about your UI layer, and that's bad news; in fact, it's not a 'layer' anymore, it's a tangled mess that will be hard to reuse and hard to maintain. So, don't do that!

So, keeping the knowledge of the UI layer in the UI layer itself, a compromise approach is to have a factory method on your base UI control. Let's call it ItemView.LoadAppropriateControl(Item item), and have just one switch statement inside that factory method that looks at the Item type, calls the appropriate factory method to get a control for that Item, and then returns the control.

This would look something like ...

C#
/// <summary>
/// Factory method to create the right kind
/// of ItemView control based on the Item passed to it
/// </summary>
public static ItemView LoadAppropriateControl (Page page, Item item)
{
    if (item is ItemImage)
      return ItemImageView.LoadThisControl (page, (ItemImage)item);
    else if (item is ItemVideo)
        return ItemVideoView.LoadThisControl (page, (ItemVideo)item);
    else
      throw new ArgumentException("ItemView only knows how " + 
            "to make controls for ItemImage and ItemVideo types");
}

With this approach, you now have one place to go define how to load a control with the correct parameters, and one place to go define how to map an object derived from Item onto the appropriate control.

Of course, you may well like this approach so much that you add other factory methods to the base ItemView to load the various types of control you need that all take an Item as a parameter. Perhaps, one to create a PanelView of the Item, another to create an IconView of the Item, and a third to create a ListEntryView of the Item.

Outside of this factory method, your code is now much easier to read:

C#
// Display a list of items in the panel
foreach (Item item in this.Items)
{
   Control c = ItemView.LoadAppropriateListEntryView (item);
   this.placeHolderList.Controls.Add (c);
}

// And now create the icon view on the right hand side
foreach (Item item in this.Items)
{
   Control c = ItemView.LoadAppropriateIconView (item);
   this.placeHolderIcons.Controls.Add (c);
}

Points of interest

Duplicate code is always a bad idea: it leads to code bloat; it causes bugs to appear in some places, but not others; 'fixed' bugs mysteriously reappear in other places because the developer didn't go find all copies of that code when he or she fixed a bug that had been found in one of them.

"One-fact, one-place" is a great mantra not just for database developers, everyone should aim for it. It you are ever tempted to use "cut-and-paste coding" because it's 'quicker', just remember that 2x the code will produce 2x as many bugs and will be 2x as hard to maintain. It is always better to avoid duplication and to spend time creating a reusable method that puts "one fact" in "one place". Every sprint cycle should include time for this kind of refactoring to ensure that your solution stays clean and maintainable.

The solution proposed here isn't perfect, but it's a lot better than seeing all those duplicate .ascx file references in your code and not knowing until run-time whether every control was instantiated properly.

History

  • August 1, 2007: First version.
  • August 4, 2007: Fixed a couple of typos.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I have been writing code and managing teams of developers for more years than I care to remember.

Comments and Discussions

 
QuestionHow to call the static method Pin
miies5-Sep-07 10:49
miies5-Sep-07 10:49 
AnswerRe: How to call the static method Pin
miies5-Sep-07 11:00
miies5-Sep-07 11:00 
AnswerRe: How to call the static method Pin
HightechRider5-Sep-07 13:49
HightechRider5-Sep-07 13:49 
QuestionHow to add such a control in declarative? Pin
toebens6-Aug-07 21:58
toebens6-Aug-07 21:58 
QuestionUseful Article! Typo? Pin
sbearman4-Aug-07 19:17
sbearman4-Aug-07 19:17 
AnswerRe: Useful Article! Typo? Pin
HightechRider4-Aug-07 20:29
HightechRider4-Aug-07 20:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.