5,693,062 members and growing! (16,143 online)
Email Password   helpLost your password?
Web Development » ASP.NET » General     Intermediate

Strongly typed LoadControl in ASP.net

By HightechRider

A cleaner more OO-way to load web user controls in ASP.net
C#, Windows, .NET, Visual Studio, ASP.NET, WebForms, Dev

Posted: 1 Aug 2007
Updated: 1 Aug 2007
Views: 11,406
Bookmarked: 19 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
12 votes for this Article.
Popularity: 4.36 Rating: 4.04 out of 5
2 votes, 16.7%
1
1 vote, 8.3%
2
1 vote, 8.3%
3
2 votes, 16.7%
4
6 votes, 50.0%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Introduction

When it comes to loading user controls in ASP.net your 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 :-

    WebUserControl1 newUserControl = (WebUserControl1)LoadControl("WebUserControl1.ascx"); 
    newUserControl.Item = item;
    newUserControl.ItemFormat = itemFormat;
    this.PlaceHolder.Add(newUserControl); 
. Here's an example from another article on code project ...
            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 show 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 leadng 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.

        /// <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 a 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' any more, it's a tangled mess that will be hard to reuse and hard to maintain. So don't do that!

So, keeping 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 ...

        /// <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:

        // 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; bugs 'fixed' 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 the duplication and to spend the time to create a reuseable 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 1st 2007, First version.
August 4th 2007, Fixed a couple of typos.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

HightechRider


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

Other popular ASP.NET articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 6 of 6 (Total in Forum: 6) (Refresh)FirstPrevNext
GeneralHow to call the static methodmembermiies11:49 5 Sep '07  
GeneralRe: How to call the static methodmembermiies12:00 5 Sep '07  
GeneralRe: How to call the static methodmemberHightechRider14:49 5 Sep '07  
QuestionHow to add such a control in declarative?membertobias42122:58 6 Aug '07  
GeneralUseful Article! Typo?membersbearman20:17 4 Aug '07  
GeneralRe: Useful Article! Typo?memberHightechRider21:29 4 Aug '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 1 Aug 2007
Editor:
Copyright 2007 by HightechRider
Everything else Copyright © CodeProject, 1999-2008
Web08 | Advertise on the Code Project