Archive for the '.NET' Category

ASP.NET ViewState Compression and AJAX

There are many articles and blog posts out there that talk about ViewState in-depth and how to handle it when it becomes too large. A large ViewState slows down your user’s browsing experience due to larger page sizes. There are many ways to address this issue: decreasing your use of ViewState, storing the ViewState in Session or elsewhere, HTTP compression, and the topic of my post, compressing the ViewState hidden field value.

At my last project we had been using ViewState compression, and it had worked just fine for them for a couple of years. The client was using the same solution that I’m sure 90% of the community was, where you override the two Page methods:

LoadPageStateFromPersistenceMedium
SavePageStateToPersistenceMedium

and store the compressed ViewState in a hidden form field. Here’s an example from one such article:

public partial class MyPage : System.Web.UI.Page
{
	protected override object LoadPageStateFromPersistenceMedium()
	{
		string viewState = Request.Form["__VSTATE"];
		byte[] bytes = Convert.FromBase64String(viewState);
		bytes = Compressor.Decompress(bytes);
		LosFormatter formatter = new LosFormatter();
		return formatter.Deserialize(Convert.ToBase64String(bytes));
	}

	protected override void SavePageStateToPersistenceMedium(object viewState)
	{
		LosFormatter formatter = new LosFormatter();
		StringWriter writer = new StringWriter();
		formatter.Serialize(writer, viewState);
		string viewStateString = writer.ToString();
		byte[] bytes = Convert.FromBase64String(viewStateString);
		bytes = Compressor.Compress(bytes);
		ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes));
	}
}

Note that Compressor is a custom class that you would write to do the compression/decompression. See the article above for sample code using Microsoft’s built-in compression libraries.

On our new endeavor of using third-party controls that used AJAX we noticed a problem. It seems these controls do not like the ViewState being ‘moved’ to a different form field (here it was moved to __VSTATE instead of the standard __VIEWSTATE). There must be some hard-coded references to this field name, and they cannot properly track their own state. The controls loaded fine but most of the AJAX calls would not run all of the correct events on the server.

After some searching I came across a solution that still compresses the ViewState but does so in an AJAX-friendly manner by leaving it in the __VIEWSTATE field. Since it was a hard-to-find forum post I thought it warranted a little more visibility and discussion.

In ASP.NET 2.0, Microsoft added a class called PageStatePersister, which is also exposed a property on the Page class itself. The page events mentioned above are still there and used, but now they hand off the dirty work to the PageStatePersister. The PageStatePersister is responsible for loading and saving ViewState.

There are a couple of ways you can implement ViewState compression using these events and/or PageStatePersister. One way is that you could you write your own persister and override the PageStatePersister property on the Page to use your class instead.

In my case I decided to override the two Page events above and now work with the PageStatePersister within the events. First, let’s look at the LoadPageStateFromPersistenceMedium event:

protected override object LoadPageStateFromPersistenceMedium()
{
	String alteredViewState;
	byte[] bytes;
	Object rawViewState;
	LosFormatter fomatter = new LosFormatter();

	this.PageStatePersister.Load();

	alteredViewState = this.PageStatePersister.ViewState.ToString();

	bytes = Convert.FromBase64String(alteredViewState);
	bytes = Compressor.Decompress(bytes);

	rawViewState = fomatter.Deserialize(Convert.ToBase64String(bytes));

	return new Pair(this.PageStatePersister.ControlState, rawViewState);
}

You see we first tell the PageStatePersister to Load itself with the ViewState. We then decode it as a base64 string and decompress. It is then deserialized and placed into a Pair object with current control state. The Pair is what is returned.

And now for the SavePageStateToPersistenceMedium event:

protected override void SavePageStateToPersistenceMedium(object viewStateIn)
{
	LosFormatter fomatter = new LosFormatter();
	StringWriter writer = new StringWriter();
	Pair rawPair;
	Object rawViewState;
	String rawViewStateStr;
	String alteredViewState;
	byte[] bytes;

	if (viewStateIn is Pair)
	{
		rawPair = ((Pair)viewStateIn);
		this.PageStatePersister.ControlState = rawPair.First;
		rawViewState = rawPair.Second;
	}
	else
	{
		rawViewState = viewStateIn;
	}

	fomatter.Serialize(writer, rawViewState);
	rawViewStateStr = writer.ToString();

	bytes = Convert.FromBase64String(rawViewStateStr);
	bytes = Compressor.Compress(bytes);

	alteredViewState = Convert.ToBase64String(bytes);

	this.PageStatePersister.ViewState = alteredViewState;
	this.PageStatePersister.Save();
}

Here we take the viewStateIn, which is the normal ViewState and instead compress it before saving to the PageStatePersister.

Now our ViewState is compressed and stored in the standard __VIEWSTATE field and our AJAX-enabled controls should be happy!

Links and references:
ViewState Compression
Forum post - Ajax Beta 1, UpdatePanel and Viewstate issue
Get view state off __VIEWSTATE!

Using System.Web.Routing with Castle MonoRail

