LoadControl a UserControl - *and* pass in Constructor Parameters

Posted on 6/30/2006 @ 8:45 PM in #Vanilla .NET by | Feedback | 33234 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. :-)

 

Sound off but keep it civil:

Older comments..


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


On 10/28/2008 7:29:09 PM Nikolay Marinov said ..
Sahil, great solution, thank you very much!!!


On 12/28/2008 8:04:01 PM Pierre Buckley said ..
Thank you for this solution. You are a genius my friend. I hope to one day have your skills with vb.net.


On 4/13/2009 1:32:40 PM Renu said ..
This code causes the page load of the user control to be fired twice. Is there any way this can be avoided?


On 4/30/2009 4:19:09 AM James West said ..
You absolutely are the man! This has worked an absolute treat for me!

Thank you


On 5/13/2009 3:25:16 PM Bill Martin said ..
Excellent solution. Very useful!


On 5/14/2009 3:41:03 PM Steve Lydford said ..
Great solution! Thanks very much.


On 7/22/2009 11:28:54 AM Jacen Solo said ..
Brilliant solution Sahil.


On 8/14/2009 10:34:34 AM Tony said ..
Thank you, sir. This is one of the rare times that I was able to find an explanation and code that specifically address an issue I was attempting to resolve.


On 9/29/2009 6:46:35 PM Dr Omm said ..
Thank you very much for this information. You save my life. I was looking for something like this for a long time ago. Please, still sharing you knowledge, you will go to the heaven...


On 4/29/2010 10:02:32 AM zmrcic said ..
I get Request for the permission of type 'System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed. on


constructor.Invoke(ctl, constructorParameters) line


On 5/29/2010 5:26:20 PM Laramie said ..
I am also receiving System.Security.Permissions.SecurityPermission error in medium trust.


On 6/30/2010 3:56:50 AM Isaac said ..
I get the error that UserControl is namespace , but it is used as type.


Can you please help me out?


On 7/5/2010 6:56:51 AM Ali said ..
Extremely helpful - THANKS!


On 7/13/2010 5:34:59 PM Amanda said ..
Huge help. Thanks so much for this.


On 8/4/2010 11:04:03 AM Zubair said ..
Oh... Malik Jee tusee Gr8 ho....:)

You really made my day.


Thanks...


On 2/16/2011 10:11:55 PM Lenin R said ..
The same concept can i use in the webservices method? if can then can youo please give me the sample. waiting for your reply. Actually i am using listview in the user control. when i click it's calling web service method and it's rendered the user control. can you please help me


On 2/22/2011 2:46:46 PM Yoann said ..
You posted this article on 6/30/2006, and almost five years later, you still rescue developers...

This was the solution I was searching for !

Thx !


On 5/4/2011 1:16:43 AM yusuf bin Hüseyin said ..
i think that your solution is great but there is simple way:


http://stackoverflow.com/questions/5871497/what-is-the-difference-loading-web-user-control-loading-on-page

also look. i really wonder why do you chooose this specific way. Your method not necessary...

MyControl instance = (MyControl)caller.LoadControl("~/Path/MyControl.ascx");

instance.member = parameter;


On 9/5/2012 3:29:25 PM Ashutosh said ..
Its a great article.Its really helpful in my project...