现在的位置: 首页 > 综合 > 正文

[转]ASP.NET State Management: View State

2013年05月08日 ⁄ 综合 ⁄ 共 17677字 ⁄ 字号 评论关闭

Sample Code Download

 

 

ASP.NET view state is a great feature and an essential tool for web development of today. It maintains the state of a page as it travels back and forth. There is no more need to worry about restoring values of page controls between postbacks. In this article you will get an in-depth perspective on view state. We will talk about ways of reducing unnecessary payload and protecting view state from prying eyes.

Among IT professionals it has become popular to debunk, dissect, expose, and unleash things. In this article we will first debunk view state, and then dissect it. No, first unleash then debunk. I think.

Dissecting View State

As far as web developers are concerned, the web is stateless. This statement will serve as the cornerstone of our entire discussion of view state. Therefore I’ll say it again—as far as we are concerned, the Web is stateless.

For example, you request an ASP.NET page from a web server. Once the page is processed on the server and returned to you that same server does not remember the page anymore. Even a slight page postback initiates the same request sequence and the web server performs the same task again as if it never saw this page in the first place. This is the grand difference between web applications and their desktop counterparts. On the desktop you can maintain state between requests. On the web it’s a much harder task.

How do you retain text in input boxes, selections of dropdown controls, etc? To maintain state on the web you need help. ASP.NET goes a long way giving you the necessary support to carry on the page state from one request to another, and it does so seamlessly. Sometimes so seamlessly that you may overlook a large chuck of text you drag around. Hold this thought. We'll get back to it shortly.

To summarize view state's mission in once sentence—view state helps you maintain values through subsequent requests of the same (!) page.

Where Is View State Stored?

View state is simply text. It is an aggregate of values of controls on a page. It's a string that contains values of page controls hashed and encoded in some manner. The view state contains no information about the server or the client. It only comprises information about the page itself and its controls. It lives along with the page in the user's browser.

As a rule, view state is stored right in the page and therefore it travels with it back and forth. Remember good old hidden input fields? View state is nothing more than a hidden input which holds a hash of control values. If you view the source of an ASP.NET web form you will see something like this:

<input type="hidden" name="__VIEWSTATE" 
value="CEbzzzEnmmz+Bc8IDFlnpgCLJ/HB00...>

Nobody in sane mind can read these strings fluently. What you see here is a base64 external linkencoded string. I emphasize encoded because some folks assume view state is encrypted. Base64 is not an encryption algorithm. Base64 makes a string suitable for HTTP transfer plus it makes it a little hard to read. Just a little. It's easy to decode this string and see what's inside. Can this be a security issue? It sure can. We'll address your concern in due time. Stick around.

How Is View State Built?

Every server control ultimately derives from the Control class. Be it a WebControl, HtmlControl or even LiteralControl it has a property called EnableViewState. When a page is built every control that has this property enabled contributes to the view state by serializing its contents (in this case: converting its contents into a string). Now, some controls are easy to serialize, while others might give us grief.

What manages the view state is the StateBag class external link. This class is like a dictionary—you may store key/value pairs in it. This is how you store a piece of useful data in the view state:

ViewState ["SortOrder"] = "email"

When the page posts back its view state is taken apart (decoded) on the server and each control participating in the view state gets its value restored.

There's an interesting gotcha you need to be aware of. Some controls get their values restored automatically (courtesy of ASP.NET) and you don't need to maintain their values! I put together two almost identical pages—one is an ASP.NET web form, and the other one is plain HTML.

The ASP.NET page has its view state turned off completely. See how it maintains text and selections once you click Submit. Scroll down to "Form Collection" to see what was posted. ASP.NET restored these values automatically!

Form posback values

The second page is old, "traditional", HTML. Click Submit to receive proof that control values won't be restored.

What's the moral of this story? You don't always need view state enabled to maintain page state. "When do I need it though? What's it for then?" Glad you asked. The prime candidates for participation in view state are those controls that don't post back with the HTTP form and controls added or populated dynamically.

