Thursday, March 08, 2007

Viewstate Plumbing: add the pipes after the walls are built

I hate plumbing. Repetitive code that you write on every web page. Viewstate is about the best thing ASP.NET ever invented, but they didn't spread its goodness through their framework. If I want a page variable in viewstate, I have to remember to put it there and pull it out, either via the ViewState[] collection or by actually overriding methods. Silly.

My ideal would be to simply mark a variable with an attribute -- "hey, I want this in my viewstate". Like so

[ViewState] protected int _myStatus;

But the ASP.NET framework's Page class wouldn't know what to do with this. Sadly, we can't change or extend methods of that class during run-time like we might in more dynamic languages, e.g. Ruby. But if we're willing to use a new base class, the plumbing is pretty easy to take care of. On load and save viewstate events, we simply scan our variables for those attributed appropriately. Then when I want a variable in ViewState, I just say so. The pipes get built for me and stay behind the walls, rather than intruding all over my Load and PreRender functions.

[AttributeUsage(AttributeTargets.Field)] public class ViewStateAttribute : Attribute {} /* inside your favorite Page subclass */ protected override void LoadViewState(object savedState) { base.LoadViewState(savedState); ForEachViewstateField(delegate(FieldInfo fi) { fi.SetValue(this, ViewState[fi.Name]); }); } private void ForEachViewstateField(Action a) { foreach (FieldInfo fi in GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { object[] atts = fi.GetCustomAttributes(typeof (ViewStateAttribute), true); if (atts.Length > 0) a(fi); } } protected override object SaveViewState() { ForEachViewstateField(delegate(FieldInfo fi) { ViewState[fi.Name] = fi.GetValue(this); }); return base.SaveViewState(); }

Labels: