惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Know Your Adversary
Know Your Adversary
D
Docker
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
人人都是产品经理
人人都是产品经理
V
V2EX
V
Visual Studio Blog
J
Java Code Geeks
博客园 - 【当耐特】
罗磊的独立博客
量子位
W
WeLiveSecurity
博客园 - 聂微东
Hugging Face - Blog
Hugging Face - Blog
The Cloudflare Blog
O
OpenAI News
PCI Perspectives
PCI Perspectives
The Last Watchdog
The Last Watchdog
S
Schneier on Security
博客园 - Franky
WordPress大学
WordPress大学
T
Tor Project blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Security Latest
Security Latest
Cisco Talos Blog
Cisco Talos Blog
腾讯CDC
美团技术团队
博客园 - 叶小钗
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
S
Secure Thoughts
Engineering at Meta
Engineering at Meta
T
Tailwind CSS Blog
TaoSecurity Blog
TaoSecurity Blog
Google DeepMind News
Google DeepMind News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Last Week in AI
Last Week in AI
aimingoo的专栏
aimingoo的专栏
G
Google Developers Blog
D
DataBreaches.Net
Project Zero
Project Zero
S
SegmentFault 最新的问题
C
Cisco Blogs
H
Help Net Security
Google Online Security Blog
Google Online Security Blog
A
About on SuperTechFans
F
Fortinet All Blogs
博客园 - 司徒正美

博客园 - sunrack

LINQ to SQL语句之Group By/Having和Exists/In/Any/All/Contains ASPxPopupControl does not show up in the right place C++/CLI托管字符串与非托管char数组的转换 程序员从初级到中级10个秘诀 Get JavaScript IntelliSense With DevExpress Client-Side Objects - v2010 vol 1 Grid Editing - Cascading Combo Boxes - sunrack ASPxRadioButtonList 报错解决办法 - sunrack - 博客园 本地连接属性“出现意外错误”的解决办法 - sunrack - 博客园 Bootstrap DotNetFX35SP1 in Visual Studio 2010 Windows Server 2008 共享策略 XtraPivotGrid Custom Summaries 手动注册 DevExpress 8.2.3 控件到 Visual Studio 工具箱 sn.exe error "Failed to generate a strong name key pair -- The keyset is not defined" Thread Tools Rate Thread 浅析JavaScript中的showModalDialog的实战应用 javascript showModalDialog模态对话框使用说明 - sunrack - 博客园 entity framework 缓存干扰的数据不一致问题 Failed to load viewstate ? Typical problem, with an obvious solution. ASPxGridView 和 AJAX Extensions ToolBox 不兼容 ASPxCombobox Features
ViewState and Dynamic Control
sunrack · 2010-05-18 · via 博客园 - sunrack

I thought I understand ViewState, until I came cross this exception:

Failed to load viewstate.  The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request.  For example, when adding controls dynamically, the controls added during a post-back must match the type and position of the controls added during the initial request.

This is a question asked by someone on a .NET mailing list. My first guess of what causing the problem is that on a page postback, when LoadViewState() is invoked to restore the saved ViewState values to the page and its controls (both Control tree and ViewState tree have been created at this stage), somehow, the ViewState tree doesn't match the control tree. So when ASP.NET tries to restore a ViewState value to a control, no control or a wrong control is found and then the exception occurs.

Note: the ViewState tree (type of Triplet or Pair) is NOT the ViewState property (type of StateBag) of the page or any of its controls. You can think it as an object representation of the ViewState value on the html page (the __VIEWSTATE hidden field), which contains all the values need to be written back to the controls during a page postback. If you don't change the default behavior, during the page initialize/load phrase, the ViewState tree will be created by de-serializing the value __VIEWSTATE field by LoadPageStateFromPersistenceMedium(), and the values on the ViewState tree will be put into the controls ViewState bag in LoadViewState() . During the page save/render phrase, the ViewState tree will be created again by SaveViewState (), then serialized and written onto html page by SavePageStateToPersistenceMedium ()

