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.
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.
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
encodedstring. 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.
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
. 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!

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).
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.
|
|
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 TypeConverterfor it
to improve performance.
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.
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" />
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
and StateItem
.
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.
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.
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.
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 Pageclass
has a very useful property, ViewStateUserKey
. 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.
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.
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 yourweb.config:
<machineKey
validationKey="value,[IsolateApps]"
decryptionKey="value,[IsolateApps]"
validation="SHA1|MD5|3DES" />
Let's take a closer look at machineKey attributes:
1.
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.
2.
decryptionKey specifies
the key used to encrypt and decrypt the view state whenvalidation="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.
3.
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
. 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.
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.
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.
Actually, this is exactly how KB316920
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 callServer.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.
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.
Hope it will help you to understand ViewState in details and its importance..'
Enjoy the day with Lots of smile and Happiness.....
No comments:
Post a Comment