Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Implementing an ASP.NET PreserveProperty Control


:P
On this page:

A few days ago I posted a note about a feature I’d like to see in ASP.NET: Control.PreserveProperty() which would allow the ability to tell ASP.NET to actively manage properties that I specify by storing and retrieving their content automatically.

 

Sounds like ViewState right? But ViewState is not declarative and not easily controlled. If you turn off ViewState (like I do whenever I can) you immediately loose the ability to track any values at all in ViewState. Turn it on and you end up picking up all ViewState of all controls that don’t have ViewState explicitly off. In other words it’s an all or nothing approach.

 

I ran into this a couple of days ago, when I was dealing with a somewhat complex DataGrid that has lots of actions firing off it. The DataGrid is set up with ViewState off and there are many actions, like Delete, set flags etc. that are accessible as options on each row. And there’s Paging. But without ViewState it gets real messy trying to get the DataGrid keep track of the CurrentPageIndex properly. Wouldn’t it be nice if you could do something like this:

 

this.gdInventoryList.PreserveProperty("CurrentPageIndex");

 

and be done with it. No ViewState, just a single value that you know of persisted. Same for something like say a color you persist on a button or label:

 

this.lblMessage.PreserveProperty("ForeColor")

 

Rather than having ViewState track EVERYTHING on the page that you really don’t care about you tell the Page or the control what you do care about and let ASP.NET persist and restore the property. Unfortunately it’s not easy to do this on the control level because ASP.NET provides the Control base class and without multiple inheritance support in C#/VB there’s no way to hook in this functionality (actually C# 3.0 will allow extender methods that might provide the ability to extend a type, but it looks like it will be limited within an assembly). Currently the only way to implement this at the control level is to implement that functionality on subclassed control for every control. Yuk.

 

Bertrand LeRoy mentioned that you can easily build a separate control for this, and so I spent some time today putting it together. Still – it would be a wonderful addition to ASP.NET at the control level to make it much easier for applications and even more so for Control Developers to persist values more easily without having to worry about various StateBag containers and the somewhat messy code that goes along with checking for the existence of values before reading them etc. This is what ControlState should have been.

 

Since it’s the holidays and I have some spare time (or I don’t have a life, whichever way you want to look at it <g>) I spent a few hours today building a control provides the PreserveProperty functionality. You can download the control with source code from:

 

http://www.west-wind.com/files/tools/PreservePropertyControl.zip

How to use the control

The PreservePropertyControl is an ASP.NET 2.0 custom server control but you can backfit it fairly easily to 1.1. The main 2.0 feature used is Generics for the Property collection and you’d have to implement the collection yourself by inheriting from CollectionBase in order to get the designer support.

 

As Bertrand suggested nicely, I implemented it as a server control that can be defined on the page declaratively. For example you can do something like this:

 

<ww:PreservePropertyControl ID="Persister" runat="server">

    <PreservedProperties>

         <ww:PreservedProperty ID="PreservedProperty1" runat="server"

                               ControlId="btnSubmit"  

                               Property="ForeColor" />

         <ww:PreservedProperty ID="PreservedProperty2" runat="server"

                              ControlId="__Page"

                              Property="CustomerPk" />

    </PreservedProperties>

</ww:PreservePropertyControl>   

 

This is nice because you can drop the control on a page and use the designer to add the PreservedProperty items – the designer will pop up the default collection editor for entering the proeprties to preserve. Then again there’s little need to have anything on the design surface so you can also do the same thing in code like this:
 

    protected PreservePropertyControl Persister = null;

 

    protected void Page_Load(object sender, EventArgs e)

    {

        Persister = new PreservePropertyControl();

        Persister.ID = "Persister";

        this.Controls.Add(Persister);

 

        this.Persister.PreserveProperty(this.btnSubmit, "ForeColor");

        this.Persister.PreserveProperty(this, "CustomerPk");

        this.Persister.PreserveProperty(this.dgInventoryList,"CurrentPageIndex");

    }

 

You can also store things like objects, so this can potentially replace ViewState as long as you have a property on either a control or the page. The nice thing about this approach is you set up the PreserveProperty call once and after that you can reference the property and always have it up to date across same page posts just like ViewState, but with ViewState actually off. In addition, you can persist things that wouldn’t auto-persist with ViewState, like properties on the Page itself. I actually do this frequently for things like IDs that identify the record I’m editing for example or to signify a certain state page (adding, editing etc.).

 

You should note though that this persistence mechanism is a little more verbose than ViewState. While you can persist much less data than auto-ViewState does in most cases, comparable persist values are going to be larger in this implementation because this control must store the control Id and property along with the value. How light the impact is depends on how much you persist – the big advantage is that you get to control exactly what gets persisted.

How it works

