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.
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;
}
}
}
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);
}
3.144.37.12