C# – Why are children of the custom user-control not being initialized

asp.netccontrolsnetuser-controls

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?

Best Answer

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.

Related Topic