开发者

How to remove or compress your asp.net viewstate

开发者 https://www.devze.com 2023-02-05 00:19 出处:网络
Just spent a lot of time exorcising asp.net\'s large (but understandably use开发者_StackOverflow中文版ful) viewstate from an app, and i think it\'s worth sharing how it\'s done.

Just spent a lot of time exorcising asp.net's large (but understandably use开发者_StackOverflow中文版ful) viewstate from an app, and i think it's worth sharing how it's done.

Basically, i want this question to be open to all solutions for shrinking/compressing/removing viewstate.


First easy option, use the built-in SessionPageStatePersister class. What this does is keep the viewstate on the session on the server, rather than sending it to the client. However, it still sends a smaller viewstate down so isn't all roses:

using System.Web.UI;
... the following goes in your Page class (eg your .aspx.cs) ...
PageStatePersister pageStatePersister;
protected override PageStatePersister PageStatePersister
{
  get
  {
    // Unlike as exemplified in the MSDN docs, we cannot simply return a new PageStatePersister
    // every call to this property, as it causes problems
    return pageStatePersister ?? (pageStatePersister = new SessionPageStatePersister(this));
  }
}

This method shrunk a particularly large postback from 100k to 80k. Not great, but a good start.


Switch to ASP.NET MVC! No ViewState!


Another better option, roll your own PageStatePersister. Here's mine, heavily inspired by http://aspalliance.com/72:

using System.Web.UI;

... in your page class:

PageStatePersister pageStatePersister;
protected override PageStatePersister PageStatePersister
{
  get
  {
    // Unlike as exemplified in the MSDN docs, we cannot simply return a new PageStatePersister
    // every call to this property, as it causes problems
    return pageStatePersister ?? (pageStatePersister = new BetterSessionPageStatePersister(this));
  }
}

... in your BetterSessionPageStatePersister.cs:

/// <summary>
/// This class allows the viewstate to be kept server-side, so that postbacks are as small as possible.
/// It is similar to the built-in 'SessionPageStatePersister', but it yields smaller postbacks,
/// because the SessionPageStatePersister still leaves some viewstate (possibly it leaves the controlstate)
/// in the postback.
/// </summary>
class BetterSessionPageStatePersister : PageStatePersister
{
  public BetterSessionPageStatePersister(Page page)
    : base(page)
  { }

  const string ViewStateFieldName = "__VIEWSTATEKEY";
  const string ViewStateKeyPrefix = "ViewState_";
  const string RecentViewStateQueue = "ViewStateQueue";
  const int RecentViewStateQueueMaxLength = 5;

  public override void Load()
  {
    // The cache key for this viewstate is stored in a hidden field, so grab it
    string viewStateKey = Page.Request.Form[ViewStateFieldName] as string;

    // Grab the viewstate data using the key to look it up
    if (viewStateKey != null)
    {
      Pair p = (Pair)Page.Session[viewStateKey];
      ViewState = p.First;
      ControlState = p.Second;
    }
  }

  public override void Save()
  {
    // Give this viewstate a random key
    string viewStateKey = ViewStateKeyPrefix + Guid.NewGuid().ToString();

    // Store the view and control state
    Page.Session[viewStateKey] = new Pair(ViewState, ControlState);

    // Store the viewstate's key in a hidden field, so on postback we can grab it from the cache
    Page.ClientScript.RegisterHiddenField(ViewStateFieldName, viewStateKey);

    // Some tidying up: keep track of the X most recent viewstates for this user, and remove old ones
    var recent = Page.Session[RecentViewStateQueue] as Queue<string>;
    if (recent == null) Page.Session[RecentViewStateQueue] = recent = new Queue<string>();
    recent.Enqueue(viewStateKey); // Add this new one so it'll get removed later
    while (recent.Count > RecentViewStateQueueMaxLength) // If we've got lots in the queue, remove the old ones
      Page.Session.Remove(recent.Dequeue());
  }
}


It's important at first to understand what the heck viewstate is and why you want it in the first place. After that, it's just a matter of being mindful of what the app is doing for you and remembering to attach the UseViewState="false" to all the elements that would normally use viewstate.

Now to remember why it's useful, you'll have a definite need to retrieve things more often manually.

A time and a place for all tools, yes?


Completely get rid of it:

    protected override object LoadPageStateFromPersistenceMedium()
    {
        return null;
    }

    protected override void SavePageStateToPersistenceMedium(object viewState)
    {
    }


You can try it or it!


You can, with a little trickery, hijack just the serialization of the page state by deriving from System.Web.Page and override the PageStatePersister property:

    private PageStatePersister _pageStatePersister = null;
    protected override PageStatePersister PageStatePersister
    {
        get { return _pageStatePersister ?? (_pageStatePersister = new PersistState(this)); }
    }

Once you've done this you can derive a new instance from HiddenFieldPageStatePersister and from there use reflection to change the persistence implementation:

    class PersistState : HiddenFieldPageStatePersister, IStateFormatter
    {
        public PersistState(Page p) : base(p)
        {
            FieldInfo f = typeof(PageStatePersister).GetField("_stateFormatter", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField);
            f.SetValue(this, this);
        }

        object IStateFormatter.Deserialize(string serializedState)
        {
            BinaryFormatter f = new BinaryFormatter();
            using (GZipStream gz = new GZipStream(new MemoryStream(Convert.FromBase64String(serializedState)), CompressionMode.Decompress, false))
                return f.Deserialize(gz);                    
        }

        string IStateFormatter.Serialize(object state)
        {
            BinaryFormatter f = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                using (GZipStream gz = new GZipStream(ms, CompressionMode.Compress, true))
                    f.Serialize(gz, state);
                return Convert.ToBase64String(ms.ToArray());
            }
        }
    }

BEWARE

This is an example only for exploratory purposes. The above code IS A SECURITY RISK as it does not sign and encrypt the payload and therefore can easily be hacked by anyone attempting to do your site harm.

Again DO NOT USE THIS CODE without a full and complete understanding of security, cryptography, and .Net serialization.

</warning>

The real problem, as other have said, is the use of page state to begin with. The easiest solution to a poorly written ASP.NET app that makes heavy use of page state is to stand up a state server and use the SessionPageStatePersister.


One method that we have used in my company is to remove most of it by removing the runat="server" calls. Then we use javascript, or a good javascript library like jQuery or Prototype, to populate the HTML elements using ajax calls to the server.

My boss did a lot of work with a website that had several megs of viewstate data. He used the above method and it "works great with no viewstate".

0

精彩评论

暂无评论...
验证码 换一张
取 消