The implementation of the control is pretty simple. It has a PreservedProperties collection that holds the Control ID (or Instance if available) and the Property name. When you call the PreserveProperty() on the control or declaratively define the control on a page, each control Id and Property name is stored in the collection. The collection is a Generic List<PreservedProperty>. I love Generics for this stuff – no more creating custom collections for child controls! Yay…

 

To get the collection to work as designable collection you have to use a few custom attributes on the Control class as well as the collection property. You also need to implement AddParsedSubObject to get the child control values to be added as PreservedProperty objects from the declarative script definition.

 

[ParseChildren(true)]

[PersistChildren(false)]

public class PreservePropertyControl : Control

{

 

    /// <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>

    /// 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 );

}

 

… more class stuff

}

 

Most of the work in the class is handled by a couple of methods called RenderOutput() and RestoreOutput. RenderOutput() basically generates the encode text to be persisted and then embeds a hidden form var. RestoreOutput() takes the output generated, reads the encoded text and then parses it back into the control properties. These methods are then hooked up to OnPreRender() and OnInit() respectively to fire automatically as part of the page cycle.

 

Creating the encoded output

RenderOutput() essentially runs through the collection of PreservedProperties that were added during the course of page Execution – usually in the Page_Load or as part of the declarative script. It reads each property value and tries to persist it into a string. The string is then encoded in a very simplistic format (for the moment). I suppose there are better ways to do this but I wanted to keep things simple at least in this first rev so each property is encoded like this:

 

`|ControlId~|PropertyName~|StringValue|`

 

The entire collection is then strung together and finally base64 encoded. For simple values this is straight forward -  the value is simply turned into a string and stored. For complex types, the value is serialized via Binary Serialization into a byte[] array and converted to an ASCII string that gets embedded instead.

 

Here’s what the encoding looks like:

 

/// <summary>

/// Creates the Hidden Text Field that contains the encoded PreserveProperty

/// state for all the preserved properties and generates it into the page

/// as a hidden form variable ( Name of control prefixed with __)

/// </summary>

/// <exception>

/// Fires an ApplicationException if the property cannot be persisted.

/// </exception>

public void RenderOutput()

{

    StringBuilder sb = new StringBuilder();

 

    foreach(PreservedProperty Property in this.PreservedProperties)

    {

        Control Ctl = Property.ControlInstance;

        if (Ctl == null)

        {

            Ctl = this.Page.FindControl(Property.ControlId);

            if (Ctl == null)

                continue;

        }

       

        // *** Try to retrieve the property or field

        object Value = null;

        try

        {

            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);

        }

 

        Type ValueType = Value.GetType();

 

        // *** Helper manages the conversion to string

        // *** Simple type or ISerializable on - otherwise ApplicationException is thrown

        string Return = this.TypedValueToString(Value,CultureInfo.InvariantCulture);

 

        // *** If we made it here and we're null the value is not set

        if (Return == null)

            continue;

 

        sb.Append("`|" + Property.ControlId + "~|" +

                  Property.Property + "~|" + Return + "|`");

    }

 

    string FullOutput =  sb.ToString();

 

    if (this.Base64Encode)

        FullOutput = Convert.ToBase64String( Encoding.UTF8.GetBytes(FullOutput) );

 

    this.Page.ClientScript.RegisterHiddenField("__" + this.UniqueID,FullOutput);

}

 

/// <summary>

/// Converts a type to string if possible. This method supports an optional culture generically on any value.

/// It calls the ToString() method on common types and uses a type converter on all other objects

/// if available

/// </summary>

/// <param name="RawValue">The Value or Object to convert to a string</param>

/// <param name="Culture">Culture for numeric and DateTime values</param>

/// <returns>string</returns>

public string TypedValueToString(object RawValue, CultureInfo Culture)

{

    Type ValueType = RawValue.GetType();

    string Return = null;

 

    if (ValueType == typeof(string))

        Return = RawValue.ToString();

    else if (ValueType == typeof(int) || ValueType == typeof(decimal) ||

        ValueType == typeof(double) || ValueType == typeof(float))

        Return = string.Format(Culture.NumberFormat, "{0}", RawValue);

    else if (ValueType == typeof(DateTime))

        Return = string.Format(Culture.DateTimeFormat, "{0}", RawValue);

    else if (ValueType == typeof(bool))

        Return = RawValue.ToString();

    else if (ValueType == typeof(byte))

        Return = RawValue.ToString();

    else if (ValueType.IsEnum)

        Return = RawValue.ToString();

    else

    {

        if (ValueType.IsSerializable)

        {

            byte[] BufferResult = null;

            if (!wwUtils.SerializeObject(RawValue, out BufferResult))

                return "";

 

            Return = Encoding.ASCII.GetString(BufferResult);

            //Return = Convert.ToBase64String(BufferResult);

        }

        else

        {

            throw new ApplicationException("Can't preserve property of type " +  

                                           ValueType.Name);

 

            //TypeConverter converter = TypeDescriptor.GetConverter(ValueType);

            //if (converter != null && converter.CanConvertTo(typeof(string)))

            //    Return = converter.ConvertToString(null, Culture, RawValue);

            //else

            //    return null;

        }

    }

 

    return Return;

}

 

