ViewState: Various ways to reduce performance overhead(转)
Introduction
In this article, I am going to explore the ViewState. ViewState is one thing that I always like to use. It makes life easier. As we all know, Viewstate is one way of maintaining the state in web applications.
As we know, a web page is created every time when a page is posted to the server. This means the values that the user entered in the webpage are going to vanish after the postback or submit. To get rid of this problem, the ASP.NET framework provides a way to maintain these values by virtue of ViewState. When we enable the viewstate of any control, the value is going to be maintained on every postback or server roundtrip.
But how are these values maintained? It doesn't come free. ViewState uses a hidden variable that resides on the page to store control values. This means that if a page has lots of controls with viewstate enabled, the page size would become heavy, in several kilobytes; i.e., it will take longer to download the page. And also, on every postback, all the data is posted to the server, increasing the network traffic as well.
In new era applications, we use lots of heavy controls like gridviews etc., on our pages which makes the page size exponentially heavy. It is always recommended to use View State judiciously, and some programmers try to avoid using it because of the performance overhead.
Here, I am going to discuss how we can reduce the performance overhead caused by View State.
What is ViewState
As we all know, the Web is a stateless medium; i.e., states are not maintained between requests by default. A web page is created every time a page is posted to the server. The ASP.NET framework provides us several ways to maintain the state. These are:
Here, we going to discuss one of the client state management techniques: ViewState. We can persist data during postbacks with the help of ViewState. Why do we call ViewState as client-side? Because the data is stored on the page, and if we move from one page to another, the data would be lost.
So where does the ViewState gets stored? The ViewState is stored on the page in a hidden field named__viewstate
.
I will not discuss the basics of ViewState in detail; for details, have a look at a very good article here on CodeProject:Beginner's Guide To View State [^].
Problems with ViewState
In new era applications, we generally have lots of rich and heavy controls on our page, and also provide lots of functionality on the page with the help of latest technologies like AJAX etc. To accomplish our tasks, we use ViewState a lot, but as we know, it doesn't come free - it has a performance overhead.
The ViewState is stored on the page in the form of a hidden variable. Its always advised to use the ViewState as little as possible. We also have other ways to reduce the performance overhead. Here, I am going to discuss the following ways:
- By compressing/decompressing the ViewState
- Store the ViewState in another server, say on a web server
We will see all these with an example.
First, let's see a normal scenario!!
Here, I have a webpage with a GridView
which shows a lot of data (16 rows). I have enabled ViewState so that I don't need to repopulate the whole grid on every postback. But what am I paying for that? Let's see the example.
First, let's see the page size on postback with enableviewstate=true
, which is true by default.
Here we can see the request size is 4.9 K. Also, we can see the ViewState of the page by viewing the View Source. Let's see it:
You can see the __viewstate
variable on the screen and the amount of data it has. If we have several controls like this, imagine your page size will be.
But the benefit is, we dont need to hit the database to fetch the data and bind it on every postback.
Now, let's see what happens when we disable the viewstate; i.e., set enableviewstate=false
.
and there is no data on the page because I loaded the data for the first time on page load. On subsequent postbacks, I don't bind it. That's why when I click on PostBack, the data gets lost, but the page size is here is just 773 B. Also, let's examine the View Source of the page:
We see the __viewstate
variable, but with very less data. Although we have disabled viewstate, ASP.NET uses viewstate for some data to maintain the page state, but this is very little and not going to cause any performance overhead.
But what do we get from the viewstate? We don't need to repopulate the grid again. Just bind it on the first page-load, and on future postbacks, the data gets populated from the viewstate. Note: Here I am using Firebug for the analysis purpose.
Now we can imagine the overhead of viewstate. You can check your application (if you are using viewstate) and see the page size is increased by the viewstate. Now we will discuss one way to reduce the page size.
View State Compression/Decompression
We can compress the viewstate to reduce the page size. By compressing the viewstate, we can reduce the viewstate size by more than 30%. But here, the question arises, when do we compress and decompress the viewstate? For that, we have to dig into the page life cycle. As we know, viewstate is used by ASP.NET to populate controls. So we should compress the viewstate after all the changes are done in the viewstate, and save it after compression, and we have to decompress the viewstate just before it is used by ASP.NET to load all the controls from the viewstate. Let's jump to the page life cycle and see how we can fit our requirement.
As we can see, there are two methods. SaveViewState
is responsible for collecting the view state information for all of the controls in the control hierarchy and persist it in the __viewstate
hidden form field. The view state is serialized to the hidden form field in the SavePageStateToPersistenceMedium()
method during the save view state stage, and is deserialized by the Page
class' LoadPageStateFromPersistenceMedium()
method in theload view state stage. In these methods, we can compress and decompress the viewstate. Let's take a pictorial view.
here, we need to override the methods SavePageStateToPersistenceMedium()
for compressing the viewstate, and SavePageStateToPersistenceMedium()
for decompressing the viewstate. I am going to use GZip for compression (provided by .NET). And this is available in the namespace System.IO.Compression
. Let's jump to the code.
I have a class CompressViewState
that is inherited from System.Web.UI.Page
. I have overridden the above two methods. I also made two private methods: for compressing a byte stream, and for decompressing it. Let's have a look at the compressing one:
/// This Method takes the byte stream as parameter /// and return a compressed bytestream. /// For compression it uses GZipStream private byte[] Compress(byte[] b) { MemoryStream ms = new MemoryStream(); GZipStream zs = new GZipStream(ms, CompressionMode.Compress, true); zs.Write(b, 0, b.Length); zs.Close(); return ms.ToArray(); }
As you can see, the Compress
method takes a byte array as a parameter and returns compressed data in byte array form. In the method, I have used GZipStream
for compressing the data. Now, let's look at the decompressing one:
/// This method takes the compressed byte stream as parameter /// and return a decompressed bytestream. private byte[] Decompress(byte[] b) { MemoryStream ms = new MemoryStream(); GZipStream zs = new GZipStream(new MemoryStream(b), CompressionMode.Decompress, true); byte[] buffer = new byte[4096]; int size; while (true) { size = zs.Read(buffer, 0, buffer.Length); if (size > 0) ms.Write(buffer, 0, size); else break; } zs.Close(); return ms.ToArray(); }
As you can see, this method takes compressed data as a parameter and returns decompressed data.
Let's now look at the overridden methods. First, LoadPageStateFromPersistenceMedium
:
protected override object LoadPageStateFromPersistenceMedium() { System.Web.UI.PageStatePersister pageStatePersister1 = this.PageStatePersister; pageStatePersister1.Load(); String vState = pageStatePersister1.ViewState.ToString(); byte[] pBytes = System.Convert.FromBase64String(vState); pBytes = Decompress(pBytes); LosFormatter mFormat = new LosFormatter(); Object ViewState = mFormat.Deserialize(System.Convert.ToBase64String(pBytes)); return new Pair(pageStatePersister1.ControlState, ViewState); }
As you can see, in this method, we have taken the viewstate in a string variable from PageStatePersister
, decompressed it (as it is compressed in the Save viewstate stage; we'll discuss after this), and deserialized it in anObject
, and returned the new Pair
.
Now moving to SavePageStateToPersistenceMedium
.
protected override void SavePageStateToPersistenceMedium(Object pViewState) { Pair pair1; System.Web.UI.PageStatePersister pageStatePersister1 = this.PageStatePersister; Object ViewState; if (pViewState is Pair) { pair1 = ((Pair)pViewState); pageStatePersister1.ControlState = pair1.First; ViewState = pair1.Second; } else { ViewState = pViewState; } LosFormatter mFormat = new LosFormatter(); StringWriter mWriter = new StringWriter(); mFormat.Serialize(mWriter, ViewState); String mViewStateStr = mWriter.ToString(); byte[] pBytes = System.Convert.FromBase64String(mViewStateStr); pBytes = Compress(pBytes); String vStateStr = System.Convert.ToBase64String(pBytes); pageStatePersister1.ViewState = vStateStr; pageStatePersister1.Save(); }
Here, we read the viewstate information from pageStatePersister
and then serialize it, and finally, after compressing it, save it in a page persister.
This is all about compressing and decompressing the viewstate. I have put the class in the App_Code folder so that it is available to the entire application. And wherever we need to use it, we should inherit it fromCompressViewState
. Thus, we can reduce the viewstate size significantly and make our applications perform much better :).
Now, let's move to another methodology: saving the viewstate into the file system.
Points to Remember
This methodology should be used only if you have enough data in the viewstate; else, you will need more time in compressing and decompressing the viewstate between requests without much gain.
Saving View State on the Server
I am going to explain two approaches here:
- Using Session
- Using a hidden field
Using Session
This way, we can reduce the viewstate size to zero. There is no performance overhead on a round trip to the server. Still, there is a bit of overhead in reading and saving the viewstate to the file system. We'll discuss this later.
Now in this methodology, we need to use two methods:
LoadPageStateFromPersistenceMedium
SavePageStateToPersistenceMedium
Now we will see how to save the viewstate to the file system. But how will we save the ViewState? What would be the name of the file? Does it have to be unique for every user? Do we need multiple files for the same user session.
There are a lot of questions. One answer, we don't need multiple files for the same user at the same time because ViewState is confined to every page, and when the user moves to another page, the previous page's ViewState gets ripped off. At a time, we just need to save the current page's ViewState.
As session ID is unique for every user, why should we not use the session ID in the file to make it unique, and whenever we'll need to get the file, we'll just pick it by using the session ID.
Let's jump to the code.
Here, I have a file name PersistViewStateToFileSystem
that is inherited from System.web.UI.Page
and resides in the App_code folder so that it is available through the whole application. Here, we have a property which returns the full path of the ViewState file. I used the session ID as a name of the file, and .vs as the extension. We also have a property that returns the path of the ViewState file:
public string ViewStateFilePath { get { if (Session["viewstateFilPath"] == null) { string folderName = Path.Combine(Request.PhysicalApplicationPath, "PersistedViewState"); string fileName = Session.SessionID + "-" + Path.GetFileNameWithoutExtension(Request.Path).Replace("/", "-") + ".vs"; string filepath = Path.Combine(folderName, fileName); Session["viewstateFilPath"] = filepath; } return Session["viewstateFilPath"].ToString(); } }
Again, here we have implemented both the functions.
LoadPageStateFromPersistenceMedium
loads the file from the ViewState file, if it exists. Let's have a look at the code for LoadPageStateFromPersistenceMedium
:
protected override object LoadPageStateFromPersistenceMedium() { // determine the file to access if (!File.Exists(ViewStateFilePath)) return null; else { // open the file StreamReader sr = File.OpenText(ViewStateFilePath); string viewStateString = sr.ReadToEnd(); sr.Close(); // deserialize the string LosFormatter los = new LosFormatter(); return los.Deserialize(viewStateString); } }
and SavePageStateToPersistenceMedium
saves the file in the file system instead of pagepersister
. Moving to the code for LoadPageStateFromPersistenceMedium
:
protected override void SavePageStateToPersistenceMedium(object state) { LosFormatter los = new LosFormatter(); StringWriter sw = new StringWriter(); los.Serialize(sw, state); StreamWriter w = File.CreateText(ViewStateFilePath); w.Write(sw.ToString()); w.Close(); sw.Close(); }
We need to inherit our page from the class PersistViewStateToFileSystem
to use it.
Removing ViewState File from the Server
What is the best time to remove the ViewState file from the server? Our requirement is to remove the file at session end. So we can use the Application level Session_End
(that is defined in the Global.asax file) to remove the file from the web server. I save the ViewState file path in the session, and access the file path from there. Let's see the code for Session_End
.
void Session_End(object sender, EventArgs e) { //Deleting the viewstate file string filePath = Session["viewstateFilPath"] as string; if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) { File.Delete(filePath); } }
You can get the full sample application in the attachment.
Now, let's move to our example. Let's see the request size.
It's just 2.5 K. We can see that our page size has reduced dramatically. Let's now see the View Source of the page:
Cool! There is no data in the ViewState variable.
Points to Remember
- This methodology only works if cookies are not disabled on client machines.
- We need to remove the ViewState file after using it. Here, I have taken the approach of removing it at
Session_End
, which works only inInProc
mode. - Only use if ViewState size is heavy, because it will take more to save the ViewState in the file system and on reading.
- We can also compress the ViewState while saving the ViewState on the server.
- This option might not work on a webfarms and webgarden scenario.
Using a Hidden Field
First, I would say thanks to all who shared their valuable feedback. I am adding a section because of them. As the session ID approach has many limitations as mentioned in the Points to Remember section, I have added the following new approach.
Using a hidden field
We can use a GUID instead of session ID to uniquely identify the ViewState file. And to get the GUID, we will use a hidden field to store the GUID so that we can get the file name as and when we require. So now here, we only need to change the View State file path property, and need to have a hidden field on every page. The hidden field on the page can be as:
<asp:hiddenfield runat="server" id="hfVSFileName">
So actually, in my demo application, I have added a key isSessionId
in the app.config
section of web.config; if it set true, then we are using the Session ID approach, else the hidden field approach. Let's look at the property code:
public string ViewStateFilePath { get { bool isSessionId = Convert.ToBoolean( System.Configuration.ConfigurationManager.AppSettings["isSessionId"]); string folderName = Path.Combine(Request.PhysicalApplicationPath, "PersistedViewState"); string fileName = string.Empty; string filepath = string.Empty; if (!isSessionId) { HiddenField hfVSFileName = null; string VSFileName = ""; // Get the HiddenField Key from the page hfVSFileName = FindControl(this, "hfVSFileName") as HiddenField; // Get the HiddenField value from the page string hfVSFileNameVal = GetValue(hfVSFileName.UniqueID.ToString()); if (!string.IsNullOrEmpty(hfVSFileNameVal)) { VSFileName = hfVSFileNameVal; } if (!Page.IsPostBack) { VSFileName = GenerateGUID(); hfVSFileName.Value = VSFileName; //Removing files from Server RemoveFilesfromServer(); } fileName = VSFileName + "-" + Path.GetFileNameWithoutExtension( Request.Path).Replace("/", "-") + ".vs"; filepath = Path.Combine(folderName, fileName); return filepath; } else { if (Session["viewstateFilPath"] == null) { fileName = Session.SessionID + "-" + Path.GetFileNameWithoutExtension( Request.Path).Replace("/", "-") + ".vs"; filepath = Path.Combine(folderName, fileName); Session["viewstateFilPath"] = filepath; } return Session["viewstateFilPath"].ToString(); } } }
In the hidden field approach, I am creating a GUID for uniqueness of the filename, and also, it is created only once on page load.
Removing ViewState Files From the Server
I have made a change regarding removing files from the server. Here, I am removing files from the server which are older than 3 days:
private void RemoveFilesfromServer() { try { string folderName = Path.Combine(Request.PhysicalApplicationPath, "PersistedViewState"); DirectoryInfo _Directory = new DirectoryInfo(folderName); FileInfo[] files = _Directory.GetFiles(); DateTime threshold = DateTime.Now.AddDays(-3); foreach (FileInfo file in files) { if (file.CreationTime <= threshold) file.Delete(); } } catch (Exception ex) { throw new ApplicationException("Removing Files from Server"); } }
I am calling this method once on every new page request. You can call this method based on your design. We can also make the number of days configurable.
Conclusion
At last, I just want to say, since we generally use lots of ViewState controls on our page, at least we should use one of the discussed approaches.