Reputation: 103387
UPDATE: I've been experimenting with my controls some more and I think I've gotten closer. Please read on for updated info.
I have 2 ASP.NET 2.0 user controls, one of which is placed inside of the other one. Inside of the inner control I have an HtmlAnchor
tag control which I'm trying to set a URL for from the outer control. When I attempt to set the HRef
property from the rendering page's markup, the HtmlAnchor
control is null
and throws an exception.
The URL set
property is being called before the OnInit event of either the inner or the outer control and before the main Page's 'OnPreInit
' event. I'm assuming this is because I am setting the URL of the child control in the markup of the parent control on the page I'm rendering the controls on which means the value is set before OnInit()
. Here is the (stripped down) version of the code I'm using:
[ParseChildren(true)]// <- Setting this to false makes the
// page render without an exception
// but ignores anything in the markup.
[PersistChildren(false)]
public partial class OuterControl : System.Web.UI.UserControl
{
// The private '_Inner' control is declared
// in the .ascx markup of the control
[PersistenceMode(PersistenceMode.InnerProperty)]
public InnerControl Inner
{
get{ return _Inner; }
set{ _Inner = value; }
}
}
public partial class InnerControl : System.Web.UI.UserControl
{
// The private 'linkHref' control is declared
// in the .ascx markup of the control
public string Url
{
get { return linkHref.HRef; }
set { linkHref.HRef = value; }
}
}
The OuterControl is used on my Default.aspx page like this:
<uc1:OuterControl ID="OuterCtrl1" runat="server">
<Inner Url="#" />
</uc1:OuterControl>
In the markup example above, if I try to render this page an exception gets thrown because the linkHref
control is null. Using my debugger I can see that every control within the InnerControl is null, but both the InnerControl & OuterControl's OnInit()
event has not been triggered yet when the Url
property is accessed.
UPDATE
I thought adding the attributes 'ParseChildren
' and 'PersistChildren
' would help. I've used them in Server Controls before but never in User Controls, although the effect seems to be similar. I don't think I'm interpreting the documentation for these two properties correctly, but it can stop exceptions from being thrown. The page markup becomes ignored though.
Does anyone know a way to have this work. I don't understand why these controls are getting values set before OnInit()
. When I try to set their values using the ASPX markup, the constructor for the InnerControl
is being called twice. Once to set the values based on the markup (I'm assuming) and again on OnInit()
(which I'm guessing is why the markup values are getting ignored).
Is this effort hopeless or am I just approaching it from the wrong angle?
Upvotes: 5
Views: 4810
Reputation: 55349
If OuterControl.ascx looks like
<%@ Register TagPrefix="uc1" TagName="InnerControl" Src="InnerControl.ascx" %>
<uc1:InnerControl runat="server" ID="_Inner" />
then the problem is that the OuterControl.Inner
property has a set
accessor. Delete the set
accessor to fix the problem:
[PersistenceMode(PersistenceMode.InnerProperty)]
public InnerControl Inner
{
get { return _Inner; }
}
Explanation: If OuterControl.Inner
has a set
accessor...
ASP.outercontrol_ascx
(the dynamically generated class that derives from OuterControl
,) which in turn instantiates an instance of ASP.innercontrol_ascx
(the dynamically generated class that derives from InnerControl
).Inner
property has a set
accessor, ASP.NET creates a new instance of InnerControl
(not ASP.innercontrol_ascx
, as you might expect) and attempts to initialize its Url
property to "#"
. Of course, this throws NullReferenceException
because linkHref
is created by ASP.innercontrol_ascx
, not by InnerControl
.Inner
to the instance of InnerControl
it created in step 2.If OuterControl.Inner
doesn't have a set
accessor, then in step 2, ASP.NET simply sets OuterCtrl1.Inner.Url
to "#"
, and step 3 is skipped.
Note: A couple other answers state that controls are created or initialized in Init
. This is incorrect; controls declared in markup are created and initialized very early in the page life cycle, in FrameworkInitialize
(which occurs before PreInit
).
Upvotes: 1
Reputation: 3182
Greetings Dan,
I faced a similar problem before with the mess of nested controls, so I found myself eager to help, and I marked this as favourite too because I liked it.
I reproduced the problem as follows:-
Created a user control called Inner:
Inner.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Inner.ascx.cs" Inherits="Inner" %>
<a runat="server" id="linkHref">I'm inner</a>
Inner.ascx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class Inner : System.Web.UI.UserControl
{
public string Url
{
get
{
return this.linkHref.HRef;
}
set
{
this.linkHref.HRef = value;
}
}
}
Created a user control called Outer:
Outer.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Outer.ascx.cs" Inherits="Outer" %>
<%@ Register src="Inner.ascx" tagname="Inner" tagprefix="uc1" %>
<uc1:Inner ID="_Inner" runat="server" />
Outer.ascx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class Outer : System.Web.UI.UserControl
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public Inner Inner
{
get
{
return this._Inner;
}
}
}
Then created a page called Default:
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register src="Outer.ascx" tagname="Outer" tagprefix="uc1" %>
<%@ Reference Control="~/Inner.ascx" %>
<!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></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<uc1:Outer ID="Outer1" runat="server">
<Inner Url="http://google.com" />
</uc1:Outer>
</div>
</form>
</body>
</html>
With all these, the page "default" runs well.
What I find strange in your code is this line:
public InnerControl Inner
{
//...
set{ _Inner = value; }
}
What's the point of creating a setter for the inner control? I can't understand it. the inner control instance is supposed to be created from the markup in the outer control, and it's this created instance whose html anchor should be manipulated. If I add these lines to the Inner property in the Outer.ascx.cs
set
{
this._Inner = (ASP.inner_ascx)value;
}
I'll get a null reference exception as in the original case. I think that the setter instructs ASP.Net page builder to create another Inner control instance and set it using the Inner property. I'm not sure, but if you have time, you can know exactly how this happens by examining the cs files generated by the page builder, they reside in the temporary ASP.Net files.
Upvotes: 2
Reputation: 238068
The control tree is built after Init(); in fact, Init() is the place to add your controls to the tree, before the viewstate is deserialized. You can't access linkHref before the control tree has been built.
One possible solution is to store the Url until you can use it. Inside the inner control, change the Url property to a simple string:
public string Url { get; set; }
In the Load or PreRender event handler of the inner control, propagate the string to the (now initialized) linkHref object:
linkHref.HRef = value;
Upvotes: 0
Reputation: 126
You said:
// The private 'linkHref' control is declared // in the .ascx markup of the control
Does it change anything if you make this a protected control?
Upvotes: 0
Reputation: 1882
Are you setting the HRef on the Page's OnInit method? If so try moving the assignment out to Page_Load.
The controls Init from the outermost to the inner most. This means if you do assign the value on the Page's OnInit the controls haven't initialized yet.
Here is a decent document on page lifecycle: http://www.codeproject.com/KB/aspnet/lifecycle.aspx
Upvotes: 0