Monday, July 28, 2014

Move ViewState out of the page

From this year I'm working on a ASP.NET Web Forms project. I'm currently tuning this project with some tweaks.
One of those tweaks is related to ViewState, IMHO the Achilles' heel of this framework.
Due to business decisions, budget etc... dev-team could not re-engineer the entire pages, so they decided to improve whatever possible.

Move ViewState out of the page... that means one thing: lighter pages!

ViewState, by default implementation,  is actually stored in a HiddenField, but you can move it everywhere: Session, Cache, Database...

You should know that pages have an overridable property called PageStatePersister that expects a System.Web.UI.PageStatePersister.
You can override it providing with a built-in PageStatePersister like HiddenFieldPageStatePersister or SessionStatePagePersister or create your custom PageStatePersister.

If you don't have a BasePage class you can inherit a PageAdapter, declare it inside deafult.browser file (App_Browser) and apply it to all your pages.

But you can also still take another way overriding Page methods : object LoadPageStateFromPersistenceMedium() and void SavePageStateToPersistenceMedium(object).

In this post I'll take care about this last way.

I created a simple interface I used in this way on my BasePage class:

private IViewStatePersister ViewStatePersister
{
    get { return new ViewStateOnCache(); } //my implementation
}

protected override object LoadPageStateFromPersistenceMedium()
{
    return ViewStatePersister.LoadViewState(GenerateKey());
}

protected override void SavePageStateToPersistenceMedium(object viewState)
{
    ViewStatePersister.SaveViewState(GenerateKey(), viewState);
}

//That how I recognize pages
private string GenerateKey()
{
 return String.Format("{0}_{1}",Session.SessionID, MakeValidFileName(Request.Url.PathAndQuery));
}

And then some implementations just for test!

//the interface
interface IViewStatePersister
{
   void SaveViewState(string key, object viewstate);
   object LoadViewState(string key);
   void RemoveViewState(string key);
}

//ViewState on file impl
class ViewStateOnFile : IViewStatePersister

   public readonly HttpServerUtility Server;

   public ViewStateOnFile(HttpServerUtility server)
   {
       Server = server;
   }

   private string GenerateFileName(string key)
   {
       string file = key + ".viewstate.txt";

       file = Path.Combine(Server.MapPath("~/ViewStateFiles") + "/" + file);

       return file;

   }

   public void SaveViewState(string key, object state)
   {
       using (var filestream = new FileStream(GenerateFileName(key), FileMode.Create))
       {
           new LosFormatter().Serialize(filestream, state);

           filestream.Flush();
       }
   }

   public object LoadViewState(string key)
   {
       using (var reader = new FileStream(GenerateFileName(key), FileMode.OpenOrCreate))
       {
           return new LosFormatter().Deserialize(reader);
       }
   }

   public void RemoveViewState(string key)
   {
       new FileInfo(GenerateFileName(key)).Delete();
   }
}

//ViewState on MongoDB impl (based on this post: http://highoncoding.com/Articles/699_Storing_ViewState_in_MongoDb_Database.aspx )
class ViewStateOnMongoDB : IViewStatePersister
{
    private const string DBName = "PageViewStates";
    private const string CollectionName = "ViewStateCollection";


    public void SaveViewState(string rawKey, object rawViewState)
    {
        var sw = new StringWriter();

        new LosFormatter().Serialize(sw, rawViewState);
        var viewState = sw.ToString();
        var key = rawKey.ToLower();

        var mongo = new Mongo();
        mongo.Connect();
        var db = mongo.getDB(DBName);
        var viewStateCollection = db.GetCollection(CollectionName);

        var document = new Document();
        document["Key"] = key;
        document["Value"] = viewState;

        var persistedDocument = GetByKey(key);

        if (persistedDocument != null)
        {
            persistedDocument["Value"] = viewState;
            viewStateCollection.Update(persistedDocument, new Document { { "Key", key } });
        }
        else
        {
            document["Value"] = viewState;
            viewStateCollection.Insert(document);
        }

        mongo.Disconnect();
        mongo.Dispose();
    }

    public object LoadViewState(string rawKey)
    {
        var key = rawKey.ToLower();

        var mongo = new Mongo();
        mongo.Connect();
        var db = mongo.getDB(DBName);
        var viewStateCollection = db.GetCollection(CollectionName);

        var spec = new Document();
        spec["Key"] = key;

        var document = viewStateCollection.FindOne(spec);

        return document == null ? null : new LosFormatter().Deserialize(document["Value"].ToString());
    }

    public void RemoveViewState(string key)
    {
  throw NotImplementedException();
    }

    private Document GetByKey(string key)
    {
        var mongo = new Mongo();
        mongo.Connect();
        var db = mongo.getDB(DBName);
        var viewStateCollection = db.GetCollection(CollectionName);

        var spec = new Document();
        spec["Key"] = key;

        var document = viewStateCollection.FindOne(spec);

        mongo.Disconnect();
        mongo.Dispose();

        return document;
    }

}
That's all!
You can try those tweaks and lighten your pages, saving bandwidth and gaining speed!

Obviously you'll get the best results putting ViewState inside Cache or Session because you can avoid serialization/deserialization time.
You can also try Redis, FileSystem, MongoDB, everything you want. But remember to keep the memory free.

Anyway..... my "very best" advice is... do not use ViewState :)

No comments:

Post a Comment