Winsmarts.com

Microsoft MVP

MVP Logo

Awarded the Microsoft MVP Award.

Hosted By

blah!bLaH!BLOG!!

LoadControl a UserControl - *and* pass in Constructor Parameters

.. and you thought it couldn't be done ;-)

Posted on 6/30/2006 @ 8:45 PM in #Vanilla .NET | 22 comments | 10292 views

The inability of LoadControl to accept Constructor Parameters is a real pain in the donkey. This post tells you how to get around that hella annoying issue.

The awesome thing about ServerControls in ASP.NET is that you can instantiate them via a constructor, and hence offer a programming model similar to windows forms user controls. Unfortunately, you can't apply the same concept to UserControls because they are really mini pages within themselves. So you are limited to calling something like -

LoadControl("WebUserControl.ascx") ;

Wouldn't it be nice if you could instead do ...

LoadControl("WebUserControl.ascx",constructorparameter1, constructorparameter2, constructorparameter3 ...) ;

So say for instance, it would be nice if the following code would work -

Control toAdd = LoadControl("WebUserControl.ascx","Sahil Malik",5) ;
PlaceHolder1.Controls.Add(toAdd) ;

Note: There is a new overload new in .NET 2.0 which takes the signature LoadControl(Type,object[]), which is very similar to the above. But it is frequently problematic in ASP.NET 2.0 to refer to a user control in a strongly typed manner.

Well, here is how you'd make it work.

1. First of all write the user control (Duhh!!). My user control has a label on it called Label1, and it has the following code-behind.

public partial class WebUserControl : System.Web.UI.UserControl
{
    public WebUserControl()
    {
    }

    public WebUserControl(string labelText, int howManyTimes)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder() ;
        for (int i = 1; i <= howManyTimes; i++)
        {
            sb.Append(labelText) ;
            sb.Append("<br>") ;
        }
        Label1.Text = sb.ToString() ;
    }
}

As you can see, I am simply appending the same text over and over again. Note that it is important to explicitly create a default public constructor (i.e. one without parameters) or you'd confuse the ASP.NET runtime.

2. Step #2 is to add the following private method to your default.aspx, or add it as a protected method to the base class of all your pages, or a static method that can somehow access your page object. In my case I kept things simple and added a private method to my default.aspx.

private UserControl LoadControl(string UserControlPath, params object[] constructorParameters)
{       
    List<Type> constParamTypes = new List<Type>() ;
    foreach (object constParam in constructorParameters)
    {
        constParamTypes.Add(constParam.GetType()) ;
    }

    UserControl ctl = Page.LoadControl(UserControlPath) as UserControl;

    // Find the relevant constructor
    ConstructorInfo constructor = ctl.GetType().BaseType.GetConstructor(constParamTypes.ToArray()) ;

    //And then call the relevant constructor
    if (constructor == null)
    {
        throw new MemberAccessException("The requested constructor was not found on : " + ctl.GetType().BaseType.ToString()) ;
    }
    else
    {
        constructor.Invoke(ctl,constructorParameters) ;
    }

    // Finally return the fully initialized UC
    return ctl;
}

3. And finally, add the following to the Page_Load (or any other suitable place) of your default.aspx -

protected void Page_Load(object sender, EventArgs e)
{
    Control toAdd = LoadControl("WebUserControl.ascx","Sahil Malik",5) ;
    PlaceHolder1.Controls.Add(toAdd) ;
}

Bingo, build the website and run, and here is what you see -

So there ya go, now you can call a UserControl pretty much like a ServerControl. Ok good, another major headache solved. :-)

 

On 9/3/2006 6:31:01 PM Netflash said ..
Great article! Since you know so weel hoe to work with UserControls, can you please give me some hints on how can i use embed images in a resource file of a UserControl that is compiled into an assembly. I can get text from the resource file but not images. With server control works well but with UC it doesn't seem to work. Thank you.

On 9/3/2006 9:57:14 PM Sahil Malik said ..
Well you can make it work using the System.Globalization namespace.

On 9/4/2006 9:08:43 AM Netflash said ..
I'm sorry, but can you explain what your idea is? Thank you.

On 9/4/2006 3:27:41 PM Sahil Malik said ..
Hmm .. (trying very hard to be polite) .. I'd love to break it down more, but then I'd be doin' your work eh? :) Anyway, I'm gonna put this in my "will blog" queue and get to it when I can. Please don't mind my being busy. :)

On 9/8/2006 1:29:59 PM Netflash said ..
I don't know where that hostility came from. I just asked for some help. I didn't ask you to e-mail me a zip with the code done. You must admit that mentioning System.Globalization has the solution, it's kind of vague. If you don't have the time i understand but it would be enough to say that. Thank you anyway.

On 9/9/2006 11:00:38 AM Sahil Malik said ..
Hey Netflash - There is no hostility :). This is a constant challenge I face, and I don't have a solution to. (Please read: http://blah.winsmarts.com/Post.aspx?postID=67). Yes I admit that mentioning Sys.g is vague, but I am also certain that a solution can be found there. Then it just boils down to spending time hitting that wall and figuring it out. At that point, I am not no advantage compared to you - except, I am doing your work for you. (And you = proverbial you, i.e. many others like you that contact me). Yes I don't have the time, but I do want to help. I think my only answer is to stop the urge to reply to every comment I get on my blog. Maybe some other person in future will come by and answer the very same Q on the same post? If you have any suggestions to help this picture, I'm all ears - and there is no hostility.

