BBorg
BBorg

Reputation: 354

ViewState as Attribute

Instead of this ..

    public string Text
    {

        get { return ViewState["Text"] as string; }

        set { ViewState["Text"] = value; }

    }

I would like this ..

    [ViewState]
    public String Text { get; set; }

Can it be done?

Upvotes: 16

Views: 2236

Answers (4)

Jeff Sternal
Jeff Sternal

Reputation: 48593

Like this:

public class BasePage: Page {

    protected override Object SaveViewState() {

        object baseState                      = base.SaveViewState();            
        IDictionary<string, object> pageState = new Dictionary<string, object>();
        pageState.Add("base", baseState);

        // Use reflection to iterate attributed properties, add 
        // each to pageState with the property name as the key

        return pageState;
    }

    protected override void LoadViewState(Object savedState) {

        if (savedState != null) {

            var pageState = (IDictionary<string, object>)savedState;

            if (pageState.Contains("base")) {
                base.LoadViewState(pageState["base"]);
            }

            // Iterate attributed properties. If pageState contains an
            // item with the appropriate key, set the property value.

        }
    }
}

Pages that inherit from this class could use the attribute-driven syntax you've proposed.

Upvotes: 5

BBorg
BBorg

Reputation: 354

Well, this is what i got so far, TY Jeff for pointing me in the right direction:

TestPage:

public partial class Pages_Test : BasePage    {
    [ViewState]
    public String Name { get; set; }

BasePage:

    #region Support ViewState Attribute

    BindingFlags _flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

    protected override Object SaveViewState()
    {
        object _baseState = base.SaveViewState();
        IDictionary<string, object> _pageState = new Dictionary<string, object> { { "base", _baseState } };

        //Use reflection to get properties marked for viewstate

        foreach (PropertyInfo _property in GetType().GetProperties(_flags))
        {
            if (_property.HasAttribute<ViewState>())
            {
                object _value = _property.GetValue(this, _flags , null, null, null);
                _pageState.Add(new KeyValuePair<string, object>(_property.Name, _value));
            }
        }
        return _pageState;
    }

    protected override void LoadViewState(Object savedState)
    {
        if (savedState != null)
        {
            var _pageState = (IDictionary<string, object>)savedState;

            if (_pageState.ContainsKey("base"))
            {
                base.LoadViewState(_pageState["base"]);
            }
            //use reflection to set properties 
            foreach (PropertyInfo _property in GetType().GetProperties(_flags ))
            {
                if (_property.HasAttribute<ViewState>() && _pageState.ContainsKey(_property.Name))
                {
                    object _value = _pageState[_property.Name];
                    _property.SetValue(this, _value, _flags , null, null, null);
                }
            }
        }
    }
    #endregion

Attribute:

/// <summary>
/// This attribute is used by the BasePage to identify properties that should be persisted to ViewState
/// Note: Private properties are not supported
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ViewState : Attribute
{
    //Marker
}

Helpers:

public static class PropertyExtension
{
    public static Boolean HasAttribute<T>(this PropertyInfo property)
    {
        object[] attrs = property.GetCustomAttributes(typeof(T), false);
        return attrs != null && attrs.Length == 1;
    }

}

EDIT

Jan has a valid point about performance, I did some profiling with the following results:

                 Without Attribute    With Attribute   Increase   Slower %
One Property     
       First Load        0,004897899     0,010734255    0,005836356    219
       Save, postback    0,002353861     0,010478008    0,008124147    445
       Load, Postback    0,001488807     0,00627482     0,004786013    421
   10 properties  
       First Load        0,006184096     0,015288675    0,009104579    247
       Save, postback    0,004061759     0,015052262    0,010990503    371
       Load, Postback    0,0015708       0,005833074    0,004262274    371

              % increase
Avg Page. 0,902215714567075 0,00648

On a Empty page the increase is considerable, but on an average page with a load of 1s this increase amounts to 0,01%.


Update : Using PostSharp, PostSharp4ViewState

Step 1 : Make sure your website is precompiled

Step 2 : Install PostSharp and PostSharp4ViewState

Step 3 : Reference PostSharp.Public And PostSharp4ViewState

Step 4 : Following is Code is now valid.

      [Persist(Mode=PersistMode.ViewState)]
      private string _name;
   public String Name { 
    get { return _name; }
    set { _name = value; }
   }

Upvotes: 2

Jan Jongboom
Jan Jongboom

Reputation: 27322

BBorg's solution is actually incredibly slow because of the heavy use of reflection.

Using PostSharp.Laos, by letting your attribute inherit from OnMethodBoundaryAspect, you can easily override public override void OnInvocation(MethodInvocationEventArgs eventArgs) and do all the magic in there. This will be way faster. Check for example the CacheAttribute example on the PostSharp homepage.

If you are really wanting bare speed, you can write a PostSharp plugin that weaves MSIL (GetFromViewState, SetInViewState methods or something) into your properties, that won't even have a performance penalty.

Upvotes: 1

Chris Shouts
Chris Shouts

Reputation: 5427

This functionality is built into NHibernate Burrow. If you don't happen to use NHibernate in your application, the source code for NHibernate Burrow is available here. Feel free to dig in, see how they did it, and rip out any parts that our useful to you (as long as you comply with the LGPL license).

The most relevant code seems to be in StatefulFieldProcessor.cs lines 51 - 72.

        /// <summary>
    /// Get the FieldInfo - Attribute pairs that have the customer attribute of type <typeparamref name="AT"/> 
    /// </summary>
    /// <typeparam name="AT"></typeparam>
    /// <returns></returns>
    protected IDictionary<FieldInfo, AT> GetFieldInfo<AT>() where AT : Attribute {
        IDictionary<FieldInfo, AT> retVal = new Dictionary<FieldInfo, AT>();
        foreach (FieldInfo fi in GetFields())
            foreach (AT a in Attribute.GetCustomAttributes(fi, typeof (AT)))
                retVal.Add(fi, a);
        return retVal;
    }

    protected IDictionary<FieldInfo, StatefulField> GetStatefulFields() {
        IDictionary<FieldInfo, StatefulField> retVal;
        Type controlType = Control.GetType();
        if (controlType.Assembly == webAssembly)
            return null;
        if (!fieldInfoCache.TryGetValue(controlType, out retVal))
            fieldInfoCache[controlType] = retVal = GetFieldInfo<StatefulField>();
        return retVal;
    }

Upvotes: 0

Related Questions