If you have worked on asp.net forms then you would have noticed the issue with viewstate from
a maintenance point of view. If we need
to temporarily keep a value during post backs then one of the ways to do it is
to store into page or control viewstate. Most of the rush jobs will introduce viewstate with magic strings (e.g. ViewState[“TestId”]) through out the page and soon you will
see the a maintenance monster getting ready to overpower you. One of the ways to isolate this problem is to
re-factor this code (view state magic strings) to properties.By this technique of limiting viewstate usage to a property we can achieve type safety and localize
the issue.
But the code might look like this and its a good start as this approach gives some type safety.
public long TestId
{
get
{
if (ViewState["TestId"] != null)
{
return System.Convert.ToInt64(ViewState["TestId"]);
}
else
{
return 0;
}
}
set
{
ViewState["TestId"] = value;
}
}
While looking at a nicer way to solve this issue i came across with a code project blog which talks about creating reusable attributes.By applying this attribute based programming model we will be able to push data onto viewstate in a much clean way as below.
// new
way of defining a viewstate property will be
[ViewStateProperty]
public long TestId { get; set; }
Though the real inspiration is from the above mentioned article, some
customizations are done to support the generic use and explained implementation details in the following 5 steps.
Step 1. Define IViewStateProperty interface which can later
provide extensibility and scoping (only those user control marked with this interface will have the feature of ViewStateProperty attribute)
public interface IViewStateProperty { }
Step 2. Define ViewStateProperty attribute
[AttributeUsage(AttributeTargets.Property,
AllowMultiple = false)]
public class ViewStateProperty : Attribute
{
public string
ViewStateName { get; private
set; }
public object
DefaultValue { get; set;
}
public ViewStateProperty()
{
this.ViewStateName = string.Empty;
}
public ViewStateProperty(object
defaultValue)
{
this.DefaultValue = defaultValue;
}
}
Step 3. Define ViewStatePageBase Custom UI Page Base class
/// <summary>/// DEVNOTE: ViewStateProperty instances that are declared as 'private' may not //// be found on reflection (if the application is running in medium trust. E.g. /// when you are running on a shared hosting environment)/// </summary>
public class ViewStatePageBase : System.Web.UI.Page
{
protected override void OnPreLoad(EventArgs e)
{
base.OnPreLoad(e);
this.DoLoadViewStateProperties(this);
this.LoadViewStatePropertiesRecursive(this.Controls);
}
protected override void OnPreRenderComplete(EventArgs e)
{
base.OnPreRenderComplete(e);
this.DoSaveViewStateProperties(this);
this.SaveViewStatePropertiesRecursive(this.Controls);
}
private void LoadViewStatePropertiesRecursive(ControlCollection
controls)
{
foreach (Control ctrl in controls)
{
if (ctrl is IViewStateProperty)
{
this.DoLoadViewStateProperties(ctrl);
}
LoadViewStatePropertiesRecursive(ctrl.Controls);
}
}
private void SaveViewStatePropertiesRecursive(ControlCollection
controls)
{
foreach (Control ctrl in controls)
{
if (ctrl is IViewStateProperty)
{
this.DoSaveViewStateProperties(ctrl);
}
this.SaveViewStatePropertiesRecursive(ctrl.Controls);
}
}
private void DoLoadViewStateProperties(Control ctrl)
{
var properties =
ctrl.GetType().GetProperties(BindingFlags.Public
| BindingFlags.NonPublic |
BindingFlags.Instance)
.Where(prop => Attribute.IsDefined(prop, typeof(ViewStateProperty), true))
.Select(p => new ViewStatePropertyInfo()
{
ViewStateName = ((ViewStateProperty) p.GetCustomAttributes (typeof(ViewStateProperty), true)
.FirstOrDefault()).ViewStateName,
PropertyInfo = p
});
foreach (var property in properties)
{
var localName = String.Format("{0}_{1}"
, ctrl.ClientID
, String.IsNullOrEmpty(property.ViewStateName)
? property.PropertyInfo.Name
: property.ViewStateName);
if (ViewState[localName] != null)
{
property.PropertyInfo.SetValue(ctrl, ViewState[localName], null);
}
}
}
private void DoSaveViewStateProperties(Control ctrl)
{
var properties =
ctrl.GetType().GetProperties(BindingFlags.Public
| BindingFlags.NonPublic |
BindingFlags.Instance)
.Where(prop => Attribute.IsDefined(prop, typeof(ViewStateProperty), true))
.Select(p => new ViewStatePropertyInfo()
{
ViewStateName = ((ViewStateProperty)p.GetCustomAttributes(typeof(ViewStateProperty), true)
.FirstOrDefault()).ViewStateName,
PropertyInfo = p
});
foreach (var property in properties)
{
var localName = String.Format("{0}_{1}"
, ctrl.ClientID
, String.IsNullOrEmpty(property.ViewStateName)
?
property.PropertyInfo.Name
: property.ViewStateName);
ViewState[localName] =
property.PropertyInfo.GetValue(ctrl, null);
}
}
private struct ViewStatePropertyInfo
{
internal string ViewStateName { get; set; }
internal PropertyInfo PropertyInfo { get; set; }
}
}
Step 4. Apply layer super type pattern to pages (aspx.cs) and
ensure they inherit from custom base class (ViewStatePageBase) instead of System.Web.UI.Page.
e.g. A Test Page will inherit from a custom ViestatePageBase
as follows
public partial class TestPage : ViewStatePageBase
Step 5. To bring user controls
to this equation try inheriting from a base class (UserControlBase) and implement
with the IViewStateProperty
public class UserControlBase : System.Web.UI.UserControl, IViewStateProperty
i.e. TestUserControl.ascx.cs
will be as defined follows
public class TestUserControl : UserControlBase
Now you are all set to decorate properties with [ViewStateProperty] attribute and use code as below
[ViewStateProperty]
public long TestId { get; set; }