The code uses Reflection to retrieve the control’s property value dynamically based on the string representation stored in the PreservedProperty item. Once the value is retrieved, it’s converted to a string using the TypedValueToString() helper method.

 

You can see that the code checks for simple types and then for ISerializable and if that doesn’t work it throws an exception back. This is similar to ViewState. Like ViewState, the control persists simple values and anything that supports ISerializable. I played around a bit with trying to check for type converters first, since TypeConverter output tends to be much less verbose and more efficient than serialization. But unfortunately many things output incomplete TypeConverter data. For example, the .Style or .Font properties have type converters but they don’t actually work for two way persistence (in fact the style converter only appears to do a .ToString() which is useless). So for now I used it’s simple types and ISerializable – it works well, but is not optimal for performance or encoded size.

 

If anybody has any ideas on how to get better type parsing support I’d love to hear about it. But it looks ViewState has these same limitations and ViewState too persists using Serialization. This control’s encoded output tends to be more verbose than the same values persisted to ViewState primarily because it persists to string (other than the serialized objects) and it has to store the control and property names.

 

Anyway, RenderOutput is self contained – it takes the generated string of all the properties and writes a hidden form var out to the page:

 

this.Page.ClientScript.RegisterHiddenField("__" + this.UniqueID,FullOutput);

 

This code is hooked up to the OnPreRender() method:

 

protected override void  OnPreRender(EventArgs e)

{

    base.OnPreRender(e);

    if (this.Enabled)

        this.RenderOutput();

}

 

OnPreRender happens late in the cycle just before the page actually renders to output. I want to do this as late as possible to make sure the last known value is persisted.

Restoring the property values

Once the value’s in the page it gets rendered into the page when it displays. Now when the page is posted back again – after the user clicks a button or other dynamic Postback link – this encoded data gets posted back to the server.

 

On the inbound end the control hooks into OnInit() to read and assign the value.

 

protected override void OnInit(EventArgs e)

{

    base.OnInit(e);

    if (this.Enabled)

        this.RestoreOutput();

}

 

OnInit is very early in the cycle and so the PreserveProperty restore action is the lowest in the totem pole of state mechanisms. If ViewState is enabled or a PostBack value exists those will overwrite the value that was preserved. The idea is that this mechanism won’t change any existing behaviors in ASP.NET. RestoreOutput() then parses the inbound string and tries to get the control and property/field and then assign the value to it.

 

The full format of the base64 decoded string looks something like this:

 

`|btnSubmit~|ForeColor~|_????___QSystem.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a___System.Drawing.Color__name_value

knownColor_state_ ___

_|``|lblMessage2~|BackColor~|_????___QSystem.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a___System.Drawing.Color__name_value

knownColor_state_ ___

_|``|__Page~|Test~|Hello|``|__Page~|Value~|2|``|__Page~|Address~|_????___HApp_Code.zbs41ciw, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null___AddressClass__Street_City_Zip_Phones_Access      SomeValue_____PhoneNumberClass_______32 Kaiea___Paia___96779      _

_121.1112___PhoneNumberClass__Phone_Fax_email_______123-213-1231___123-123-1231_      _rickstrahl@hotmail.com_|`

 

There are a few different things persisted here. ForeColor (which turns into a serialized object), a simple value and a custom object. Notice that this serialization format isn’t very tight. There’s a lot of extra content – for one even to persist a simple integer value you have to write out the actual value, plus the name of the control and the property. The ID is the UniqueID by the way so that the control can be found regardless of the containership level.

 

The question marks and underscores in the text above are binary characters that don’t print and part of the ASCII text from the serialization. Although it looks funky they do work just fine holding the binary data values. Notice that many things that are non-obvious serialize - like Colors for example. So ForeColor and BackColor serialize into fairly large object signatures.

 

RestoreOutput() then parses through this string by splitting the string first into the individual preserved properties and then each of the components of each property (ID, Property and Value). Finally it takes the value and tries to reassign it to the control by reading the control’s original value and type, converting the string value to that type and then assigning it to the property/field. Here’s what RestoreOuput() looks like:

 

protected void RestoreOutput()

