If you have worked with ASP.NET MVC 3, you are probably aware of the ViewBag
property which allows you to pass data from
the Controller
to the View by using dynamic properties instead of hard coding strings. While the properties still are not strongly typed and
the compiler does
not check them, it can provide an advantage. For example, you can avoid casting as it returns dynamic objects. This post will show how
to use the same approach in ASP.NET Web Forms for the ViewState
property giving us a dynamic viewstate class.
In order to access the properties which do not exist at compile time the class has to inherit from
DynamicObject
. This will allow us
to override methods which are called when we get or set dynamic properties. In these methods we will use the ViewState of the current page to store and extract values.
To start with we need let’s create the DynamicViewState
class and base page class which will contain the
ViewBag
property:
public class DynamicViewState : DynamicObject
{
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return base.TrySetMember(binder, value);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return base.TryGetMember(binder, out result);
}
}
public class BasePage : Page
{
private readonly dynamic viewBag;
protected dynamic ViewBag
{
get { return viewBag; }
}
}
The TryGetMember
and TrySetMember
methods are invoked whenever we get or set properties of
ViewBag
instance. This means that we should have access
to ViewState of the current page so let’s pass the page to the constructor of
DynamicViewState
:
public class DynamicViewState : DynamicObject
{
private readonly BasePage basePage;
public DynamicViewState(BasePage basePage)
{
this.basePage = basePage;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
return base.TrySetMember(binder, value);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return base.TryGetMember(binder, out result);
}
}
public class BasePage : Page
{
private readonly dynamic viewBag;
public BasePage()
{
viewBag = new DynamicViewState(this);
}
protected dynamic ViewBag
{
get { return viewBag; }
}
}
We now have the page but as ViewState
is protected property, we cannot access it from our methods. We could add methods for storing and retrieving
items from the ViewState but that would allow all other classes to manipulate the page’s ViewState. Instead a better option is to make
the DynamicViewState
class nested. This way we will be able to access the
ViewState
property without exposing it to the outside world.
The implementation of the DynamicViewState
now looks like this:
public class BasePage : Page
{
private class DynamicViewState : DynamicObject
{
private readonly BasePage basePage;
public DynamicViewState(BasePage basePage)
{
this.basePage = basePage;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
basePage.ViewState[binder.Name] = value;
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = basePage.ViewState[binder.Name];
return true;
}
}
private readonly dynamic viewBag;
public BasePage()
{
viewBag = new DynamicViewState(this);
}
protected dynamic ViewBag
{
get { return viewBag; }
}
}
Sample usage looks like this:
public partial class SamplePage : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
ViewBag.loadCount++;
loadLabel.Text = string.Format("Load count: {0}", ViewState["loadCount"]);
}
else
{
ViewBag.loadCount = 0;
loadLabel.Text = "Load count: 0";
}
}
}
As you can see we can mix ViewBag and ViewState together without any issues as ViewBag is simply a façade over ViewState.
If we also override TrySetIndex
and TryGetIndex
methods we can access
ViewBag
properties by using indexes exactly in the same way as ViewState. This is how final implementation look like:
public class BasePage : Page
{
private class DynamicViewState : DynamicObject
{
private readonly BasePage basePage;
public DynamicViewState(BasePage basePage)
{
this.basePage = basePage;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
basePage.ViewState[binder.Name] = value;
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = basePage.ViewState[binder.Name];
return true;
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
if (indexes.Length == 1)
{
basePage.ViewState[indexes[0].ToString()] = value;
return true;
}
return base.TrySetIndex(binder, indexes, value);
}
public override bool TryGetIndex(GetIndexBinder binder,
object[] indexes, out object result)
{
if (indexes.Length == 1)
{
result = basePage.ViewState[indexes[0].ToString()];
return true;
}
return base.TryGetIndex(binder, indexes, out result);
}
}
private readonly dynamic viewBag;
public BasePage()
{
viewBag = new DynamicViewState(this);
}
protected dynamic ViewBag
{
get { return viewBag; }
}
}
Apart from dynamic ViewState, the above approach can be also used for providing similar implementations for Session, Cache, and similar objects.
Full source code with a demo application is available at Dynamic ViewState in ASP.NET WebForms.