2.09.2006

ViewState: Misunderstood monster or fluffy sleeping kitten?

The former!

Let me explain something to guys. ViewState is more complicated than you think it is. That's why you all abuse it so much :) Let me demonstrate:

public class MyControl : Control
{
public string Text
{
get { return (string) this.ViewState["Text"]; }
set { this.ViewState["Text"] = value; }
}

protected override void OnLoad(object sender, EventArgs args)
{
if(!Page.IsPostBack)
{
this.Text = "Hello World!";
}
}

public override void Render(HtmlTextWriter writer)
{
writer.WriteLine(this.Text);
}
}

What is WRONG with this picture?

Hmmmmmmm???

No.. that's not it.

You don't know?

I told you that's not it.

Give up?

That's what I thought. Sorry for my rudeness but it seems like no one on the planet knows why this is wrong. I have to deal with it every day as a lead programmer. But hey, I don't blame you. I've done this sort of thing myself. It looks perfectly harmless. You want the default value of "Text" to be "Hello World!". And why wouldn't you anyway, "Hello World!" has been serving us programmers since 1812. What better way to honor its 194th birthday than to make it the default value on your Text property?

No problem. Let it shine I say! But that's not what is wrong.

What's wrong is HOW you make it the default value. You know that HTTP is a connectionless protocol right? Well yeah, that's why Session state and ViewState were invented. To help you maintain data across those stateless requests. Brilliant, it really is! "Hello World" has never been so happy.

So let me ask you... What is this line of code for?

if(!Page.IsPostBack)

I know. It's so you don't do unnecessary work, right?

ViewState will be so kind as to save the value for you. Therefore, you'd be wasting CPU cycles if you had to set it on every request, right? Sure. What's that you say? Oh... You're also worried about overwriting the value in case someone has changed it? I see. Well that's pretty smart... I mean, afterall, ViewState is loaded before OnLoad. We wouldn't want to overwrite any precious changes to that value. Very good! Now wait just a second here...

You were on the right track, but you failed to understand the REAL problem here. The problem isn't that you would be wasting CPU cycles if you reassigned the value on every request...

Oh no. The problem is much greater than that.

The problem is that you are completely unaware of the purpose of ViewState. Did I say that already? Sorry, really I am...

Here... There's no better way than to show you.
public class MyControl : Control
{
public string Text
{
get { return this.ViewState["Text"] == null ?
"Hello World" :
(string) this.ViewState["Text"]; }
set { this.ViewState["Text"] = value; }
}

public override void Render(HtmlTextWriter writer)
{
writer.WriteLine(this.Text);
}
}

What have we got here. Hmm... No OnLoad anymore.
"Hello World" has moved up into the property. If the value in ViewState is NULL we return it. That means, dun dun dun! We have a default value! And we didn't have to set it, its just THERE.

Ok... so big deal. Who cares?

I'll tell you who cares... and its not a fluffy sleeping kitten.

In the 2nd control, the value "Hello World!" is not persisted into the hidden form field "_VIEWSTATE". In the first control, it is.

Its a very... subtle... difference. But its VERY IMPORTANT!!!

I have seen controls written the "bad" way that contained dozens of properties. The result is, when someone puts this nasty control on a page, even BEFORE THE FIRST POSTBACK, their viewstate is HUGELY BLOATED with crap. If you do it the 2nd way, its just as big as if you had ZERO properties. If some unlucky Joe Programmer uses your control within a DataGrid or repeater, they'll be taking a hit once for every data item they bind.

VIEWSTATE IS NOT TO BE TAKEN LIGHTLY :)

Here's the rule of thumb that you must live and die by: VIEWSTATE IS FOR TRACKING CHANGES TO THE FORM'S STATE. Thats CHANGES. It is NOT meant to store the default values of your properties. Why on earth should it store default values? THEY'RE DEFAULT VALUES for crying out loud. Don't do it! :)

I feel much better now. Thank you! :)

7 Comments:

Anonymous Anonymous said...