So, I thought I could reproduce same exception with something simple like this:

Defualt.aspx

<form id="form1" runat="server">

<asp:Button ID="btnPostback" runat="Server" Text="Postback" />

</form>

Default.aspx.cs

public partial class _Default : Page

{

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if (!IsPostBack)

{

Button btnClickMe = new Button();

form1.Controls.Add(btnClickMe);

}

}

}

It is indeed a very simple page with a button named btnPostback created statically on .aspx file, and another button named btnClickMe created dynamically in Page.OnInit(), and I will not recreate the btnClickMe for postbacks. So on a page postback, by the time OnInit() and LoadPageStateFromPersistenceMedium() is executed, the control tree and ViewState tree would have different structure, the ViewState tree will have value for btnClickMe, but the control tree will not have the control btnClickMe. I thought it would be good enough to cause the exception, but soon I was proved wrong, there was no exception thrown.

To find out why, let's have a look of the actual ViewState value generated on the html page

“/wEPDwUKMTQ2OTkzNDMyMWRkOWxNFeQcY9jzeKVCluHBdzA6WBo=”

With a little help from ViewState Decoder I got this:

<viewstate>

<Pair>

<Pair>

<String>1469934321</String>

</Pair>

</Pair>

</viewstate>

There is no view state data for the neither of the buttons! I did expect something like <Pair /> for a control has empty state though.

So, I think here is the first thing I learned:

For a control on the Control tree, there may not be a corresponding item on the ViewState tree (if there is no state for this control need to be saved). If there is nothing found on ViewState tree for a control, the control’s LoadViewState() will not be invoked.

So, let's do something to make the button "dirty" and its ViewState saved.

public partial class _Default : Page

{

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if (!IsPostBack)

{

Button btnClickMe = new Button();

form1.Controls.Add(btnClickMe);

btnClickMe.Text = "Click me";

}

}

}

The ViewState now became:

/wEPDwUKMTQ2OTkzNDMyMQ9kFgICAw9kFgICAw8PFgIeBFRleHQFCENsaWNrIG1lZGRkaZj77nQ7KGQERj05RRc1lk+fvNA=

<viewstate>

<Pair>

<Pair>

<String>1469934321</String>

<Pair>

<ArrayList>

<Int32>3</Int32>

<Pair>

<ArrayList>

<Int32>3</Int32>

<Pair>

<Pair>

<ArrayList>

<IndexedString>Text</IndexedString>

<String>Click me</String>

</ArrayList>

</Pair>

</Pair>

</ArrayList>

</Pair>

</ArrayList>

</Pair>

</Pair>

</Pair>

</viewstate>

The format of the ViewState looks quite interesting, but let's worry about it later.For now, it does look like Text property of the btnClickMe having been saved. Great!

But when I ran it, still no exception was thrown.

So, I guess that is just the way it works:

For an Item on the ViewState tree, if there is no corresponding control can be found on control tree, this ViewState Item will be ignored.

So, how about creating a different control instead? Something like this:

public partial class _Default : Page

{

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if (!IsPostBack)

{

Button btnClickMe = new Button();

form1.Controls.Add(btnClickMe);

btnClickMe.Text = "Click me";

}

else

{

Label label = new Label();

form1.Controls.Add(label);

}

}

}

Still no exception! And it is very interesting, after the the page postback, the btnClickMe was gone and a label was shown with text "Click me"! I didn’t assign any value to its Text Property. Why "Click me" was there? The ASP.NET has restored the ViewState value onto label, but the value actually doesn't belong to it!

So, here is another interesting thing:

ASP.NET doesn’t really know which control a ViewState item belongs to. It only matches a item on the ViewState tree and a control on Control tree by the index.

If we have a look of the format of the saved ViewState, it contains nothing but just the indices of the control and the value-keys, so there is no way for ASP.NET can figure out which control exactly it belongs to. Anyway, I think this make perfect sense, we do want the _VIEWSTATE fields as small as possible, don't we?

