It Could Be Done!

the blog about ASP.NET and not only

Master Page and PreInit

Emil Stoichev reminded today that it is very important to understand what is ViewState, how it works and how we should use it.  The other problem mentioned there is unavailability of controls in PreInit phase if MasterPage is used. 

The problem is very simple.  If you have the following content page (in master page),

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" Title="Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
    <p>Content</p>
    <p><asp:Label runat="server" ID="ContentPageLabel" /></p>
</
asp:Content>

<
script runat="server">
    protected override void OnPreInit(EventArgs e)
    {
        base.OnPreInit(e);
        // next line crashes
        ContentPageLabel.Text = "Hello, World!";
    }
</script>

it crashes on the label text assignment with:

Object reference not set to an instance of an object.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:

Line 11: base.OnPreInit(e); 
Line 12: // next line crashes
Line 13: ContentPageLabel.Text = "Hello, World!";
Line 14: }
Line 15: </script>

I had to solve this problem some time ago,and  I found that once you access the Master property of you content page for the first time all controls become  instantiated.

So, adding just a single line of code before label text assignment fixes the problem.

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
    <p>Content</p>
    <p><asp:Label runat="server" ID="ContentPageLabel" /></p>
</
asp:Content>

<
script runat="server">
    protected override void OnPreInit(EventArgs e)
    {
        base.OnPreInit(e);
        // the following line is important
        MasterPage master = this.Master;
        // unfortunately, compiler warns us that master is not used

        ContentPageLabel.Text = "Hello, World!";
    }
</script>

Note that everything works like without MasterPage with this small change. 

Posted: Oct 09 2007, 04:49 PM by ysw | with 15 comment(s)
Filed under:

Comments

Emil Stoichev said:

I've just tested your workaround and it works, but honestly I can't understand why. It doesn't actually loads the MasterPage which was my first thought. Can you explain why this does work?

# October 9, 2007 10:22 AM

ysw said:

Page.Master property getter triggers creation of the master page and in turn master page instantiates ITemplates generated for each <asp:Content> section.  I remeber I was looking into the Temporary ASP.NET Files folder to find out how pages are wrapped into master page.

# October 9, 2007 10:32 AM

Emil Stoichev said:

It looks like a cheap operation. I'll give it a try ;). Thanks.

# October 9, 2007 10:50 AM

Joe Chung said:

You could avoid this cruft entirely by accessing the control in the Init event instead of PreInit.  The PreInit event is a good place to do things like change master pages and themes in code, not access controls.

# October 9, 2007 11:23 AM

ysw said:

Joe, Emil suggested to use PreInit as this is the only way to set control properties before controls start tracking ViewState.  This way, it is possible to get smaller ViewState without disabling it.  So, if you DataBind your DropDowns in PreInit (depending on QueryString) you avoid placing their items in the ViewState.  

# October 9, 2007 11:30 AM

AndrewSeven said:

Unless there has been a change, it is Init, not PreInit.

I would guess that the property access triggers CreateChildControls().

Things that happen late (Render) are not tracked by viewstate either.

There must be a more elegant solution to this problem,  but if nothing else, you could use the more explicit:

protected override void OnLoad(EventArgs e)

   {

       base.OnLoad(e);

  ContentPageLabel.EnableViewState=false;

    ContentPageLabel.Text = "Hello, World!";

   }

# October 9, 2007 12:41 PM

ysw said:

Andrew, you are right, control does not trackview state at the time of Init event, but Page_Init fires the last one and at this time all controls already track viewstate.  That is why at the page lavel, PreInit is important.

# October 9, 2007 4:55 PM

bschooley said:

Just taking a guess here really, but did you try calling "EnsureChildControls()" during OnPreInit before setting the property?

# October 9, 2007 5:08 PM

Kylir Horton said:

Another good use of this is moving around AJAX controls within the page's control collection. They don't like to be moved after PreInit. That's what I'm using this trick for.

# December 21, 2007 9:48 AM

imiv said:

How avoid this problem if I use .Net 3.5? This solution does not work :-( Could you please help me?

# February 27, 2008 9:43 AM

ysw said:

What is the problem with .NET 3.5?  As .NET 3.5 is .NET 2.0 SP1 + new assemblies I do not see a reason why something must be different.

# February 27, 2008 10:25 AM

imiv said:

Thank you for answer. You are right in .NET 3.5 your solution works. The reason of my problem was that for master of the page I use nested master. And then I add line MasterPage master = this.Master I get the same error: Exception Details: System.NullReferenceException: Object reference not set to an instance of an object. But then I add line  MasterPage master = this.Master.Master, all work. Thank you very much for help.

# February 27, 2008 12:04 PM

NeedHelp said:

Unfortunately, this trick doesn't seem to work if the control you are trying to access is also inside an UpdatePanel. Any suggestions?

# March 22, 2008 8:50 PM

NH said:

Also finding that this does not work in .NET 3.5. However, one thing I've noticed is that if you type this.Master in the Immediate window of the IDE, it does have the desired effect. I guess the IDE invocation does a deeper prodding of the object.

# April 14, 2008 1:58 PM

NH said:

It does work under 3.5 as well (note that 3.5 uses the same System.Web). The trick, in my case, was that 3.5 has better support for nested master pages: if you have a nested master, make sure to iterrate all the way to the root before you can access child controls on PreInit (e.g. this.Master.Master).

# April 15, 2008 11:42 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)