![]() |
Web Development »
ASP.NET »
General
Intermediate
Strongly typed LoadControl in ASP.netBy HightechRiderA cleaner more OO-way to load web user controls in ASP.net |
C#, Windows, .NET, ASP.NET, Visual Studio, WebForms, Dev
|
||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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 ...
Code such as that show in the introduction has several issues:
./controls/SiteHeader.ascx are often scattered throughout the solution making it hard to move a control to a different folder or to rename it.Item required by your control.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.
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!
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); }
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.
August 1st 2007, First version.
August 4th 2007, Fixed a couple of typos.
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 1 Aug 2007 Editor: |
Copyright 2007 by HightechRider Everything else Copyright © CodeProject, 1999-2009 Web21 | Advertise on the Code Project |