The above sample has demonstrated a ViewState value for a Button's Text property was restored to a Label's Text Property on page postback. Now it comes to some interesting questions: what will happen if

1. The second control doesn't have the property with same name?

2. The second control has the property with different data type?

3. 2 controls are very different, say, a button and a GridView?

Let's find out!

public partial class _Default : Page

{

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if (!IsPostBack)

{

Button btnClickMe = new Button();

form1.Controls.Add(btnClickMe);

btnClickMe.Text = "Click me";

btnClickMe.CommandName = "XXX";

}

else

{

Label label = new Label();

form1.Controls.Add(label);

}

}

}

This time I have assigned a value to button's CommandName property, but Label doesn't have this property. If you run this code, still no exception will occur. Is it "XXX" for CommandName just simply ignored? Let's have a look of the saved ViewState after postback:

/wEPDwUKMTQ2OTkzNDMyMQ9kFgICAw9kFgICAw8PFgQeBFRleHQFCENsaWNrIG1lHgtDb21tYW5kTmFtZQUDWFhY

ZGRk7q5i15YA6gDUPW8m/IVLqGXnb+4=

<viewstate>

<Pair>

<Pair>

<String>1469934321</String>

<Pair>

<ArrayList>

<Int32>3</Int32>

<Pair>

<ArrayList>

<Int32>3</Int32>

<Pair>

<Pair>

<ArrayList>

<IndexedString>Text</IndexedString>

<String>Click me</String>

<IndexedString>CommandName</IndexedString>

<String>XXX</String>

</ArrayList>

</Pair>

</Pair>

</ArrayList>

</Pair>

</ArrayList>

</Pair>

</Pair>

</Pair>

</viewstate>

Note this is the saved ViewState after a post back, the values are for the label. So even a label doesn't have CommandName property, the value was still written to Label's ViewState bag, and then saved. So if you dynamically change a control at runtime, the new control may silently "inherit" some rubbish ViewState from control was previously sitting at the position, and carry it all the time, pass it from server to client, and client back to server.

To test the second question out, there is a bit more code I had to write, as I couldn't find a property which on two different controls with different data type, so I have defined my own ones.

public class MyButton : Button

{

public string MyProperty

{

get { return ViewState["MyProperty"] == null ? String.Empty : ViewState["MyProperty"] as String; }

set { ViewState["MyProperty"] = value; }

}

}

public class MyLabel: Label

{

public Color MyProperty

{

get { return ViewState["MyProperty"] == null ? Color.Black : (Color)ViewState["MyProperty"]; }

set { ViewState["MyProperty"] = value; }

}

}

public partial class _Default : System.Web.UI.Page

{

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if (!IsPostBack)

{

MyButton btnClickMe = new MyButton();

form1.Controls.Add(btnClickMe);

btnClickMe.MyProperty = "XXX";

}

else

{

MyLabel label = new MyLabel ();

label.ID = "label";

form1.Controls.Add(label);

}

}

protected override void OnLoad(EventArgs e)

{

base.OnLoad(e);

if (IsPostBack)

{

MyLabel label = form1.FindControl("label") as MyLabel;

label.Text = label.MyProperty.ToString();

}

}

}

As you can see both MyButton and MyLabel have Property called MyProperty, though MyButton.MyProperty is string type, but MyTextBox.MyProperty is Color type.

From previous test, we have learned that the LoadViewState() will write the ViewState values into controls' ViewState bag directly without being bothered to go through the controls' property. So, I would expect "XXX" will be written into MyLabel’s ViewState bag successfully even though MyLabel.MyProperty really expects a Color value, but we are going to have problem if we try to access the value in MyLabel.MyProperty.

My guess was right this time, if you run the code, an InvlaidCastException will be thrown by (Color)ViewState["MyProperty"] when the Property is accessed in OnLoad().