{

    if (!this.Page.IsPostBack)

        return;

 

    string RawBuffer = HttpContext.Current.Request.Form["__" + this.UniqueID];

    if (string.IsNullOrEmpty(RawBuffer))

        return;

 

    if (this.Base64Encode)

        RawBuffer = Encoding.UTF8.GetString(Convert.FromBase64String(RawBuffer));

 

    string[] PropertyStrings = RawBuffer.Split(new string[1] { "`|" }, StringSplitOptions.RemoveEmptyEntries);

    foreach(string TPropertyString in PropertyStrings)

    {

        // *** Strip off |`

        string PropertyString = TPropertyString.Substring(0, TPropertyString.Length - 2);

 

        string[] Tokens = PropertyString.Split(new string[1] {"~|"},

                                               StringSplitOptions.None);

        string ControlId = Tokens[0];

        string Property = Tokens[1];

        string StringValue = Tokens[2];

 

        Control Ctl = this.Page.FindControl(ControlId);

        if (Ctl == null)

            continue;

 

        // *** Get existing Value

        object CtlValue = wwUtils.GetPropertyCom(Ctl,Property);

 

        object Value = this.StringToTypedValue(StringValue, CtlValue.GetType(),CultureInfo.InvariantCulture);

        if (Value == null)

            continue;

 

        wwUtils.SetPropertyCom(Ctl, Property, Value);

    }

}

 

public object StringToTypedValue(string SourceString, Type TargetType, CultureInfo Culture)

{

    object Result = null;

 

    if (TargetType == typeof(string))

        Result = SourceString;

    else if (TargetType == typeof(int))

        Result = int.Parse(SourceString, System.Globalization.NumberStyles.Integer, Culture.NumberFormat);

    else if (TargetType == typeof(byte))

        Result = Convert.ToByte(SourceString);

    else if (TargetType == typeof(decimal))

        Result = Decimal.Parse(SourceString, System.Globalization.NumberStyles.Any, Culture.NumberFormat);

    else if (TargetType == typeof(double))

        Result = Double.Parse(SourceString, System.Globalization.NumberStyles.Any, Culture.NumberFormat);

    else if (TargetType == typeof(bool))

    {

        if (SourceString.ToLower() == "true" || SourceString.ToLower() == "on" || SourceString == "1")

            Result = true;

        else

            Result = false;

    }

    else if (TargetType == typeof(DateTime))

        Result = Convert.ToDateTime(SourceString, Culture.DateTimeFormat);

    else if (TargetType.IsEnum)

        Result = Enum.Parse(TargetType, SourceString);

    else

    {

        if (TargetType.IsSerializable)

        {

            if (SourceString == "" || SourceString == "")

                return null;

 

            byte[] BufferResult = Encoding.ASCII.GetBytes(SourceString);

            //byte[] BufferResult = Convert.FromBase64String(SourceString);

            Result = wwUtils.DeSerializeObject(BufferResult, TargetType);

        }

        else

        {

            System.ComponentModel.TypeConverter converter = System.ComponentModel.TypeDescriptor.GetConverter(TargetType);

            if (converter != null && converter.CanConvertTo(typeof(string) ) )

                try

                {

                    return converter.ConvertFromString(null, Culture, SourceString);

                   

                }

                catch

                { ;   }

 

                throw (new ApplicationException("Type Conversion failed for " +

                                                TargetType.Name + "\r\n" + SourceString) );

        }

    }

 

    return Result;

}

 

The code uses a couple of helper methods for GetProperty and SetProperty which are basically Reflection Type.InvokeMember calls, but otherwise this code is the reverse of what ReturnOutput() does. This code grabs the current value of the Control property to figure out what type to convert back to. It then converts the value and if successful assigns it back to the control.

 

There you have it. Since I’m still sitting on a bunch of 1.1 ASP.NET code that has moved to ASP.NET 2.0 I went through my current app I’m working on and added the control to about 10 pages that have DataGrids and persisted the ActivePageIndex and a couple of other custom settings that have been problematic. I was able to rip out a bunch of scattered code out of these pages that dealt with properly keeping track of these values previously. Very cool…

 

I’ve posted the code and a small sample at:

 

http://www.west-wind.com/files/tools/PreservePropertyControl.zip

 

You can check it out and play with this for yourself. If you come up with some useful changes please let me know by posting here…

 

Enjoy…

 


The Voices of Reason


 

Edward Jewson
December 26, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Hi, intresting stuff, but the download link doesnt work.

Rick Strahl
December 26, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Thanks Edward... typo in the text above. Should work now...

Nick Parker
December 26, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Rick, this is definitely a good idea. With C#/VB.NET 3.0, extension methods will make things appear even more integrated for ideas like this.

Marc Brooks
December 26, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Cool idea, I've got a couple comments.

First, in ASP.Net 2.0, you can have ViewState stored server-side in SessionState (instead of client-side in a hidden variable). This greatly reduces the network payload, at a slight to moderate extra load on the server. In a web-farm, it even works with SQL Session state. See http://msdn2.microsoft.com/en-us/library/system.web.ui.sessionpagestatepersister.aspx

Second, you should probably use the LOSFormatter to store your property values. It is very light-weight and mirrors the way ASP.Net runtime does ViewState. I've got code appended (hopefully).

Third, you should probably load your attributes a little earlier in the page lifecycle than Page_Load... perhaps OnInit?

