1. VIEWSTATE STORES VALUES
If you've ever used a hashtable, then you've got it. There's no rocket science here. ViewState has an indexer on it that accepts a string as the key and any object as the value. For example:
如果你用过hashtable,那你已经知道这点了。这里没有关于火箭的科学。ViewState有一个indexer,接受一个string作为key,任何object作为value.比如说
ViewState["Key1"] = 123.45M; // store a decimal value
ViewState["Key2"] = "abc"; // store a string
ViewState["Key3"] = DateTime.Now; // store a DateTime
Actually, "ViewState" is just a name. ViewState is a protected property defined on the System.Web.UI.Control class, from which all server controls, user controls, and pages, derive from. The type of the property is System.Web.UI.StateBag. Strictly speaking, the StateBag class has nothing to do with ASP.NET. It happens to be defined in the System.Web assembly, but other than it's dependency on the State Formatter, also defined in System.Web.UI, there's no reason why the StateBag class couldn't live along side ArrayList in the System.Collections namespace. In practice, Server Controls utilize ViewState as the backing store for most, if not all their properties. This is true of almost all Microsoft's built in controls (ie, label, textbox, button). This is important! You must understand this about controls you are using. Read that sentance again. I mean it... here it is a 3rd time: SERVER CONTROLS UTILIZE VIEWSTATE AS THE BACKING STORE FOR MOST, IF NOT ALL THEIR PROPERTIES. Depending on your background, when you think of a traditional property, you might imagine something like this:
实际上,“ViewState”只是一个名字。ViewState是一个定义在System.Web.UI.Control类里的protected property,所有的服务器控件,用户控件,页面都继承自这个类。这个property的类型是System.Web.UI.StateBag。严格来说,StateBag跟ASP.NET一点关系都没有。除了它对也定义在System.Web.UI 的State Formatter的依赖之外,它只是碰巧被定义在System.Web assembly中,其实没什么理由让StateBag不能跟System.Collections中的ArrayList一起存在。实践证明,Server Controls utilize ViewState as the backing store for most, if not all their properties(因为这个在后面多次重复出现,而且作者特别强调,所以大家还是自己理解原文吧)。其实真的是几乎所有的Microsoft的东东都构建在控件里(ie, label, textbox, button)。这很重要!你必须理解你在使用的控件相关的东东。再读一遍这个句子。我的意思是…已经第三次了:SERVER CONTROLS UTILIZE VIEWSTATE AS THE BACKING STORE FOR MOST, IF NOT ALL THEIR PROPERTIES.根据你的背景。当你想起一个传统的property,你可能会想像这样一个东西:
public string Text {
get { return _text; }
set { _text = value; }
}
What is important to know here is that this is NOT what most properties on ASP.NET controls look like. Instead, they use the ViewState StateBag, not a private instance variable, as their backing store:
重要的是要知道大多数ASP.NET控件的properties并不是这样的。而是,他们使用了ViewState StateBag, 而不是private instance variable作为他们的backing store:
public string Text {
get { return (string)ViewState["Text"]; }
set { ViewState["Text"] = value; }
}
And I can't stress it enough -- this is true of almost ALL PROPERTIES, even STYLES (actually, Styles do it by implementing IStateManager, but essentially they do it the same way). When writing your own controls it would usually be a good idea to follow this pattern, but thought should first be put into what should and shouldn't be allowed to be dynamically changed on postbacks. But I digress -- that's a different subject. It is also important to understand how DEFAULT VALUES are implemented using this technique. When you think of a property that has a default value, in the traditional sense, you might imagine something like the following:
我实在受不了了 – 这是几乎ALL PROPERTIES,甚至是STYLES的真相(实际上,STYLES没有实现IStateManager接口,但是本质上他们的做法是一样的)。当你在写你自己的控件的时候,通常遵循这个pattern是个好主意,但是首先要考虑在postbacks时什么东东应该被允许动态改变,什么东东不应该被允许。但我有点离题了 – 这个另外一个主题。理解DEFAULT VALUES时怎么使用这个技术实现的也很重要。当你考虑到一个property有一个default value时,以传统的理解,你可能会想象下面这样的东西:
public class MyClass {
private string _text = "Default Value!";
public string Text {
get { return _text; }
set { _text = value; }
}
}
The default value is the default because it is what is returned by the property if no one ever sets it. How can we accomplish this when ViewState is being used as the private backing? Like this:
Default value之所以是default是因为如果没有人设置它,那么这就是它通过property返回的value.当ViewState被用来作为private backing时,我们又是怎么来完成的呢?就像这样:
public string Text {
get {
return ViewState["Text"] == null ?
"Default Value!" :
(string)ViewState["Text"];
}
set { ViewState["Text"] = value; }
}
Like a hashtable, the StateBag will return null as the value behind a key if it simply doesn't contain an entry with that key. So if the value is null, it has not been set, so return the default value, otherwise return whatever the value is. For you die-hards out there -- you may have detected a difference in these two implementations. In the case of ViewState backing, setting the property to NULL will result in resetting the property back to it's default value. With a "regular" property, setting it to null means it will simply be null. Well, that is just one reason why ASP.NET always tends to use String.Empty ("") instead of null. It's also not very important to the built in controls because basically all of their properties that can be null already are null by default. All I can say is keep this in mind if you write your own controls. And finally, as a footnote really, while this property-backing usage of the ViewState StateBag is how the StateBag is typically used, it isn't limited to just that. As a control or page, you can access you're own ViewState StateBag at any time for any reason, not just in a property. It is sometimes useful to do so in order to remember certain pieces of data across postbacks, but that too is another subject.
就像一个hashtable, 如果没有key相关联的entry,那么通过一个key,StateBag会返回一个null.所以如果value是null,它还没被设置,就返回默认值,否则就返回设置的value. For you die-hards out there(不会翻…) -- 你会发现这两个实现有一个不同。在ViewState做backing的时候,把property设置为null将会导致把property重设为它的default value.对一个“通常”的property而言,把它设置为null意思就是设置为null(译注:而不是重设为default value)。这其实就是ASP.NET总是倾向于使用String.Empty ("")来代替null的一个原因。The built in controls也不是很重要,因为基本上他们的properties可以是null,其实默认就是null.我所有想说的就是在你自己写你的控件的时候,记住这些。最后,作为一个footnote, 其实基本上ViewState StateBag作为property-backing的用途就是StateBag怎么被使用的,这不只是纠正。作为一个页面或者控件,你可以在任何时间为了任何理由access 你自己的ViewState StateBag,而不仅仅在一个property里。有时候这样有用是为了在across postbacks时记住某些片断,但这也是另一个主题了。
2. VIEWSTATE TRACKS CHANGES
Have you ever set a property on a control and then somehow felt... dirty? I sure have. In fact, after a twelve-hour day of setting properties in the office, I become so filthy my wife refuses to kiss me unless I'm holding flowers to mask the stench. I swear! Ok so setting properties doesn't really make you dirty. But it does make the entry in the StateBag dirty! The StateBag isn't just a dumb collection of keys and values like a Hashtable (please don't tell Hashtable I said that, he's scarey). In addition to storing values by key name, the StateBag has a TRACKING ability. Tracking is either on, or off. Tracking can be turned on by calling TrackViewState(), but once on, it cannot be turned off. When tracking is ON, and ONLY when tracking is ON, any changes to any of the StateBag's values will cause that item to be marked as "Dirty". StateBag even has a method you can use to detect if an item is dirty, aptly named IsItemDirty(string key). You can also manually cause an item to be considered dirty by calling SetItemDirty(string key). To illustrate, lets assume we have a StateBag that is not currently tracking:
你曾今在一个控件上设置了一个属性,而感到...dirty吗?我肯定有.事实上,在办公室待了12个小时去设置属性值之后,我变得那么dirty以至于我老婆不想亲我了除非我鲜花罩住那难闻的气味。I swear! 好了,其实设置属性值并不会让我变dirty。但是它确实让StateBag的entry变dirty了!StateBag并不是像hashtable那样有一些键/值的哑巴集合(请不要告诉 Hashtable我说了这些,它会感到惊慌的)。除了使用key name储存values,StateBag有一种Tracking的能力。Tracking要么是开着的,要么是关闭的。Tracking可以通过调用TrackViewState()打开,但是一旦打开,就关不掉了(译注:有点像潘多拉的盒子,哈)当tracking打开了,也只有当它打开了,任何对StateBag值得改变将会导致这个item被标记为”Dirty”.StateBag甚至有一个方法,你可以用来测试他是不是dirty的,名字叫做IsItemDirty(string key).你可以手动通过调用SetItemDirty(string key)让一个item被认为是dirty的。举例来说,让我们假设我们有一个StateBag现在没有tracking:
stateBag.IsItemDirty("key"); // returns false
stateBag["key"] = "abc";
stateBag.IsItemDirty("key"); // still returns false
stateBag["key"] = "def";
stateBag.IsItemDirty("key"); // STILL returns false
stateBag.TrackViewState();
stateBag.IsItemDirty("key"); // yup still returns false
stateBag["key"] = "ghi";
stateBag.IsItemDirty("key"); // TRUE!
stateBag.SetItemDirty("key", false);
stateBag.IsItemDirty("key"); // FALSE!
Basically, tracking allows the StateBag to keep track of which of it's values have been changed since TrackViewState() has been called. Values that are assigned before tracking is enabled are not tracked (StateBag turns a blind eye). It is important to know that any assignment will mark the item as dirty -- even if the value given matches the value it already has!
基本上,tracking允许TrackViewState()在调用后,StateBag的值被改变后,一直track它。在没设置track能力以前设置的值不被track(StateBag这时候是瞎的)。重要的是知道任何赋值都会把item标记为dirty – 甚至只是被给的值吻合它已经有的值。
stateBag["key"] = "abc";
stateBag.IsItemDirty("key"); // returns false
stateBag.TrackViewState();
stateBag["key"] = "abc";
stateBag.IsItemDirty("key"); // returns true
ViewState could have been written to compare the new and old values before deciding if the item should be dirty. But recall that ViewState allows any object to be the value, so you aren't talking about a simple string comparison, and the object doesn't have to implement IComparable so you're not talking about a simple CompareTo either. Alas, because serialization and deserialization will be occuring, an instance you put into ViewState won't be the same instance any longer after a postback. That kind of comparison is not important for ViewState to do it's job, so it doesn't. So that's tracking in a nutshell.
ViewState可能已经被写成在决定item是否为dirty前,比较新旧值。但是会想到ViewState可以让一个object作为值,所以你不能只是讨论简单的字符串比较,而一个对象不一定实现Icomparable接口,所以你也不能只是讨论简单的CompareTo。哎,因为序列化和反序列化将要发生,你放到ViewState的实例在postback后将不再是同样的实例。这种比较对于ViewState作它的工作并不重要,所以它就不做咯。所以tracking就在一个nutshell里.
But you might wonder why StateBag would need this ability in the first place. Why on earth would anyone need to know only changes since TrackViewState() is called? Why wouldn't they just utilize the entire collection of items? This one point seems to be at the core of all the confusion on ViewState. I have interviewed many professionals, sometimes with years and years of ASP.NET experience logged in their resumes, who have failed miserably to prove to me that they understand this point. Actually, I have never interviewed a single candidate who has! First, to truly understand why Tracking is needed, you will need to understand a little bit about how ASP.NET sets up declarative controls. Declarative controls are controls that are defined in your ASPX or ASCX form. Here:
你可能会奇怪为什么StateBag从一开始就需要这个能力。为什么在这个地球上会有人想要知道在TrackViewState()被调用后的所有改变?为什么他们不仅仅是利用整个items的集合?这个问题好像就是所有对ViewState的迷惑的核心所在。我采访过很多专家有些是在他们的简历上写着多年ASP.NET经验的,而他们在向我证明他们理解这点上输得很惨。实际上,我还没有采访到能理解它的人!首先,真正理解为什么需要Tracking,你需要理解一些关于ASP.NET怎么建立declarative controls。Declarative controls是定义在ASPX或ASCX form中的控件。看这里:
<asp:Label id="lbl1" runat="server" Text="Hello World" />
I do declare that this label is declared on your form. The next thing we need to make sure you understand is ASP.NET's ability to wire up declared attributes to control properties. When ASP.NET parses the form, and finds a tag with runat=server, it creates an instance of the specified control. The variable name it assigns the instance to is based on the ID you assigned it (by the way, many don't realize that you don't have to give a control an ID at all, ASP.NET will use an automatically generated ID. Not specifying an ID has advantages, but that is a different subject). But that's not all it does. The control's tag may contain a bunch of attributes on it. In our label example up above, we have a "Text" attribute, and it's value is "Hello World". Using reflection, ASP.NET is able to detect whether the control has property by that name, and if so, sets its value to the declared value. Obviously the attribute is declared as a string (hey, its stored in a text file after all), so if the property it maps to isn't of type string, it must figure out how to convert the given string into the correct type, before calling the property setter. How it does that my friend is also an entirely different topic (it involves TypeConverters and static Parse methods). Suffice it to say it figures it out, and calls the property setter with the converted value.
我声明这个label在你的form里。接下来我们要保证你理解的是ASP.NET绑定declared attributes到控件属性的这种ability。当ASP.NET解析form的时候,他们找到一个tag使用了runat=server,它创建了一个特定控件的实例。它给实例赋的变量名是根据你给它的ID来的(by the way,很多人没有意识到你根本不用给一个控件一个ID,ASP.NET会使用一个自动生成的ID,不指定ID 有一些好处,但这是另外一个主题了)。但这还不是它所作的全部。控件的tag可以有一打的属性。在我们上面的label的例子中,我们有一个“Text”属性,它的值是”Hello World”.使用反射,ASP.NET能够探测控件是否有这样一个名字的属性,如果有,设置它的值为声明的值。显然,属性被声明为string(hey,它毕竟存在一个文本文件里),所以它映射的类型不是string,它必须在调用property setter之前,找方法怎么把给出的string转化为正确的类型。它是怎么做的,我的朋友,这也是个完全不同的主题(它跟TypeConverters和static Parse方法有关)。好了,假设它已经有办法了,使用转化后的值调用property setter。
Recall that all-important statement from the first role of the StateBag. Here it is again: Server Controls utilize ViewState as the backing store for most, if not all their properties. That means when you declare an attribute on a server control, that value is usually ultimately stored as an entry in that control's ViewState StateBag. Now recall how tracking works. Remember that if the StateBag is "tracking", then setting a value to it will mark that item as dirty. If it isn't tracking, it won't be marked dirty. So the question is -- when ASP.NET calls the SET on the PROPERTY that corresponds to the ATTRIBUTE that is DECLARED on the control, is the StateBag TRACKING or isn't it? The answer is no it is not tracking, because tracking doesn't begin until someone calls TrackViewState() on the StateBag, and ASP.NET does that during the OnInit phase of the page/control lifecycle. This little trick ASP.NET uses to populate properties allows it to easily detect the difference between a declaratively set value and dynamically set value. If you don't yet realize why that is important, please keep reading.
回想到作为StateBag的第一角色得出的所有重要结论,它又来了:Server Controls utilize ViewState as the backing store for most, if not all their properties.
这意思就是说当你在一个服务器控件上声明一个属性(值),这个值通常基本上就存为ViewState StateBag的entry.现在回忆tracking是怎么工作的。回想到如果StateBag正被"tracking",那么给它设置一个值会让它标记为”dirty”.如果它没被tracking,它就不会被标记为dirty。那么问题就是 –- 当ASP.NET对PROPERTY调用SET,通知到声明在控件上的属性,这时候StateBag有没有被Tracking呢?答案是不,它没被tracking,因为tracking只有在StateBag 的TrackViewState()被调用后才会开始,ASP.NET在页面生命周期的OnInit解析时作了这件事情。ASP.NET用在populate properties上的这个小技巧可以让它轻松发现到底是声明性设置(值)还是动态设置(值)。如果你还没意识到为这么着很重要,继续读。
3. SERIALIZATION AND DESERIALIZATION
Aside from how ASP.NET creates declarative controls, the first two capabilities of ViewState we've discussed so far have been strictly related to the StateBag class (how it's similar to a hashtable, and how it tracks dirty values). Here is where things get bigger. Now we will have to start talking about how ASP.NET uses the ViewState StateBag's features to make the (black) magic of ViewState happen.
除了ASP.NET怎么创建declarative controls的部分,我们到现在讨论的ViewState的前两个能力都跟StateBag类有紧密联系(它跟hashtable的相似之处,它怎么track dirty value).这就是让事情开始变得庞大的地方。现在我们将要开始讨论ASP.NET怎么使用ViewState StateBag的特性来让ViewState的(黑)魔法发生的。
If you've ever done a "View Source" on an ASP.NET page, you've no doubt encountered the serialization of ViewState. You probably already knew that ViewState is stored in a hidden form field aptly named _ViewState as a base64 encoded string, because when anyone explains how ViewState works, that's usually the first thing they mention.
如果你对ASP.NET页面做过”View Source”这个事情,你应该就会毫不怀疑ViewState被序列化了。你很可能已经知道ViewState是一个 base64编码存在一个叫做_ViewState的隐藏form字段中的string,因为任何人解释ViewState怎么工作的时候,这通常就是第一件被提到的事情。
A brief aside -- before we understand how ASP.NET comes up with this single encoded string, we must understand the hierarchy of controls on the page. Many developers with years of experience still don't realize that a page consists of a tree of controls, because all they work on are ASPX pages, and all they need to worry about are controls that are directly declared on those pages... but controls can contain child controls, which can contain their own child controls, etc. This forms a tree of controls, where the ASPX page itself is the root of that tree. The 2nd level is all the controls declared at the top level in the ASPX page (usually that consists of just 3 controls -- a literal control to represent the content before the form tag, a HtmlForm control to represent the form and all its child controls, and another literal control to represent all the content after the close form tag). On the 3rd level are all the controls contained within those controls (ie, controls that are declared within the form tag), and so on and so forth. Each one of the controls in the tree has it's very own ViewState -- it's very own instance of a StateBag. There's a protected method defined on the System.Web.UI.Control class called SaveViewState. It returns type 'object'. The implementation for Control.SaveViewState is to simply pass the call along to the Control's StateBag (it too has a SaveViewState() method). By calling this method recursively on every control in the control tree, ASP.NET is able to build another tree that is structured not unlike the control tree itself, except instead of a tree of controls, it is a tree of data.
A brief aside – 在我们开始理解ASP.NET怎么产生这个编码字符串之前,我们必须理解页面上的控件的继承层次。很多有多年经验的开发人员仍没有意识到一个页面包含a tree of controls,因为他们都工作在ASPX页面,他们所需要担心的只是直接声明在这些页面上的控件...但是控件包含子控件,子控件又可以包含子控件。这就产生a tree of controls,而ASPX页面自身就是这棵树的根。第二层就是声明在ASP.NET顶层上的控件(通常只包含三个控件 -- a literal control用来represent form tag之前的内容,一个HtmlForm control用来represent form 和它的子控件,还有另一个literal control用来represent the close form tag 后的内容)。第三层就是所有在这些控件里的控件(比如,声明在the form tag里的控件),依此类推第四层。每一个在这棵树上的控件都有他自己的ViewState –- 它是控件自己的StateBag实例。在System.Web.UI.Control类中定义了一个protected method,叫做SaveViewState。它返回’object’类型。Control.SaveViewState的实现只是简单得把这个调用传递给Control的 StateBag(它也有一个SaveViewState()方法)。在control tree上的每一个控件上通过递归调用这个方法,ASP.NET就可以建立另一棵树,它的结构根control tree本身没什么不一样,除了把一个控件树变为了一棵数据树。
The data at this point is not yet converted into the string you see in the hidden form field, it's just an object tree of the data to be saved. Here is where it finally comes together... are you ready? When the StateBag is asked to save and return it's state (StateBag.SaveViewState()), it only does so for the items contained within it that are marked as Dirty. That is why StateBag has the tracking feature. That is the only reason why it has it. And oh what a good reason it is -- StateBag could just process every single item stored within it, but why should data that has not been changed from it's natural, declarative state be persisted? There's no reason for it to be -- it will be restored on the next request when ASP.NET reparses the page anyway (actually it only parses it once, building a compiled class that does the work from then on). Despite this smart optimization employed by ASP.NET, unnecessary data is still persisted into ViewState all the time due to misuse. I will get into examples that demonstrate these types of mistakes later on.
这里的数据并没有转化为你在隐藏字段里看到的字符串,这只是保存数据的object tree.现在让我们把所有东西结合起来...你准备好了吗?当StateBag被要求保存,返回它的状态(StateBag.SaveViewState()),但它仅仅在它保存的item被标记为Dirty的时候才作这件事情。这就是为什么StateBag有一个tracking特性。这是他拥有这个特性的唯一理由。那么一个好的理由是 – StateBag能仅仅处理在它里面的每一个item,但为什么没有被改变的数据和被声明的状态(译注:声明值)被持久化?没有任何理由让它这么做 –它将在下一次请求,ASP.NET解析页面时被restored(实际上,它只被解析一次,构建了一个编译好的类型,然后就有这个类型做以后的工作)。尽管ASP.NET作了这么聪明的一个优化,因为误用,不需要的数据还是总被持久化到ViewState,我稍后会给出几个例子说明这些类型的错误。
To be continued…