Automatic State Preservation Inner Workings

,

This section looks at how the state preservation system works behind the scenes. You see how properties decorated with the Stateful attribute are identified and how delegates for property accessors are created to store and retrieve property value. You also see how a custom ViewState class is used to load and save state via the ViewModelBase class.

The following sections are advanced in nature. Feel free to skip ahead. If you choose to use the custom state preservation system, you may return to this chapter at a later time.

Identifying Stateful ViewModels

The custom IStatePreservation interface is implemented by viewmodels that want to save and restore state. If a viewmodel implements IStatePreservation it has its LoadState or SaveState method called during page navigation.

LoadState and SaveState accept two parameters: a persistent state dictionary, which is normally the IsolatedStorageSettings.ApplicationSettings dictionary, and a transient state dictionary, which is normally the PhoneApplicationService state dictionary.

To coordinate this activity the custom StateManager class is used. When the application starts, the StateManager class subscribes to the relevant navigation events of the PhoneApplicationFrame, as shown:

public static void Initialize()
{
    PhoneApplicationService.Current.Deactivated += OnDeactivated;
    var frame = (PhoneApplicationFrame)Application.Current.RootVisual;
    frame.Navigating += OnNavigating;
    frame.Navigated += OnNavigated;
}

When navigating away from a page, the StateManager requests that the associated viewmodel save its state, if it implements IStatePreservation:

static void OnNavigating(object sender, NavigatingCancelEventArgs e)
{
    var frame = (PhoneApplicationFrame)Application.Current.RootVisual;
    var element = frame.Content as FrameworkElement;
    if (element != null)
    {
        IStatePreservation preserver
            = element.DataContext as IStatePreservation;
        if (preserver != null)
        {
            preserver.SaveState(
                IsolatedStorageSettings.ApplicationSettings,
                PhoneApplicationService.Current.State);
        }
    }
}

When navigating to a page, the StateManager requests that the viewmodel load its state:

static void OnNavigated(object sender, NavigationEventArgs e)
{
    var element = e.Content as FrameworkElement;
    if (element != null)
    {
        IStatePreservation statePreservation
            = element.DataContext as IStatePreservation;
        if (statePreservation != null)
        {
            statePreservation.LoadState(
                IsolatedStorageSettings.ApplicationSettings,
                PhoneApplicationService.Current.State);
        }
    }
}

It is not necessary to implement IStatePreservation on each viewmodel because the ViewModelBase class implements it. When the ViewModelBase class is instantiated, it locates all attributes decorated with the StateFul attribute and registers those properties with the custom ViewState class. The ProperyInfo class is used to create a delegate for both the get and set property accessors, as shown in the following excerpt:

void ReadStateAttributes()
{
    var properties = GetType().GetProperties();

    foreach (PropertyInfo propertyInfo in properties)
    {
        object[] attributes = propertyInfo.GetCustomAttributes(
                                    typeof(StatefulAttribute), true);

        if (attributes.Length <= 0)
        {
            continue;
        }

        StatefulAttribute attribute
            = (StatefulAttribute)attributes[0];
        var persistenceType = attribute.StateType;

        if (!propertyInfo.CanRead || !propertyInfo.CanWrite)
        {
            throw new InvalidOperationException(string.Format(
                "Property {0} must have a getter and a setter.",
                propertyInfo.Name));
        }

        /* Prevents access to internal closure warning. */
        PropertyInfo info = propertyInfo;

        viewState.RegisterState(
            propertyInfo.Name,
            () => info.GetValue(this, null),
            obj => info.SetValue(this, obj, null),
            persistenceType);
    }
}

The ViewState class provides thread state access to two dictionaries: one for persistent state; the other for transient state. The RegisterState method places the Func and Action associated with the state into its respective dictionary, as shown:

public void RegisterState<T>(
    string stateKey,
    Func<T> getterFunc,
    Action<T> setterAction,
    ApplicationStateType stateType)
{
    ArgumentValidator.AssertNotNull(stateKey, "propertyName");
    ArgumentValidator.AssertNotNull(getterFunc, "propertyGetterFunc");
    ArgumentValidator.AssertNotNull(setterAction, "propertySetterAction");

    if (stateType == ApplicationStateType.Persistent)
    {
        lock (persistentStateLock)
        {
            persistentState[stateKey]
                = new Accessor<T>(getterFunc, setterAction);
        }
    }
    else
    {
        lock (transientStateLock)
        {
            transientState[stateKey]
                = new Accessor<T>(getterFunc, setterAction);
        }
    }
}

The nested Accessor class is a container for a Func and an Action and is usually populated with lambda expressions for the getter and setter of a viewmodel property. The following excerpt shows the nested Accessor class:

class Accessor<T> : IStateAccessor
{
    readonly Func<T> getter;
    readonly Action<T> setter;

    public Accessor(Func<T> getter, Action<T> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }

    public object Value
    {
        get
        {
            return getter();
        }
        set
        {
            setter((T)value);
        }
    }
}

When the StateManager calls the IStatePreservation.SaveState method of the ViewModelBase instance, the ViewModelBase class calls the ViewState object’s SavePersistentState and SaveTransientState methods, which are shown in the following excerpt:

public void SavePersistentState(IDictionary<string, object> stateDictionary)
{
    SaveState(stateDictionary, persistentState, persistentStateLock);
}

public void SaveTransientState(IDictionary<string, object> stateDictionary)
{
    SaveState(stateDictionary, transientState, transientStateLock);
}