OK, the last one now, what will happen if two controls are very different? Ok, maybe we don't need something complicated as GridView, let's just try a DropDownList:

public partial class _Default : Page

{

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if (!IsPostBack)

{

Button btnClickMe = new Button();

form1.Controls.Add(btnClickMe);

btnClickMe.Text = "Click me";

}

else

{

DropDownList ddl = new DropDownList();

form1.Controls.Add(ddl);

}

}

}

When I ran the page, after clicking Postback button, I got a page returned with this error message:

Failed to load viewstate.  The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request.  For example, when adding controls dynamically, the controls added during a post-back must match the type and position of the controls added during the initial request.

Aha, finally get it!

To find out why, I made some little changes, first I defined MyDropDownList:

public class MyDropDownList : DropDownList

{

protected override void LoadViewState(object savedState)

{

base.LoadViewState(savedState);

}

}

There is nothing in MyDropDownList, it just overrides the LoadViewState(), so I can place a break point there.

And then I changed page to use MyDropDownList:

public partial class _Default : Page

{

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if (!IsPostBack)

{

Button btnClickMe = new Button();

form1.Controls.Add(btnClickMe);

btnClickMe.Text = "Click me";

}

else

{

MyDropDownList ddl = new MyDropDownList();

form1.Controls.Add(ddl);

}

}

}

Let’s see what is going to happen, put a break point at base.LoadViewState(), run it, click the Postback button, MyDropDownList created, the its LoadViewState() invoked, program hanged at base.LoadViewState(), good, just worked as expected! Hold on, this looks like a problem: LoadViewState(object savedState) seems to be expecting a Triplet object as a parameter, but what is actually passed in here is a Pair!

It does make sense, doesn't it? Don't forget the Pair object is the saved ViewState left behind from the btnClickMe, and on the postback, ASP.NET doesn’t know which control it belongs to, what the ViewState tree can tell is  "it belongs to the 3rd control on the form1" On the postback, the 3rd control on form1 now became a DropDownList, but ASP.NET is silly enough to try to restore it with the Pair object. For a DropDownList, only a Triplet object can be used to restore it, so, of course, when LoadViewState() is trying to do something like "Triplet triplet1 = (Triplet)savedState;", an exception will occur.

After having inspected some ASP.NET framework code using Lutz Roeder's .NET Reflector, expecially SaveViewState() and LoadViewState() method, I finally got a better picture of the what happened. A Control actually has full responsibility of saving/loading its ViewState. In ASP.NET, most of controls inherit their parent’s behavior defined in Control, WebControl or ListControl, but a control have complete control over it, and in theory, a control can have any data structure for holding its saved ViewState, as long as it is serializable and both SaveViewState() and LoadViewState() understand it. Normally, the ViewState of a WebControl will be saved as a Pair object, if Pair is not enough, a Triplet object may be used, like what ListControl does(ListControl needs a another object to hold the states for its child items). When ASP.NET tries to restore a control's ViewState with a velue which is saved for another control, if the two controls have different save ViewState object type, the Exception above will be thrown.

Conclusion:

When ASP.NET tries to restore ViewState values to a page and its controls, a ViewState tree will be created by deserializing the _VIEWSTATE value on html page. The ViewState tree contains only control indices and key-value pairs. ASP.NET finds the control for a ViewState item by the index*, and directly writes the value into control's ViewState bag. If you dynamically create/remove controls at runtime, it will be very likely to fool ASP.NET to restore ViewState values to a wrong control and causing a problem. Depending on what control is dynamically created/removed, following problems may occur:

1 Runtime exception when restoring ViewState

2 Runtime exception when accessing a property of a control

3 A control's property may have an unexpected value

4 A control may carry rubbish ViewState value and increase the size of _VIEWSTATE field on html page

Problems above may be difficult to notice and debug, especially 3 and 4 

*One can override this behavior using ViewStateModeByIdAttribute.