Lastly, you should also check out some cool alternatives like using attributes to decorate plain-old page variables which then get auto-stored and restored at page load and unload time. There's an excellent article on CodeProject that lets you select attribute-by-attribute where a value gets cached (ViewState, Session, Cache, Context). See http://www.codeproject.com/aspnet/PersistAttribute.asp

Code that I use it for saving multi-part keys for URL decoration:

public static string KeyToString(object itemKey)
{
using (TextWriter writer = new StringWriter())
{
LosFormatter formatter = new LosFormatter();
formatter.Serialize(writer, itemKey);
return HttpUtility.UrlEncode(writer.ToString());
}
}

public static object StringToKey(string itemString)
{
if (itemString == null || itemString.Length == 0)
return null;

string itemKey = HttpUtility.UrlDecode(itemString);
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(itemKey);
}

Bertrand Le Roy
December 26, 2005

# re: Implementing an ASP.NET PreserveProperty Control

This is great! I'll relay this idea internally to see what we can do to integrate a similar feature if you don't mind.
A few remarks. You should apply the NonVisualControl attribute so that it can be hidden from the designer surface.
Also, as has been noted, you should just instantiate an ObjectStateFormatter, which will give a few benefits apart from relieving the control from the serialization task: you'll get protection against tampering, a more efficient format etc.
Even easier: why don't you just store the state into the control's... ControlState? Yes, the more I think about it, the more it seems like the perfect solution.

Rick Strahl
December 26, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Nick: I think Extension methods are going to be limited because they are limited to a specific assembly, not global. If you extend Control you need that visible EVERYWHERE otherwise it's a hassle.

Marc: Thanks for all the good suggestions. I haven't played with the LosFormatter although I was thinking that ViewState encoding was more efficient. What's interesting is that I compared ViewState and my generated strings and in most cases there were Binary Serialized objects in there. Hmmm... gotta take a closer look. I knew going in that the serialization will need tweaking. This is great feedback!

Attributes - I don't think this is a good fit. The idea of this is to be able to do away with ViewState in most places and persist anything that's on the page and can be referenced through a control. How would you get an attribute on ForeColor of a control for example? I think it has to be completely declarative.

Bertrand: Definitely pass it on as feedback. I was going to post it as a LadyBug suggestion after getting more feedback here. I still think the way this should be implemented is as Control method, with each control managing its own collection of preserved values.


Kevin Wright
December 27, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Rick, am I right in thinking that this could be applied to a Master Page, and therefore inherited "automatically" throughout the whole app?

Kevin

Peter Bromberg
December 27, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Rick, that's pretty cool! I backported it to ASP.NET 1.1 just for fun. Really all I needed to do was change the Generic List to an ArrayList, and I added the recommended LOSFormatter code, which is much more lightweight for Web type properties and simple objects:

public static string LOSSerializeObject(object obj)
{
System.Web.UI.LosFormatter output = new System.Web.UI.LosFormatter();
StringWriter writer = new StringWriter();
output.Serialize(writer, obj);
return writer.ToString();

}

public static object LOSDeserializeObject( string inputString)
{
System.Web.UI.LosFormatter input = new System.Web.UI.LosFormatter();
return input.Deserialize(inputString);
}

Nikhil Kothari
December 27, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Nice!

I actually discussed this with Bertrand.

I am not sure this should be baked into Control, and this feature is better suited as an external control - that is my opinion. Here's why:

1. Controls will have too many properties indicating which properties should be state managed. Or you have to implement it as a list of properties, which leads to #2. Either way, the design experience will become challenging.

2. Inefficient lookups to determine what to save - either a control will have to enumerate its list of properties, do string comparisons, or some sort of hashtable lookups. It doesn't matter much in terms of a single control, but these things add up, since there is a multiplying effect with everything you put on a Control.

3. A lot of view state comes from persisting a control tree, where depth of the tree causes additional structure in the view state graph. Getting rid of that would result in more goodness. An external control helps that.

4. An external control helps model the concept. The page has some interesting state. This control represents the state of the page, the controls just happen to be initialized with the state of the page.

I think what you want is the ability to build extender controls, where the properties and functionality are implemented by the extender control, but the persistence happens in-place with the control. That would give the best of both worlds.

Quick comment on the implementation - You could avoid the AddParsedSubObject stuff, by instructing the parser that your default property is the collection you have (using the ParseChildren attribute).

Rick Strahl
December 28, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Thanks everybody for these great comments!

Bertrand - thanks for the hint on ControlState. It'd be nice if I could use it, but I'm not sure I can. What is the structure I would store? An array of objects that contain ControlId, Property and Value. I don't think this will persist well <g>. Hmmm... maybe a Dictionary with a single key for ControlId$Property... I have to play with that.

Kevin - yes if you add the control to a Master page any page that uses the master page will have access to the PreserveProperty Control. However, I'm not sure if you'd want to do this. You'd probably want to explicitly add it to the forms that need it. It's not going to be needed on every one.

