So, after all the great suggestions that were made regarding the PreserveProperty Control last week I sat down and played around with a few of the suggestions made in comments. The most significant one was creating a more efficient storage mechanism then what was shown in the original post.
As I posted in the comments I added Peter Bromberg’s code he posted in the comments to the LosFormatter which reduced the size of the custom control state quite a bit and made the code a lot simpler for parsing complex objects.
Bertrand LeRoy also made a cool suggestion about using ControlState instead of managing the persistence on my own. I hadn’t really spent much time looking at how control state is implemented so I had to some quick reading up on Fredrik Normén's Blog.
ControlState is pretty easy to work with simply by implementing two methods on the Page class: SaveControlState and LoadControlState. Save basically needs to return an object that contains the state and ASP.NET conveniently parcels up that state and stores it in ViewState. It stores it even if ViewState is disabled on a page which is an important consideration for viewstate. When the page posts back LoadControlState then is called with an object parameter that essentially restores that object saved in SaveControlState. It’s a very easy mechanism and does away with all the messy encoding code I had been using before. This reduced the code of the class drastically.
First try didn’t go so well. I ended up using a generic Dictionary<string,object> list and while that worked just fine – control state properly encoded the object – the output generated was huge. The problem is that a dictionary object is basically a bunch of sub objects of key value pairs that ended up generating a huge list of items. Ooops, that wasn’t much of an improvement.
So, I went back to using a good old Hashtable which persists quite nicely into ViewState and reducing my sample output by 30% - Cool! Simpler code and a good sized reduction in the data is great.
One thing I don’t like is that ControlState stores its value in the ViewState hidden variable. I like the idea of storing this state separately, so I also added a StorageMode property which allows selecting how the data is actually stored. The options are ControlState, HiddenVariable and SessionVariable.
Both Hidden and Session Variable use the LosFormatter to simply turn the Hashtable into a string and store that either in a hidden form var or a Session variable. It turns out the output generated is just about the same as it is for ControlState, minues the preamble for the ControlState in ViewState.
The Session mechanism does basically the same thing opting to store the resulting string from the LosFormatter in a Session Variable. Using Session Variables is probably not a good idea in most situations because it stores a single value in the session object. The problem is that if you have two pages open within the same browser instances (say two tabs or two windows or even two frames) you’re going to get session cross talk. Still, in some applications that may not be an issue as it is highly unlikely two apps are run.
One thing to note is that, as with ViewState and stock ControlState you will get generic errors if you try to store objects that the LosFormatter can handle. Basically complex objects that don’t support Serialization or aren’t flagged for custom TypeConverters. This is a bummer because some common things you might want to preserve like say the Style object can’t be serialized.
Here’s the code for the class and you can also download this from:
http://www.west-wind.com/files/tools/PreservePropertyControl.zip
[ParseChildren(true)]
[PersistChildren(false)]
public class PreservePropertyControl : Control
{
private const char CTLID_PROPERTY_SEPERATOR = '|';
/// <summary>
/// Collection of all the preserved properties that are to
/// be preserved/restored. Collection hold, ControlId, Property
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<PreservedProperty> PreservedProperties
{
get
{
return _PreservedProperties;
}
}
List<PreservedProperty> _PreservedProperties = new List<PreservedProperty>();
/// <summary>
/// Internal object used for persisting to control state. Control stores all preserved
/// property values into this dictionary which in turn persists the values into viewstate
/// </summary>
protected Hashtable SerialzedProperties = new Hashtable();
/// <summary>
/// Determines the storage mode for the control
/// </summary>
[Description("Determines how the Property data is persisted.")]
public PropertyStorageModes StorageMode
{
get
{
return _StorageMode;
}
set
{
_StorageMode = value;
}
}
private PropertyStorageModes _StorageMode = PropertyStorageModes.ControlState;
/// <summary>
/// Required to be able to properly PreservedProperty Collection
/// </summary>
/// <param name="obj"></param>
protected override void AddParsedSubObject(object obj)
{
if (obj is PreservedProperty)
this.PreservedProperties.Add(obj as PreservedProperty);
}
/// <summary>
/// Determines whether the control preserves and restores values
/// </summary>
[Description("Determines whether the control preserves and restores values"),DefaultValue(true)]
public bool Enabled
{
get
{
return _Enabled;
}
set
{
_Enabled = value;
}
}
private bool _Enabled = true;
/// <summary>
/// Adds a control to the collection. At this point only the
/// control and property are stored.
/// </summary>
/// <param name="WebControl"></param>
/// <param name="Property"></param>
/// <returns></returns>
public bool PreserveProperty(Control WebControl, string Property)
{
PreservedProperty pp = new PreservedProperty();
pp.ControlId = WebControl.UniqueID;
pp.ControlInstance = WebControl;
pp.Property = Property;
this.PreservedProperties.Add(pp);
return true;
}
/// <summary>
/// Adds a control to the collection. At this point only the
/// control and property are stored.
/// </summary>
/// <param name="ControlId"></param>
/// <param name="Property"></param>
/// <returns></returns>
public bool PreserveProperty(string ControlId, string Property)
{
Control ctl = this.Page.FindControl(ControlId);
if (ctl == null)
throw new ApplicationException("Can't persist control: " + ControlId + "." + Property);
return this.PreserveProperty(ctl, Property);
}
/// <summary>
/// Read in data of preserved properties in OnInit
/// </summary>
/// <param name="e"></param>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (this.Enabled)
{
if (this.StorageMode == PropertyStorageModes.ControlState)
this.Page.RegisterRequiresControlState(this);
else
this.LoadStateFromLosStorage();
}
}
/// <summary>
/// Write out data for preserved properties in OnPreRender
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (this.Enabled && StorageMode != PropertyStorageModes.ControlState)
this.SaveStateToLosStorage();
}
/// <summary>
/// Saves the preserved Properties into a Hashtabe where the key is
/// a string containing the ControlID and Property name
/// </summary>
/// <returns></returns>
protected override object SaveControlState()
{
foreach (PreservedProperty Property in this.PreservedProperties)
{
// *** Try to get a control instance
Control Ctl = Property.ControlInstance;
if (Ctl == null)
{
// *** Nope - user stored a string or declarative
Ctl = this.Page.FindControl(Property.ControlId);
if (Ctl == null)
continue;
}
string Key = Ctl.UniqueID + CTLID_PROPERTY_SEPERATOR + Property.Property;
// *** If the property was already added skip over it
// *** Values are read now so duplicates are always the same
if (this.SerialzedProperties.Contains(Key))
continue;
// *** Try to retrieve the property
object Value = null;
try
{
// *** Use Reflection to get the value out
// *** Note: InvokeMember is easier here since
// we support both fields and properties
Value = Ctl.GetType().InvokeMember(Property.Property,
BindingFlags.GetField | BindingFlags.GetProperty |
BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.IgnoreCase, null, Ctl, null);
}
catch
{
throw new ApplicationException("PreserveProperty() couldn't read property " + Property.ControlId + " " + Property.Property);
}
// *** Store into our hashtable to persist later
this.SerialzedProperties.Add(Key, Value);
}
// *** store the hashtable in control state (or return it
return this.SerialzedProperties;
}
/// <summary>
/// Overridden to store a HashTable of preserved properties.
/// Key: CtlID + "|" + Property
/// Value: Value of the control
/// </summary>
/// <param name="savedState"></param>
protected override void LoadControlState(object savedState)
{
Hashtable Properties = (Hashtable)savedState;
IDictionaryEnumerator Enum = Properties.GetEnumerator();
while (Enum.MoveNext())
{
string Key = (string)Enum.Key;
string[] Tokens = Key.Split(CTLID_PROPERTY_SEPERATOR);
string ControlId = Tokens[0];
string Property = Tokens[1];
Control Ctl = this.Page.FindControl(ControlId);
if (Ctl == null)
continue;
Ctl.GetType().InvokeMember(Property,
BindingFlags.SetField | BindingFlags.SetProperty |
BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.IgnoreCase, null, Ctl,new object[1] { Enum.Value } );
}
}
private void SaveStateToLosStorage()
{
string Serialized = LosSerializeObject( this.SaveControlState() );
if (this.StorageMode == PropertyStorageModes.HiddenVariable)
this.Page.ClientScript.RegisterHiddenField("__" + this.UniqueID, Serialized);
else // if (this.StorageMode == PropertyStorageModes.SessionVariable)
HttpContext.Current.Session["__" + this.UniqueID] = Serialized;
}
private void LoadStateFromLosStorage()
{
string RawBuffer = null;
if (this.StorageMode == PropertyStorageModes.HiddenVariable)
{
RawBuffer = HttpContext.Current.Request.Form["__" + this.UniqueID];
if (RawBuffer == null)
return;
}
else
{
RawBuffer = HttpContext.Current.Session["__" + this.UniqueID] as string;
if (RawBuffer == null)
return;
}
// *** Retrieve the persisted HashTable and pass to LoadControlState
// *** to handle the assignment of property values
this.LoadControlState(LosDeserializeObject(RawBuffer));
}
private string LosSerializeObject(object obj)
{
LosFormatter output = new LosFormatter();
StringWriter writer = new StringWriter();
output.Serialize(writer, obj);
return writer.ToString();
}
private object LosDeserializeObject(string inputString)
{
LosFormatter input = new LosFormatter();
return input.Deserialize(inputString);
}
protected override void Render(HtmlTextWriter writer)
{
if (this.DesignMode)
writer.Write("[ *** wwPersister: " + this.ID + " *** ]");
base.Render(writer);
}
}
/// <summary>
/// Determines how preserved properties are stored on the page
/// </summary>
public enum PropertyStorageModes
{
ControlState,
HiddenVariable,
SessionVariable
}
Other Posts you might also like