views:

1152

answers:

4

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?

A: 

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

jhunter
I'm setting the HRef in the Page's markup, not the OnInit method. I know this happens before the Page's OnPreInit() event. I'd like to be able to use the markup to set control values if possible since it means not having to recompile to make changes.
Dan Herbert
A: 

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?

Nathan Southerland
A: 

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;
Andomar
+2  A: 

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"&gt;

<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<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.

Ashraf Sabry