The ViewState.SaveState method enumerates the Accessor objects in the specified accessors dictionary and resolves the value to be serialized using the Accessor.Value property. The value is serialized to a byte array, which is then stored in the specified state dictionary, as shown in the following excerpt:

void SaveState(
        IDictionary<string, object> stateDictionary,
        Dictionary<string, IStateAccessor> accessors,
        object propertiesLock)
{
    lock (propertiesLock)
    {
        foreach (KeyValuePair<string, IStateAccessor> pair in accessors)
        {
            string stateKey = pair.Key;
            IStateAccessor accessor = pair.Value;

            object accessorValue = accessor.Value;

            if (accessorValue == null)
            {
                stateDictionary.Remove(stateKey);
                continue;
            }

            byte[] bytes;
            try
            {
                bytes = Serialize(accessorValue);
            }
            catch (Exception ex)
            {
                stateDictionary[pair.Key] = null;
                Debug.Assert(false,
                            "Unable to serialize state value. " + ex);
                continue;
            }

            stateDictionary[stateKey] = bytes;
        }
    }
}

Using the Silverlight Serializer for Binary Serialization

Mike Talbot’s Silverlight Serializer is used in the sample code to serialize objects. The Silverlight Serializer has better performance and is easier to use than the built-in DataContractSerializer.

See http://whydoidoit.com/silverlight-serialize for more information on the Silverlight Serializer.

When using the Silverlight Serializer, an object can be converted to and from a byte array without the need to decorate it with a myriad of DataMember attributes, as is necessary with the DataContractSerializer.

The SilverlightSerializer class is used in the custom ViewState class to serialize and deserialize viewmodel data, as shown in the following excerpt:

protected byte[] Serialize(object value)
{
    byte[] state = SilverlightSerializer.Serialize(value);
    return state;
}

protected T Deserialize<T>(byte[] data) where T : class
{
    T result = SilverlightSerializer.Deserialize<T>(data);
    return result;
}

Within the ViewState class, state restoration works in much the same way as state persistence, but in reverse. We enumerate over the Accessor objects and attempt to retrieve a corresponding state value from the state dictionary. This value is then converted from a byte array to an object using the Deserialize method, as shown in the following excerpt:

void LoadState(
        IDictionary<string, object> stateDictionary,
        Dictionary<string, IStateAccessor> accessors,
        object propertiesLock)
{
    lock (propertiesLock)
    {
        foreach (KeyValuePair<string, IStateAccessor> pair in accessors)
        {
            object stateValue;
            string stateKey = pair.Key;
            IStateAccessor accessor = pair.Value;

            if (!stateDictionary.TryGetValue(stateKey, out stateValue))
            {
                continue;
            }
            byte[] bytes = stateValue as byte[];

            if (bytes == null)
            {
                Debug.Assert(false, "state value is not a byte[]");
                continue;
            }

            object deserializedValue;
            try
            {
                deserializedValue = Deserialize(bytes);
            }
            catch (Exception ex)
            {
                string message = "Unable to deserialize bytes. " + ex; Debug.Assert(false, message);
                continue;
            }

            if (deserializedValue == null)
            {
                const string message
                        = "Deserialized object should not be null."; Debug.Assert(false, message);
                continue;
            }

            try
            {
                accessor.Value = deserializedValue;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unable to set state value. " + ex);
                continue;
            }
        }
    }
}

The ViewModelBase class includes various state registration methods that can be used instead of the State attribute to specify stateful properties. The RegisterStatefulProperty method, for example, allows you to specify a stateful property using a lambda expression, as shown in the following example:

RegisterStatefulProperty(ApplicationStateType.Transient, () => SearchText);

The RegisterStatefulProperty method has an optional setAction parameter, which allows you to provide an alternative handler for setting the value; this is useful for properties that lack a public set accessor. The RegisterStatefulProperty calls the overloaded private method by the same name, as shown:

protected void RegisterStatefulProperty<TProperty>(
    ApplicationStateType applicationStateType,
    Expression<Func<TProperty>> expression,
    Action<TProperty> setAction = null)
{
    RegisterStatefulProperty((name, getter, setter)
        => viewState.RegisterState(
                    name, getter, setter, applicationStateType),
                expression, setAction);
}

The private RegisterStatefulProperty method retrieves the PropertyInfo instance for the expression (described in the next section) and uses it to create delegates for the property accessors. It then invokes the specified registerAction, which, in this case, is a call to viewState.RegisterState. The RegisterStatefulProperty method is shown in the following excerpt:

void RegisterStatefulProperty<T>(
    Action<string, Func<T>, Action<T>> registerAction,
    Expression<Func<T>> expression, Action<T> setAction = null)
{
    ArgumentValidator.AssertNotNull(registerAction, "registerAction");
    ArgumentValidator.AssertNotNull(expression, "expression");

    PropertyInfo propertyInfo = PropertyUtility.GetPropertyInfo(expression);
    string name = propertyInfo.Name;
    var propertyGetterFunc = propertyInfo.CreateGetter<T>(this);

    if (setAction == null)
    {
        try
        {
            setAction = propertyInfo.CreateSetter<T>(this);
        }
        catch (Exception ex)
        {
            string message = string.Format(
                "Unable to get setter for property '{0}' {1} ", name, ex);
            Console.WriteLine(message);
            Debug.Assert(false, message);
            return;
        }
    }
    registerAction(name, propertyGetterFunc, setAction);
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.174.183