Introduction
First of all, I will say hi to all. It is my very first attempt to write an article on any forum. I read somewhere that you can check that whether you are learning or not if "you are making mistakes provided everytime you make a new mistake". Well, I make lots of mistakes more than normal people :). Anyway in this article, if you see some area of improvement, then please let me know so that next time I just make a new mistake :).
Basically, this article will talk about VIEWSTATE
and server response customization techniques. Well, you know what is VIEWSTATE
and why ASP.NET introduced it. VIEWSTATE
(as the name tells) keep the state of the controls rendered by the server on client agents (e.g. Web Browsers, etc.) so that on next postback, it can inform the server about the previous state of the page control hierarchy, etc. All in all, what we can say is that it is a very important page element to keep state information as well as some other information to server (e.g. whether page is POSTBACK, etc.).
There are some other ways to keep the state information like server session but this consumes the server resources and may not be scalable solutions. Lots of people use the first option that is VIEWSTATE
to maintain the state information of server controls. VIEWSTATE
is saved in a hidden field named __VIEWSTATE
.
One of the big problems with VIEWSTATE
is that it travels with each request and response and consumes precious bandwidth. So, in this article, we will look around for a way to minimize the VIEWSTATE
size. I have developed a little HTTPMODULE
to customize the Page rendered content.
Background
It is better that you just have a look at these to better understand what I am trying to do here
Let's Have A Look Inside Page Class
In order to fully understand the concept and use it in your own scenario, I feel it is important to give you some inside idea how Page
class is maintaining VIEWSTATE
. If you are already familiar with it, you can skip this section.
Page
class provides a number of virtual functions and properties to override. In our case, we just need to override one such property of Page
class, i.e. PageStatePersister
. Internal Implementation of Page
's PageStatePersister
property is shown below:
1
2 load controls state (VIEWSTATE/CONTROLSTATE)
3 protected virtual PageStatePersister PageStatePersister
4 {
5 get
6 {
7 if (this._persister == null)
8 {
9 PageAdapter pageAdapter = this.PageAdapter;
10 if (pageAdapter != null)
11 {
12 this._persister = pageAdapter.GetStatePersister();
13 }
14 if (this._persister == null)
15 {
16 this._persister = new HiddenFieldPageStatePersister(this);
17 }
18 }
19 return this._persister;
20 }
21 }
As highlighted in the above code snippet, PageAdapter
is used to get PageStatePersister
. Default persister is HiddenFieldPageStatePersister
.
1
2 public virtual PageStatePersister GetStatePersister()
3 {
4 return new HiddenFieldPageStatePersister(base.Page);
5 }
As far as my knowledge is concerned, there are two Page
State Persisters provided by the ASP.NET FCL (Framework Class Library).
HidenFieldPageStatePersister
(Default):
It will use a hidden field such as _VIEWSTATE
to keep the state in Base64
encoded string
. SessionPageStatePersister
: It is used to save the state in Session
by using server resources.
How to Use this Code
Here in this section, I will give you a step by step demo about how we can achieve our final goal, i.e., to build custom State Persister to save and load compressed
VIEWSTATE
.
1 public class ZipPageStatePersister : PageStatePersister
2 {
3 public ZipPageStatePersister(Page _page);
4
5 public static string CompressData(byte[] data);
6 public static byte[] Decompress(byte[] data);
7 public override void Load();
8 public override void Save();
9 }
In the above code snippet, I just gave you an outline of my custom PageStatePersister
Type. It has two abstract
methods, Load and Save implementation along with two static
custom Compression
/DeCompression
methods. Let's have a look at each one by one...
1 public override void Save()
2 {
3 if (this.ViewState != null || this.ControlState != null)
4 {
5 using (StringWriter _writer = new StringWriter())
6 {
7 Pair _pair = new Pair(this.ViewState, this.ControlState);
8
9 LosFormatter _formatter = new LosFormatter();
10 _formatter.Serialize(_writer, _pair);
11
12 string _zippedData =
13 CompressData(Convert.FromBase64String(_writer.ToString()));
14
15 this.Page.ClientScript.RegisterHiddenField("_VSTATE
16 , _zippedData);
17 }
18 }
19 }
20
In the above code, we are checking that VIEWSTATE
or ControlSate
are not both null
. And we are using LOSFormatter
for serialization of our states. One of the very important points to note is that we are registering hidden fields to our page instance, the first parameter is the name of that hidden field and the second parameter is value (in our case, it custom serialize state compressed and then encoded in Base64 string
).
1 public override void Load()
2 {
3 if (!string.IsNullOrEmpty(this.Page.Request.Form["_VSTATE"]))
4 {
5 byte[] bytes =Convert.FromBase64String(this.Page.Request.Form[
6 _VSTATE"]);
7
8 byte[] _decompressed = Decompress(bytes);
9 StringReader _reader = new
10 StringReader(Convert.ToBase64String(_decompressed));
11
12 LosFormatter _formatter = new LosFormatter();
13 Pair _pair = _formatter.Deserialize(_reader.ReadToEnd()) as Pair;
14
15 if (_pair != null)
16 {
17 this.ViewState = _pair.First;
18 this.ControlState = _pair.Second;
19 }
20 }
21 }
22
When our page is loaded (in the case of POSTBACK
), then hidden field "_VSTATE
" (first argument of RegisterHiddenField
method described above); is keeping the state of our Page
. What is your guess - how we will load that state, you are guessing hmmmmm.... yes, you are right. It is load
method that will do the trick this time. In the above code, you can see that we are doing exactly the opposite of what we did in the save
method. First, we are decoding state information from Base64
to byte array and decompressing it using GzipCompressor
. And the next few lines, I don't think need explanation. Quite simple, ahhhhhhha?
OK, I am not showing compression and decompression code here because in those methods, I am just using GZipCompressor
class. Anyway, now we are ready to override Page
's property PageStatePersister
. And here is the code...
1 public partial class WebForm1 : System.Web.UI.Page
2 {
3 private ZipPageStatePersister _zipperPSP;
4
5 protected WebForm1()
6 {
7 this._zipperPSP = new ZipPageStatePersister(this);
8 }
9
10 11 12 13 protected override PageStatePersister PageStatePersister
14 {
15 get
16 {
17 return _zipperPSP;
18
19 }
20 }
21 }
22
We are creating a private
member of our newly created State Persister class and returning it in PageStatePersister
instead of default PageStatePersister
i.e. HiddenFieldPageStatePersister
; Well we are done here. Now when you will view your page in browser, you can see a hidden field name _VSTATE
containing the state information. But you will probably notice that old _VIEWSTATE
hidden field is still there, but empty in this case.
Getting Rid of _VIEWSTATE Field
When I saw this behavior, then the first solution that came into my mind was to use HTTPHANDLER
. But we cannot write to HttpApplication.Context.Resoponse.OutputStream
. Fortunately, I found somebody has already encountered it. The solution is to keep the client stream and then create own customized stream to write the output. I will not explain the whole procedure here as you can find, but I will just explain what I did to remove _VIEWSTATE
field.
StdViewStateRemover is implementing IHttpModule and VSStream is nested type extending Stream class to customize the output in its write method
Here is the write
method that I used to get rid of standard _VIEWSTATE
field.
1 public override void Write(byte[] buffer, int offset, int count)
2 {
3 System.Text.UTF8Encoding utf8 = new System.Text.UTF8Encoding();
4 System.Text.StringBuilder strBuff = new
5 System.Text.StringBuilder(utf8.GetString(buffer));
6
7
8 Regex reg =
9 new Regex("<input type=\"text\" name=\"__VIEWSTATE\"[\\w\\W]* />");
10
11 string _temp = reg.Replace(strBuff.ToString(), string.Empty);
12 strBuff.Remove(0, strBuff.Length - 1).Append(_temp);
13
14 mSR.Write(strBuff.ToString());
15 }
HTTPModule Registering in web.config
In order to HttpModule
, you have to register module in web.config:
1 <httpmodules>
2 <add type="WebApplication1.StdViewStateRemover, WebApplication1 "
3 name="StdViewStateRemover">
4 </add>
5 </httpmodules>
6
Now when you view the page in browser, then in source, you can easily verify that _VSTATE
exists with compressed Base64
encoded string
.
Points of Interest
OK. That's it for now. But you have got an idea how you can extend this HttpModule
to customize page response. If you don't want to extend PageStatePersister
, then you can also compress viewstate
in Write
method of custom output stream.
Now, I am trying to find out how much this solution is promising for me to utilize in large VIEWSTATE
case and how much it has an impact on my server's performance while compressing/decompressing.