Several folks have already posted about using System.Web.Routing with standard ASP.NET WebForms, and I’d like to extend the concept for use with MonoRail. The default MonoRail router is not terribly sophisticated. It works by using pairs of regular expressions to match and then rewrite a url into /area/controller/action/id format. Here is an example from the documentation:

<rule>
	<pattern>(/blog/posts/)(\d+)/(\d+)/(.)*$</pattern>
	<replace><![CDATA[ /blog/view.rails?year=$2&month=$3 ]]></replace>
</rule>

It’s functional, and you can achieve most any route that you’d like, but the syntax is awkward. System.Web.Routing provides a much cleaner solution:

routes.Map(
   "MonthlyArchives",
   "/blog/posts/{year}/{month}",
   new { controller = "blog", action = "view" }
);

ScottGu gives a good tutorial.

Before we can get started, we’ll need to examine how both MonoRail and System.Web.Routing perform routing and IHttpHandler creation. Unfortunatley, an explanation of ASP.NET handlers and modules is beyond the scope of this post. Although dated, there are some good overviews.

System.Web.Routing runs as an IHttpModule, examines incoming requests, matches them against registered routes, and, if a match is found, uses the IRouteHandler associated with the matched route to create an IHttpHandler, which will process the request.

MonoRail on the other hand has a straightforward IHttpHandlerFactory implementation, MonoRailHttpHandlerFactory, which parses the request url for /area/controller/action.extension/id syntax. If the MonoRail routing module is installed, that module will rewrite the url and create a RouteMatch object in the current HttpContext.Items collection.

Therefore a successful integration will need to use the existing MonoRailHttpHandlerFactory infrastructure–directly creating controllers requires far too much supporting context which the factory provides–yet allow System.Web.Routing to inform the controller/action selection using its much richer RouteData output.

We’ll start by subclassing the MonoRailHttpHandlerFactory and adding the IRouteHandler interface:

public class MonoRailRouteHandler : MonoRailHttpHandlerFactory, IRouteHandler
{
    #region IRouteHandler Members

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var context = HttpContext.Current;
        return GetHandler(
            context,
            context.Request.RequestType,
            context.Request.RawUrl,
            context.Request.PhysicalApplicationPath);
    }

    #endregion
}

This handler is clearly just a passthrough to the underlying MonoRail GetHandler factory method. We’ll need to somehow use the routing information from System.Web.Routing to tell GetHandler what to create. Fortunately, Castle is very flexible here, and has a dependency, IUrlTokenizer, which takes a url and returns a UrlInfo structure. UrlInfo contains area, controller, and action properties (amongst other less interesting properties). Next lets build a IUrlTokenizer that just provides whichever UrlInfo it’s told to:

public class MockUrlTokenizer : IUrlTokenizer
{
    [ThreadStatic]
    private static UrlInfo _currentUrlInfo;

    public static UrlInfo CurrentUrlInfo
    {
        get { return _currentUrlInfo;}
        set { _currentUrlInfo = value; }
    }

    public void AddDefaultRule(string url, string area, string controller, string action)
    {
        throw new NotImplementedException();
    }

    public UrlInfo TokenizeUrl(string filePath, string pathInfo, Uri uri, bool isLocal, string appVirtualDir)
    {
        return CurrentUrlInfo;
    }
}

Finally we’ll modify our IRouteHandler to use the new UrlTokenizer as well as convert the System.Web.Routing RouteData to a MonoRail RouteMatch:

public class MonoRailRouteHandler : MonoRailHttpHandlerFactory, IRouteHandler
{
    public MonoRailRouteHandler()
    {
        UrlTokenizer = new MockUrlTokenizer();
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var context = HttpContext.Current;

        // parse urlinfo from the s.w.routing data
        var values = requestContext.RouteData.Values;
        MockUrlTokenizer.CurrentUrlInfo = new UrlInfo(
            context.Request.Url.Host,
            context.Request.Url.Host,
            context.Request.ApplicationPath,
            context.Request.Url.Scheme,
            context.Request.Url.Port,
            context.Request.RawUrl,
            values.ContainsKey("area") ? (string)values["area"] : String.Empty,
            values.ContainsKey("controller") ? (string)values["controller"] : String.Empty,
            values.ContainsKey("action") ? (string)values["action"] : String.Empty,
            String.Empty,
            context.Request.Url.Query
        );

        // create a monorail routematch
        var match = new RouteMatch();
        foreach (var pair in values)
        {
            match.Parameters.Add(pair.Key, pair.Value as string);
        }
        context.Items[RouteMatch.RouteMatchKey] = match;

        // allow standard monorail handler to proceed
        return GetHandler(
            context,
            context.Request.RequestType,
            context.Request.RawUrl,
            context.Request.PhysicalApplicationPath);
    }
}

Now you can simply register routes normally, using the new MonoRailRouteHandler.

Go ahead and grab the source file which includes a couple of extras:
MonoRailRouteHandler.cs.




Creative Commons Attribution 3.0 United States
Creative Commons Attribution 3.0 United States