Peter, Marc - thanks for the LosFormatter code and hints. I've put it into the code, but just for the object serialization. The formatter generates base64 output it looks like so I don't really want to encode the simple types, only complex values, because I end up encoding the final result.

FWIW, I've also added a property PersistToSession that persists to the Session object, so nothing gets sent over the wire. Thanks to Marc for that hint on ViewState persistance to Session. Heck I'm getting all sorts of good ideas (for other stuff) here...

Rick Strahl
December 28, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Nikhil,

Yes, extender controls would be a nice addition. This would solve a host of problems with custom controls I've built today. For example, I have controls that do custom databinding by adding a set of properties to controls (BindingSourceObject, BindingSourceMember) - the only way this works is by subclassing each control class that needs it. Even if you add behavior classes it still requires subclassing. An extender class would make this so much easier with a single class to handle this logic that could be dynamically attached. If you go this route though I'd hope for a more straightforward implementation than WinForms <g>...


Rick Strahl
December 28, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Nikhil,

Here are some comments on your points:

#1
Realistically I would see this as non-visual feature. The benefit of doing this declaratively as a control doesn't seem to really buy all that much. I did it because Bertrand suggested it <g> and because it's easy enough to do. In my code where I use the control now it's done non-visually... It's a heck of a lot quicker to write Persist.PreserveProperty(this,"PageTitle") than to go through the designer.

#2
One way or another you have to enumerate when assigning the values back. I also would expect that this sort of state if used likely is not going to have a lot of values in there. Unless you can think of a more efficient way to store the ControlId/Property definition I don't see how you can get around that. Hmmm... if you store the statebag on the Control itself then you can remove the ControlID and Reflection calls to get to the control which should improve the size and parse perf.

#3
Yes, definitely but I suspect that the efficiencies aren't going to be much different. You'll end up replacing the control tree formatting with strings for controlId/properties and tree walking to enumeration. But as I said in #2 I think that that's minimal - the key advantage is the declarative nature.

#4
Actually I agree overall. I just think for a usability and logical place where you'd expect the functionality is on the control. Putting it on the control and storing the state there also reduces the need to store some sort of identifier for the control (ControlID) so the data can compact a little more than what I have now.

Fabrice
December 28, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Rick,

Congratulations for the great idea and thanks for the implementation.

I'd recommend being careful with persisting page information in the Session. Remember that you can have two instances of the same page with different state in two tabs/windows...
I don't know how you implemented it, but you need to make sure that you can differentiate the different page states in the session dictionary.

Fabrice

Rick Strahl
December 28, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Fabrice,

Yes the Session option is an optional value and off by default. I'm storing the value in a single session var for all pages and that can cause problems with multiple browser instances that are in process (like Tabs) or FRAMES/IFRAMES.

I guess the developer would have to decide whether that's an option depending on the application.

The updated code that uses the LosFormatter is online now.

cowgaR
December 29, 2005

# re: Implementing an ASP.NET PreserveProperty Control

Rick, this is the greates end of the year 2005 I could wish for :-) Great work!

Now if someone would do some funny test with some specific written application - to see how we can speed ASP20 applications with this and all avaiable goodies:

-raw ASP20 app with viewstate on all the time and all settings on default
-'smart' ASP20 with viewstate off all the time, presisting info in session where possible using this control + gziping(compresing) data sent and recevied by the server + using DECEMBER CTP of ATLAS to only send data that need it :-)

That's what I am going to try ones I got to more complex app that I am with today. All in all, ASP20 can be really snappy!

Rick Strahl
January 06, 2006

# re: Implementing an ASP.NET PreserveProperty Control

There's an update to this control using ControlState or Hidden Var persistence using the LosFormatter and Hashtable persistence, which reduces the code and results in smaller persisted data footprints. There are also a few other small additions and you can find the updated information here:

http://west-wind.com/weblog/posts/4094.aspx

The code online has been updated to this new code.

Rick Strahl
January 06, 2006

# re: Implementing an ASP.NET PreserveProperty Control

cowgaR,

It really depends on what you're doing. Obviously the more data is stored in viewstate the slower things get and the more bandwidth is consumed. I think the big benefit of this approach is that it's declarative rather than the all inclusive thing ViewState does - you tell what to persist and nothing more. For my apps 90% of the time this will be a better choice than letting ViewState manage things for me.

But this stuff has overhead. The controls have to persist and restore the data (well, so does ViewState) and there's Reflection involved to read and write the values back. I suspect in the context of full page overhead these things are negliable though, especially since ASP.NET will write out some ViewState no matter what.

If you do some testing, be sure to drop a line here to tell us how it worked out.

NG
March 15, 2006

# Hi I find this interesting but...

I have a few question like can i add it to a control instead of a page?.

I am pretty new to .NET though i found this good one i need to know how can i implement it inside another control to persist the values .

Rick Strahl's WebLog
March 18, 2006

# ASP.NET ListBoxes, SelectedValue and ViewState