On 9/21/2006 9:51:38 AM Per Magne Bjornerud said ..
I've been using this approach: One static method in the UserControl functioning as a surrogate constructor. Taking the calling object as a paramter, as this seems to be needed to load it properly. public static MyControl ConstructFor(TemplateControl caller, object parameter) {
MyControl instance = (MyControl)caller.LoadControl("~/Path/MyControl.ascx");
instance.member = parameter;
return instance;
} --
The the caller object would simply do this: MyControl control1 = MyControl.ConstructFor(this);

On 1/6/2007 8:25:04 PM Fritz Schenk said ..
Thoroughly elegant!

On 1/29/2007 3:27:02 AM Steve said ..
Hi malik Great solution. As I prefer coding in VB, how do i translate the line: - List<Type> constParamTypes = new List<Type>() ; Thanks in advance

On 5/12/2007 8:48:00 PM Magni Hansen said ..
Translated into vb Dim constParamTypes As New List(Of Type)()

On 6/4/2007 3:09:30 AM Pradeep said ..
HOw to create user control in asp.net

On 7/16/2007 11:24:40 AM teatime said ..
Per Magne Bjornerud: most impressive, saved me a whole world of pain.

On 9/6/2007 4:38:32 PM Timothy J Bridgemen said ..
Sahil, I'm just starting a project that involves creating user controls that I dynamically add to the page at run time that the user can modify. With the aid of this post I was able to initialize the controls with values after converting the code to VB.Net. What I'm missing is how do I get the values back out. Any help you can give would be appreciated. Let me know VIA my Email when you answer. Thank-you,
Tim

On 9/22/2007 6:07:24 PM Raj said ..
Any idea why I get this exception: The requested constructor was not found on : " + ctl.GetType().BaseType.ToString()) Thank you,
Raj

On 11/27/2007 9:55:03 AM Barret said ..
Great article! Exactly what I was looking for -- Thanks!

On 12/4/2007 5:39:17 AM halby said ..
awesome mate!!!! i was looking for this code for couple of days!!!! thanks a lot

On 1/17/2008 7:34:58 AM Bob Smith said ..
Fantastic Article, Saved me ages!

On 5/4/2008 6:20:42 AM Kevin said ..
Could anyone help me to translate LoadControl functioin to VB ? I have tried it but failed. Really thanks a lot.

On 5/14/2008 8:44:42 PM Ray said ..
I am wondering of the WebUserControl.ascx is being loaded (executed) 2 times because of the code: Page.LoadControl(UserControlPath) as UserControl; and constructor.Invoke(ctl, constructorParameters) Is that true ?

On 6/10/2008 7:37:01 AM Ken said ..
Here is a dirty but working aproge: Loading control: Control _Control = LoadControl("PageItem.ascx");
_Control.SkinID = "1";
MyPage.Controls.Add(_Control); And in the control just us (in Page_Load): Label1.Text = this.SkinID;

On 9/10/2008 5:01:23 AM Mark Morgan said ..
Thanks for this article, it's helped me a lot, what I need to do is have a piece of UI and duplicate it onto multiple dynamically created tabs (using ASP.NET Ajax Toolkit), this technique I think will help me achieve that. For the vb.net developers out there I've converted the c# and got it working (not a lot of work on my part, but hopefully useful), again kudos to Sahil! User Control in /Controls folder WebUserControl.ascx <%@ Control Language="VB" AutoEventWireup="false" CodeFile="WebUserControl.ascx.vb" Inherits="Controls_WebUserControl" %>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label> WebUserControl.ascx.vb Partial Class Controls_WebUserControl
Inherits System.Web.UI.UserControl Public Sub New()
End Sub
Public Sub New(ByVal labelText As String, ByVal howManyTimes As Integer)
Dim sb As New System.Text.StringBuilder()
For i As Integer = 1 To howManyTimes
sb.Append(labelText)
sb.Append("<br>")
Next
Label1.Text = sb.ToString()
End Sub End Class UCTest.aspx page : <%@ Page Language="VB" AutoEventWireup="false" CodeFile="UCTest.aspx.vb" Inherits="UCTest" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">; <html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder> </div>
</form>
</body>
</html> UCTest.aspx.vb Imports System.Collections.Generic
Partial Class UCTest
Inherits System.Web.UI.Page Private Overloads Function LoadControl(ByVal UserControlPath As String, ByVal ParamArray constructorParameters As Object()) As UserControl
Dim constParamTypes As New List(Of Type)()
For Each constParam As Object In constructorParameters
constParamTypes.Add(constParam.[GetType]())
Next Dim ctl As UserControl = TryCast(Page.LoadControl(UserControlPath), UserControl) ' Find the relevant constructor
Dim constructor As Reflection.ConstructorInfo = ctl.[GetType]().BaseType.GetConstructor(constParamTypes.ToArray()) 'And then call the relevant constructor
If constructor Is Nothing Then
Throw New MemberAccessException("The requested constructor was not found on : " + ctl.[GetType]().BaseType.ToString())
Else
constructor.Invoke(ctl, constructorParameters)
End If ' Finally return the fully initialized UC
Return ctl
End Function Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
Dim toAdd As Control = LoadControl("~/Controls/WebUserControl.ascx", "Sahil Malik", 5)
PlaceHolder1.Controls.Add(toAdd)
End Sub
End Class

On 9/10/2008 10:23:47 AM Sahil Malik said ..
Thanks Mark. It is because people sometimes find what I write useful .. that I keep doing it. And thanks for contributing here. Sahil

Please post your comments:


Your feedback will be submitted for moderation, and will appear after it is approved.

Name:  
Email (optional): Your email address will not be posted.
URL (optional):
Comments: HTML will be ignored, URLs will be converted to hyperlinks  
Enter the text you see in the box:
 

Site designed and maintained by Sahil Malik | All Rights Reserved. ©2007 WinSmarts.com.