Let me give you an example. Suppose you have an empty dropdown list which you populate with user names from the database. When the page runs for the first time (!Page.IsPostback. Rings a bell?) you need to databind it and fill it with user names. What happens once the page loads? Without view state the dropdown list will be empty. As you enable view state the dropdown list content will be restored on postback. Or... you would have to populate the list from the database every time the page posts back! If you weigh database access vs. view state the scale tips in favor of view state (unless the list of users is so huge that you'd rather ping the database each time instead of dragging around a giant view state string).

What Exactly Can I Store in View State?

Documentation states the view state is "optimized" for a small number of types. The lucky ones are strings, integers, booleans, ArrayLists and HashTables. Everything else should either be serializable or have a TypeConverter defined for it.

Beware of performance drawbacks for complex types. Serializing and deserializing a complex object could incur too much overhead. Choose wisely! Consider storing such an object in Session or Cache instead. Remember—whatever you store in the view state travels back and forth wasting bandwidth and making page downloads slower.

Susan Warren external link has kindly put together this decision chart of view state vs. session:

  Session State View State
Holds server resources? Yes No
Times out? Yes – after 20 minutes (default) No
Stores any .NET type? Yes No, limited support for: strings, integers, Booleans, arrays, ArrayList, hashtable, custom TypeConverters
Increases "HTML payload"? No Yes

As you see some objects are a perfect fit for view state, while others had better be stored in the session. If you still need to store an object in the view state think about creating a TypeConverter for it to improve performance.

How Do I Enable View State?

You may do so on the machine, application, page and control level. First, you must have a server side form (<form runat="server">). By default view state is enabled (as of ASP.NET 1.1). A quick peek into machine.config reveals this much:

<pages enableViewState="true" enableViewStateMac="true" ... />

The nature of machine.config prescribes general settings for all applications. In other words since view state is enabled on the machine level it is enabled on the application, page and control level. Thus each server control has view state enabled by default. Settings roll downhill so to say.

How Do I Disable View State?

Again, you may do so on the machine, application, page and control level. To disable view state of an individual control set its EnableViewState to false:

<asp:Label id=Label1 runat="server" EnableViewState="false" />

As a rule of thumb if a control does not contain any dynamic data, or its value is hardcoded or assigned on every page request and you're not handling its events you can disable the control's view state.

A good example of a big consumer of view state is the DataGrid control. Should you enable its view state or not? It depends. Again, if the page with the DataGrid does not post back you can live without view state just fine. On the other hand, if the DataGrid has sorting or paging enabled you will want view state.

Remember, DataGrid renders as a table with rows and cells being its child controls. View state is maintained for these child controls. The DataGrid has no memory of the datasource you bound it to! The datasource is NOT stored in the viewstate.

You may also disable view state on an entire page by modifying the Page directive:

<%@ Page ... EnableViewState="false" %>

If you really wish you may also disable view state on the whole web application by adding the following line to your web.config:

<pages enableViewState="false" />

When In Page Lifecycle Is It Safe To Use View State?

In the page lifetime cycle view state is available between the Init and PreRender events. If you need to dig deeper into the class behind the view state check out StateBag external link and StateItem external link.

Can I Get Rid Of View State Completely?

No. You will always have a relatively short string representing the page itself even if you turn off the view state on each and every control.

Size Matters

Spammers know better. By trimming the view state where you can live without it you do yourself and others a favor by reducing payload and improving page performance. Just recently we were cleaning up dead view state in our main product at work. I was shocked how much lighter most pages have become. A couple of pages that didn't post back had large databound controls. By disabling view state on one of them the size of the view state went from 28K to 20 bytes! Quoting a 1400% reduction would be ridiculous but you get the point. The whole exercise proved to be well worth it. You need to thoroughly understand what each page does before you trim its view state.

Adressing Security Issues

I started my career as a hacker (A note to Big Brother: there's nothing interesting here. Go away). My first languages where Pascal (oh yes, Borland was beating Microsoft back then), Assembler and then C++ (another score for Borland and Zortech). I can tell you right up front—if something can be engineered, it can as well be reverse engineered. Not that all "security consultants" out there suck... Still, Microsoft folks in their infinite wisdom gave us tools to protect our view state or at least make it tamper-resistant. And please remove that PostIt with the admin login off the server case.

The good thing in our story is that the "shared secret" is stored on the server away from prying eyes. Now you can assure your clients that no sensitive information will leak into the wrong hands. At least not via view state. You can put that stickie back now.

Protecting View State: The Easy Way

Once again, view state is represented as a base64-encoded string. The keyword here is encoded. Not encrypted. Therefore if you were to decode it you would be able to gain insight of the workings of a web page. Maybe someone would be smart enough to store a credit card number in view state! This also means that someone may build their own page, pre-fill it and send it to your server (one-click attack) thus fooling it to believe it's dealing with a legitimate page. There's a simple yet effective way to counter this attack.

By default ASP.NET builds a so-called Message Authentication Code (MAC) and appends it to the view state. When the page posts back ASP.NET recalculates the hash and compares it to the one that arrived with the view state string. If they are different it reverts to old control values. If you peek into your machine.config again you'll notice that MAC validation is on by default:

<pages ... enableViewStateMac="true" />

It is strongly recommended that you keep MAC validation enabled at all times. It works well to prevent spoofing attacks, i.e. when an attacker feeds values that you normally don't allow. This validation doesn't work too well with one-click attacks, though, because the view state will appear to be valid and the page will execute under the security context of the user.

To thwart one-click attacks you can resorts to another simple technique. In ASP.NET 1.1 the Page class has a very useful property, ViewStateUserKey external link. Set it in Page_Init to some unique value (authenticated user ID, for example).

Note: This is not an issue if users browse anonymously and don't participate in sensitive transactions.

By default ASP.NET creates MACs using the SHA1 hashing algorithm:

<machineKey ... validation="SHA1"/>

If you wish you may instruct it to use MD5 instead. SHA1 produces a larger hash than MD5 and is therefore considered more secure. Keep in mind, though, that the view state string can still be base64-decoded and viewed on the client.

Protecting View State: The Hard Way

It's not really that hard. You can go beyond comparing view state hashes and have it encrypted as well. This is a two step process:

  1. Set enableViewState="true"
  2. Set machineKey validation type to 3DES. This causes ASP.NET to encrypt the view state.

Your web.config should have these two entries:

<pages enableViewState="true" enableViewStateMac="true" />
<machineKey ... validation="3DES" />

Simple as that. Here's what happens behind the covers: ASP.NET creates a random encryption key and stores it in each server's Local Security Authority (LSA). Therefore it becomes virtually impossible to decrypt the view state string since the "shared secret" is stored on the server. ASP.NET uses the key from LSA to encrypt and decrypt the view state.

Protecting View State: Web Farm Scenario

By default ASP.NET uses autogenerated keys for view state validation and encryption. Validation and decryption happen separately and therefore two different keys are employed. Both keys reside in each server's SLA. What happens if your web application runs in a web farm? How would you facilitate a view state that came from another server? Obviously the set of keys from this other server will be different (remember, they are generated automatically and therefore are unique?).

The way out is to create both the validation and decryption keys by hand and store them in your web.config:

<machineKey
validationKey="value,[IsolateApps]"
decryptionKey="value,[IsolateApps]"
validation="SHA1|MD5|3DES" />

Let's take a closer look at machineKey attributes:

  • validationKey specifies the key for validation of the view state. ASP.NET will use this key when calculating MACs. The key must be 20 to 64 bytes (40 to 128 hexadecimal characters). The recommended key length is 64 bytes. This key should be generated in a random manner. If you tag IsolateApps to the end of the key value ASP.NET will generate a unique key for each application using the application's ID.
  • decryptionKey specifies the key used to encrypt and decrypt the view state when validation="3DES". They key must be 8 for DES encryption or 24 bytes for 3DES (16 or 48 hexadecimal characters respectively). The recommended key length is 48 bytes. This key should be generated in a random manner. If you tag IsolateApps to the end of the key value ASP.NET will generate a unique key for each application using the application's ID.
  • validation sets the type of encryption. When set to SHA1 or MD5 it instructs ASP.NET to use either SHA1 or MD5 algorithm to create view state MACs. When set to 3DES instructs ASP.NET to encrypt the view state (also provides integrity checking) with the help of the Triple-DES symmetric encryption algorithm.

For your convenience I've put together an online machineKey Generator external link. The tool creates a complete machineKey that you can paste in your web.config.

If you still have doubts that view state can be very secure read on—you'll learn how to store it in the database.

Keeping View State On The Server

The good old Page class allows us to tap into the process of storing and loading view state. With the technique you can reroute the view state to a database. Why would you want to? Maybe you manipulate a lot of dynamic data and your view state is till big aggravating matters for users with slow internet connections. Or maybe you are still concerned that someone decrypts the view state in spite of all these tight security measures. You are free to use this technique. After all, the page is only the view state's default persistence media.

The Page class gives us the right tools for the job. It provides two handy virtual methods:

protected virtual 
void SavePageStateToPersistenceMedium(object viewState);

protected virtual
object LoadPageStateFromPersistenceMedium();

You can easily guess from their names that these two methods persist view state to and load from a medium. This medium can be just about anything—a file, a database, etc.

At the beginning of this article I explained the role of the StateBag class. I also said that the view state is represented as a textual string. How does the conversion happen? There's hope. It lies with the class called LosFormatter.

These days MSDN states that LosFormatter

"serializes the view state for a Web Forms page" and "is designed for highly compact ASCII format serialization. This class supports serializing any object graph, but is optimized for those containing strings, arrays, and hashtables. It offers second order optimization for many of the .NET primitive types."

Good enough. LosFromatter lists two methods we're after:

public void Serialize(Stream, object);
public void Serialize(TextWriter, object);

and

public object Deserialize(Stream);
public object Deserialize(string);
public object Deserialize(TextReader);

The Serialize method is the one that converts an instance of StateBag (second parameter) and writes it into a Stream or TextWriter. The Deserialize method performs the opposite task. It builds an instance of StateBag from a base64 encoded string, a stream or a TextReader.

Some code is in order to illustrate the mechanics of view state persistence:

protected override 
SavePageStateToPersistenceMedium (object ViewState)
{
StringBuilder sb = new StringBuilder ();
StringWriter swr = new StringWriter (sb);

LosFormatter formatter = new LosFormatter ();
formatter.Serialize (swr, viewState);
swr.Close ();

// Store the textual representation of ViewState in the
// database or elsewhere
// The serialized view state is available via sb.ToString ()
}

protected override object LoadPageStateFromPersistenceMedium()
{
object objViewState;
string strViewState;

// Viewstate should be read from the database or
// elsewhere into strViewState
LosFormatter formatter = new LosFormatter ();
try
{
objViewState = formatter.Deserialize (strViewState);
}
catch
{
throw new HttpException ("Invalid viewstate");
}
return objViewState;
}

Feel free to download a full-fledged sample of this code.

Burning View State Fat

Reflecting on our experience of our flagship product at work, I'd like to share a little "success story". We decided to give the code above a try and redirected view state persistence to the database. Next we ran stress tests in Microsoft Application Center Test (ACT) which comes for free with Visual Studio.NET. Preliminary results were surprising. Even though database access is slower than simply retrieving view state from the hidden __VIEWSTATE field, when the view state is large enough—and it is since our application manipulates a lot of dynamic data—the reduction in page download and postback time outweighted storing the view state to and retrieving from the database! In other words, by serving leaner pages we were able to process more requests within the same time span of a stress test cycle and save bandwith while persisting view state in the database.

"View State Is Invalid" Error Message When You Use Server.Transfer

Actually, this is exactly how KB316920 external link is titled. This KB article deals with an issue I've seen come up in newsgroups quite often.

Here's the gist of the problem. Suppose you have a web page. The first page has a MAC appended to its view state (which is done by default, remember?). Now, what if you need to call Server.Transfer and you want to preserve its QueryString and the Form collection? You may do so by calling an overloaded Server.Transfer and passing true as its second parameter.

Next, when this second page is invoked it receives the view state of the calling web form in its __VIEWSTATE hidden field. The view state authentication check will fail since the newly arrived view state is invalid on the second page.

The KB article makes its point clear—view state is page scoped and is valid for that page only. View state should not be transferred across pages.

Summary

View state is of tremendous value for web developers. It abstracts you from the dirty work of persisting and restoring control values between page postbacks. However its ease of use has a price tag on it and you need to clearly understand when you do need to maintain view state at the expense of serving larger pages, and when you don't at the expense of inability to facilitate dynamic data. Decide judiciously what to store in the view state to avoid incurring overhead. This article taught you how to secure the view state from tampering. You also learned how to take it out of web forms and persist it on the server.

抱歉!评论已关闭.