Hi Dave,
Another way to set default properties is to just set them on or prior to the init event (rather than on load). That way the ViewState statebag won't actually persist them in viewstate (i.e. they won't be marked dirty when set).

This keeps your getters and setters nice and simple, and allows you to set the defaults in one place. (You can also use attributes on the properties themselves)

February 15, 2006 3:31 PM  
Blogger Infinity88 said...

Dave T... that is a good tip, but it still has problems.

1. If a user of your control specifies values for those properties declaratively, and you set defaults in OnInit you will be overwriting those values! You are effectively removing the ability for users of your control to specify the properties declaratively. Declarative attributes are assigned to the control even before OnInit occurs. So if you use this trick be sure and do it within your constructor.

2. You are correct that the values you set won't be persisted in viewstate. ViewState is not "tracking" yet in the OnInit phase. However, another one of those mundane yet crucial issues, is that while its true that your OWN viewstate is not being tracked yet, your CHILD CONTROLS are! So you cannot set default values to your child controls in this manner without incurring a viewstate penalty. In fact, the only way to set default values to child controls is to set them declaratively (if writing a user control), set them before adding them to the control collection (if you are creating them dynamically), or override the protected AddedControl method (yuck). ASP.NET 2.0 provides the OnPreInit event which I thought was seriously lacking in 1.x, and you can set them safely in there -- HOWEVER THE ONPREINIT EVENT IS ONLY AVAILABLE AT THE PAGE LEVEL! So it's useless to control developers! Why is OnInit a recursive call while OnPreInit is not? Doesn't make sense.

February 15, 2006 3:48 PM  
Anonymous Anonymous said...

Ah you're right. I was thinking of ViewState in terms of page level properties, rather than in the custom control context you were refering to (for point 1).

You probably know already, but you can hookup the preinit event for a custom control like this:


protected override void OnInit(EventArgs e) {
Page.PreInit += new EventHandler(Page_PreInit);
base.OnInit(e);
}

void Page_PreInit(object sender, EventArgs e) {
//Set default that won't overwrite
//property set in ASPX
}


That won't overwrite a property set by a developer in the ASPX. I'm not sure if that approach has any drawbacks?

Cheers,
Dave

February 15, 2006 6:00 PM  
Blogger Infinity88 said...

I would imagine that hooking into the pre-init event from the init method wouldn't do you much good :) Kind of too late.

Even if you could I still think declared attributes are already assigned at that point.

Hooking into the PreInit event from your constructor doesn't work either since you don't even have a context yet (this.Page would be null).

If you only had the ability to override OnPreInit at the control level. I wrote Scott Gu (aka asp.net god) a lengthy comment about this issue once. I'm still interested in hearing the response :) Not that he's ignoring me, far from it!

Also to be fair ASP.NET 2.0 offers some other means of getting default values into your control that are code based, like the new <%$ %> syntax and expression builders.

February 15, 2006 6:12 PM  
Anonymous Anonymous said...

LOL me dumb. Please strike the entirety of my posts from the record. :)

Still, the code worked when I tested it :S Bit bizarre - it must have been a lucky coincidence.

I tried to override the Page property, and hookup the event on set, but it failed so I am assuming the ASP.NET engine bypasses the public accessor (need to check with Lutz's app :))

Btw, I found your blog from your recent post on ScottGu's.

Cheers,
Dave

February 15, 2006 6:27 PM  
Blogger Infinity88 said...

LOL I didn't know the Page property was even virtual. I think the problem there is that it isn't a simple accessor -- it digs deep into a context object to get the page, which probably comes from context.Handler. YOu could probably get to the page that way but.. what a hack that would be.

Anyway thanks for your awesome comments, I look forward to hearing more. And thanks to Scott Gu for the linkification.

February 15, 2006 6:37 PM  
Anonymous Anonymous said...

Hi, I was out blogging and found your site. It certainly got my attention and interest. I was looking for Medications information and even though this isn't a perfect match I enjoyed your site. Thanks for the read!

June 23, 2006 10:11 AM  

Post a Comment

<< Home