I'm struggling once again with an OnSelectedIndexChanged form that just doesn't want to behave when ViewState is off.

Rick Strahl's WebLog
May 05, 2006

# Extender Controls: Extending Controls with new Properties

I've been rethinking some previous design choices in the light of using Extender Controls - controls that attach behavior to existing controls and provide functionality that otherwise requires inheritance.

Luke's Blog
May 10, 2006

# Una rapida e chiara sintesi su cosa


Dave Bacher
May 23, 2006

# re: Implementing an ASP.NET PreserveProperty Control

It would be fairly easy to write an extender that investigated attributes to serialize.

XmlSerializer presents a good model for this sort of thing. It checks for an interface first. If the interface is not found, then it falls back to using attributes. If you set things up to produce attribute-based XML, you could probably get close to the behavior you are wanting using XmlSerializer.

I personally also tend to override the page viewstate methods in 1.1, to redirect viewstate to either Session or the Cache, depending on the application. To do this, you generate a GUID number, and place that in the viewstate field on the page. The data that would normally be in viewstate, you write to session/cache using that GUID.

This cuts down the per-request HTML massively, while keeping all page behavior consistent. I am relatively sure that when you point ViewState at an alternative provider in .NET 2.0, that it is just doing this same thing.

Ken Forslund
June 06, 2006

# re: Implementing an ASP.NET PreserveProperty Control

This article was very useful. I grabbed the code for serializing an object to a string (and back) for my problem. I've got a hefty business object, and a screen that is supposed to "manage" that object.

Serializing the object in Page_PreRender and deserializing in Page_Load for postbacks, allows me to keep the form and the object in sync and cache the object until the Save button is clicked and I do a object.Save()

All the controls just have to manipulate the object's methods. In Page_PreRender, I just run some code that repopulates any form fields and datagrids.

Sadly, it took this much work, because ObjectDataSource and DataGrids are incompatible with complex business objects with funky constructors. Particularly, when you don't want each command to affect the database right away...

Good job!

eggheadcafe.com articles
September 25, 2006

# A Short Synopsis of ASP.NET ViewState


Rick Strahl's Web Log
October 14, 2006

# Updating the ASP.NET PreserveProperty Control - Rick Strahl's Web Log

After the many comments made on the previous entry I've updated the PreserveProperty Control. This version is more efficient in storage and uses much less internal code instead relying more on ASP.NET's internal serialization support. There are also a few new options like choosing the storage mechanism.

Rick Strahl's Web Log
November 13, 2006

# An ASP.NET feature I’d like to see: Control.PreserveProperty() - Rick Strahl's Web Log

It would be really nice if there was an easier and more deterministic way to persist values in ASP.NET. One way that I think would be nice is to have a PreserveProperty() method on the control class that could automatically store and restore a value on a control which simplify persistence greatly and allow tight control over exactly what gets persisted both on the control as well as the application level.

SteveC
February 12, 2007

# re: Implementing an ASP.NET PreserveProperty Control

Rick, how does this look in its "final form"? Have you abandoned the idea at this point?

Rick Strahl
February 12, 2007

# re: Implementing an ASP.NET PreserveProperty Control

Not sure what you mean Steve. The code is posted above and that's pretty much the
final form <g>...

AndrewS
June 21, 2007

# re: Implementing an ASP.NET PreserveProperty Control

Thanks for the control. It has saved me a lot of work.

I am creating some new controls that have data that must be persisted all the time. To facilitate this I created a new attribute and used it to decorate the control and the properties that need to persisted. I then extended your module to add properties by attribute.

The error handling isn't the best.

I guess this somewhat like ControlState -- information that must always be saved.

The attribute is simply:

public class PersistanceAttribute : Attribute
    {
        bool _persist = true;

        public bool Persist
        {
            get { return _persist; }            
        }

        public PersistanceAttribute() 
        {            
        }

        public PersistanceAttribute(bool persist)
        {
            _persist = persist;
        }

        public override bool IsDefaultAttribute()
        {
            return _persist == true;
        }
    }


The added functions in your control are:

public bool PreservePropertyByAttribute(Control WebControl, bool recurse)
        {
            bool success = true;
            if (recurse)
            {
                foreach (Control c in WebControl.Controls)
                {
                    success = PreservePropertyByAttribute(c, true);
                    if (!success) break;
                }
            }

            if(success)
            {
                List<string> properties = PropertiesByAttribute(WebControl);
                foreach(string property in properties)
                {
                    success = PreserveProperty(WebControl, property);
                    if (!success) break;
                }
            }
            return success;
        }

        protected List<string> PropertiesByAttribute(Control c)
        {
            Type controlType = c.GetType();
            Type attributeType = typeof(PersistanceAttribute);
            List<string> properties = new List<string>();

            bool persistClass = false;
            object[] classAttributes = controlType.GetCustomAttributes(attributeType, true);
            foreach (PersistanceAttribute pa in classAttributes)
            {
                if (pa.Persist)
                {
                    persistClass = true;
                    break;
                }
            }

            if (persistClass)
            {
                BindingFlags bf = BindingFlags.FlattenHierarchy |
                    BindingFlags.Public |
                    BindingFlags.Instance;

                MemberInfo [] members = controlType.GetMembers(bf);
                foreach(MemberInfo mi in members)
                {
                    object [] memberAttributes = mi.GetCustomAttributes(attributeType, true);
                    foreach (PersistanceAttribute mpa in memberAttributes)
                    {
                        if (mpa.Persist)
                        {
                            properties.Add(mi.Name);
                        }
                    }
                }                    
            }
            return properties;
        }


Thanks again for the control.

AndrewS

Rick Strahl
June 22, 2007

# re: Implementing an ASP.NET PreserveProperty Control

Andrew nice idea although I'm not sure that this gains you all that much. After all adding preserved properties is just a single line of code (assuming the PreserveProperty Control's been added). I can see though that this could be handy for control development where you can immediately associate properties with the persistence. Sure is nicer than ViewState backing <g>...

AndrewS
June 22, 2007

# re: Implementing an ASP.NET PreserveProperty Control

Rick:

Since most of my pages will have dynamically generated controls, this method helps. In a static layout scenerio, it doesn't gain much.

I don't know if the attribute method is the best way to go. It's clean looking in the code and easy to understand though probably lacking in performance due to reflection.

I could add something like an array of strings on the control instead of attributes to indicate which properties always need to be persisted.

AndrewS

AndrewS
June 26, 2007

# re: Implementing an ASP.NET PreserveProperty Control

Rick:

I ended up adding caching to the PreservePropertyByAttribute procedure. This resulted in a 50x speed up according to the Trace Profile. I now don't feel so bad about the slow code. :)

Andrew.

Atlas and more
July 21, 2007

# Saving partial state of an ASP.NET control - Atlas and more


PremY
January 27, 2008

# re: Implementing an ASP.NET PreserveProperty Control

To Rick: great idea

To AndrewS:
Can you post your complete solution "properties by attribute" and caching
Thanks

Yves

Dimeji
February 07, 2008

# re: Implementing an ASP.NET PreserveProperty Control

Good article. I have a problem. Lets say we have a calendar which i add textboxes based on a start and end date in the DayRender event handler. Fine. Then i type in some characters in these dynamically generated textboxes and i click an external button. How do i get those textboxes with its saved state? I'm guessing its similar to what you described in this article.

I know already a calendar doesnt save state but renders them. Still isnt there a way? the last article i read mentioned IsTrackViewState or smt like that.

Thanks in advance.

DotNetMonster
March 31, 2008

# re: Implementing an ASP.NET PreserveProperty Control

I just happen to stumble upon this article while searching for something else. How about modifying a bit of this control and use it instead? (I had a short dead line so i just used Array list. I would change it to something sencible for the second milestone. For now it works magic)

    [ToolboxData("<{0}:StateHolder runat=server></{0}:StateHolder>")]
    public class StateHolder : Control
    {
        public ArrayList State
        {
            get
            {
                if(ViewState["ds"] == null)
                {
                    ViewState["ds"] = new ArrayList(); ;
                }

                return (ArrayList)ViewState["ds"];
            }
        }

        protected override void Render(HtmlTextWriter output)
        {
            
        }
    }

Larry Stewart
January 12, 2009

# re: Implementing an ASP.NET PreserveProperty Control

Hello... Just want you to know that I'm somewhat new to programming (2 years).

I'm trying to implement this into a test MASTER page to preserve the value of a hidden field to be used by all the pages the use this master page. However, It does not seem to be keeping the value from page to page.

I put the .dll in the bin folder, registered the control and added the following code server side:

If Not Page.IsPostBack Then

If Not String.IsNullOrEmpty(Request.QueryString("destid")) <> Nothing Then

HiddenDestinationID.Value = Request.QueryString("destid")
'Me.Persister.PreserveProperty(Me, "Value")
Persister = New PreservePropertyControl()
Persister.ID = "Persister"
Me.Controls.Add(Persister)

Me.Persister.PreserveProperty(Me.HiddenDestinationID, "Value")

End If
End If

The hiddenfield gets set properly but as soon as I navigate to another page it is empty.
I'm required to use VB.NET for this project so I converted the code.

Any ideas in what I'm doing wrong?

Thank you

Lance R.
July 31, 2009

# re: Implementing an ASP.NET PreserveProperty Control

Larry,
The following line will always evaluate to true:
If Not String.IsNullOrEmpty(Request.QueryString("destid")) <> Nothing Then


remove the "<> Nothing" if all you want to do is check for null or empty string.



-Lance

Michael
January 15, 2010

# re: Implementing an ASP.NET PreserveProperty Control

Very good control, this save me too much time in create a new control with same functionality.

Tks Rick...

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024