SuspensionManager.cs (13046B)
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Runtime.Serialization; 6 using System.Text; 7 using System.Threading.Tasks; 8 using Windows.ApplicationModel; 9 using Windows.Storage; 10 using Windows.Storage.Streams; 11 using Windows.UI.Xaml; 12 using Windows.UI.Xaml.Controls; 13 14 namespace File360.Common 15 { 16 /// <summary> 17 /// SuspensionManager captures global session state to simplify process lifetime management 18 /// for an application. Note that session state will be automatically cleared under a variety 19 /// of conditions and should only be used to store information that would be convenient to 20 /// carry across sessions, but that should be discarded when an application crashes or is 21 /// upgraded. 22 /// </summary> 23 internal sealed class SuspensionManager 24 { 25 private static Dictionary<string, object> _sessionState = new Dictionary<string, object>(); 26 private static List<Type> _knownTypes = new List<Type>(); 27 private const string sessionStateFilename = "_sessionState.xml"; 28 29 /// <summary> 30 /// Provides access to global session state for the current session. This state is 31 /// serialized by <see cref="SaveAsync"/> and restored by 32 /// <see cref="RestoreAsync"/>, so values must be serializable by 33 /// <see cref="DataContractSerializer"/> and should be as compact as possible. Strings 34 /// and other self-contained data types are strongly recommended. 35 /// </summary> 36 public static Dictionary<string, object> SessionState 37 { 38 get { return _sessionState; } 39 } 40 41 /// <summary> 42 /// List of custom types provided to the <see cref="DataContractSerializer"/> when 43 /// reading and writing session state. Initially empty, additional types may be 44 /// added to customize the serialization process. 45 /// </summary> 46 public static List<Type> KnownTypes 47 { 48 get { return _knownTypes; } 49 } 50 51 /// <summary> 52 /// Save the current <see cref="SessionState"/>. Any <see cref="Frame"/> instances 53 /// registered with <see cref="RegisterFrame"/> will also preserve their current 54 /// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity 55 /// to save its state. 56 /// </summary> 57 /// <returns>An asynchronous task that reflects when session state has been saved.</returns> 58 public static async Task SaveAsync() 59 { 60 try 61 { 62 // Save the navigation state for all registered frames 63 foreach (var weakFrameReference in _registeredFrames) 64 { 65 Frame frame; 66 if (weakFrameReference.TryGetTarget(out frame)) 67 { 68 SaveFrameNavigationState(frame); 69 } 70 } 71 72 // Serialize the session state synchronously to avoid asynchronous access to shared 73 // state 74 MemoryStream sessionData = new MemoryStream(); 75 DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes); 76 serializer.WriteObject(sessionData, _sessionState); 77 78 // Get an output stream for the SessionState file and write the state asynchronously 79 StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(sessionStateFilename, CreationCollisionOption.ReplaceExisting); 80 using (Stream fileStream = await file.OpenStreamForWriteAsync()) 81 { 82 sessionData.Seek(0, SeekOrigin.Begin); 83 await sessionData.CopyToAsync(fileStream); 84 } 85 } 86 catch (Exception e) 87 { 88 throw new SuspensionManagerException(e); 89 } 90 } 91 92 /// <summary> 93 /// Restores previously saved <see cref="SessionState"/>. Any <see cref="Frame"/> instances 94 /// registered with <see cref="RegisterFrame"/> will also restore their prior navigation 95 /// state, which in turn gives their active <see cref="Page"/> an opportunity restore its 96 /// state. 97 /// </summary> 98 /// <param name="sessionBaseKey">An optional key that identifies the type of session. 99 /// This can be used to distinguish between multiple application launch scenarios.</param> 100 /// <returns>An asynchronous task that reflects when session state has been read. The 101 /// content of <see cref="SessionState"/> should not be relied upon until this task 102 /// completes.</returns> 103 public static async Task RestoreAsync(String sessionBaseKey = null) 104 { 105 _sessionState = new Dictionary<String, Object>(); 106 107 try 108 { 109 // Get the input stream for the SessionState file 110 StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(sessionStateFilename); 111 using (IInputStream inStream = await file.OpenSequentialReadAsync()) 112 { 113 // Deserialize the Session State 114 DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes); 115 _sessionState = (Dictionary<string, object>)serializer.ReadObject(inStream.AsStreamForRead()); 116 } 117 118 // Restore any registered frames to their saved state 119 foreach (var weakFrameReference in _registeredFrames) 120 { 121 Frame frame; 122 if (weakFrameReference.TryGetTarget(out frame) && (string)frame.GetValue(FrameSessionBaseKeyProperty) == sessionBaseKey) 123 { 124 frame.ClearValue(FrameSessionStateProperty); 125 RestoreFrameNavigationState(frame); 126 } 127 } 128 } 129 catch (Exception e) 130 { 131 throw new SuspensionManagerException(e); 132 } 133 } 134 135 private static DependencyProperty FrameSessionStateKeyProperty = 136 DependencyProperty.RegisterAttached("_FrameSessionStateKey", typeof(String), typeof(SuspensionManager), null); 137 private static DependencyProperty FrameSessionBaseKeyProperty = 138 DependencyProperty.RegisterAttached("_FrameSessionBaseKeyParams", typeof(String), typeof(SuspensionManager), null); 139 private static DependencyProperty FrameSessionStateProperty = 140 DependencyProperty.RegisterAttached("_FrameSessionState", typeof(Dictionary<String, Object>), typeof(SuspensionManager), null); 141 private static List<WeakReference<Frame>> _registeredFrames = new List<WeakReference<Frame>>(); 142 143 /// <summary> 144 /// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to 145 /// and restored from <see cref="SessionState"/>. Frames should be registered once 146 /// immediately after creation if they will participate in session state management. Upon 147 /// registration if state has already been restored for the specified key 148 /// the navigation history will immediately be restored. Subsequent invocations of 149 /// <see cref="RestoreAsync"/> will also restore navigation history. 150 /// </summary> 151 /// <param name="frame">An instance whose navigation history should be managed by 152 /// <see cref="SuspensionManager"/></param> 153 /// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to 154 /// store navigation-related information.</param> 155 /// <param name="sessionBaseKey">An optional key that identifies the type of session. 156 /// This can be used to distinguish between multiple application launch scenarios.</param> 157 public static void RegisterFrame(Frame frame, String sessionStateKey, String sessionBaseKey = null) 158 { 159 if (frame.GetValue(FrameSessionStateKeyProperty) != null) 160 { 161 throw new InvalidOperationException("Frames can only be registered to one session state key"); 162 } 163 164 if (frame.GetValue(FrameSessionStateProperty) != null) 165 { 166 throw new InvalidOperationException("Frames must be either be registered before accessing frame session state, or not registered at all"); 167 } 168 169 if (!string.IsNullOrEmpty(sessionBaseKey)) 170 { 171 frame.SetValue(FrameSessionBaseKeyProperty, sessionBaseKey); 172 sessionStateKey = sessionBaseKey + "_" + sessionStateKey; 173 } 174 175 // Use a dependency property to associate the session key with a frame, and keep a list of frames whose 176 // navigation state should be managed 177 frame.SetValue(FrameSessionStateKeyProperty, sessionStateKey); 178 _registeredFrames.Add(new WeakReference<Frame>(frame)); 179 180 // Check to see if navigation state can be restored 181 RestoreFrameNavigationState(frame); 182 } 183 184 /// <summary> 185 /// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/> 186 /// from <see cref="SessionState"/>. Any navigation state previously captured will be 187 /// removed. 188 /// </summary> 189 /// <param name="frame">An instance whose navigation history should no longer be 190 /// managed.</param> 191 public static void UnregisterFrame(Frame frame) 192 { 193 // Remove session state and remove the frame from the list of frames whose navigation 194 // state will be saved (along with any weak references that are no longer reachable) 195 SessionState.Remove((String)frame.GetValue(FrameSessionStateKeyProperty)); 196 _registeredFrames.RemoveAll((weakFrameReference) => 197 { 198 Frame testFrame; 199 return !weakFrameReference.TryGetTarget(out testFrame) || testFrame == frame; 200 }); 201 } 202 203 /// <summary> 204 /// Provides storage for session state associated with the specified <see cref="Frame"/>. 205 /// Frames that have been previously registered with <see cref="RegisterFrame"/> have 206 /// their session state saved and restored automatically as a part of the global 207 /// <see cref="SessionState"/>. Frames that are not registered have transient state 208 /// that can still be useful when restoring pages that have been discarded from the 209 /// navigation cache. 210 /// </summary> 211 /// <remarks>Apps may choose to rely on <see cref="NavigationHelper"/> to manage 212 /// page-specific state instead of working with frame session state directly.</remarks> 213 /// <param name="frame">The instance for which session state is desired.</param> 214 /// <returns>A collection of state subject to the same serialization mechanism as 215 /// <see cref="SessionState"/>.</returns> 216 public static Dictionary<String, Object> SessionStateForFrame(Frame frame) 217 { 218 var frameState = (Dictionary<String, Object>)frame.GetValue(FrameSessionStateProperty); 219 220 if (frameState == null) 221 { 222 var frameSessionKey = (String)frame.GetValue(FrameSessionStateKeyProperty); 223 if (frameSessionKey != null) 224 { 225 // Registered frames reflect the corresponding session state 226 if (!_sessionState.ContainsKey(frameSessionKey)) 227 { 228 _sessionState[frameSessionKey] = new Dictionary<String, Object>(); 229 } 230 frameState = (Dictionary<String, Object>)_sessionState[frameSessionKey]; 231 } 232 else 233 { 234 // Frames that aren't registered have transient state 235 frameState = new Dictionary<String, Object>(); 236 } 237 frame.SetValue(FrameSessionStateProperty, frameState); 238 } 239 return frameState; 240 } 241 242 private static void RestoreFrameNavigationState(Frame frame) 243 { 244 var frameState = SessionStateForFrame(frame); 245 if (frameState.ContainsKey("Navigation")) 246 { 247 frame.SetNavigationState((String)frameState["Navigation"]); 248 } 249 } 250 251 private static void SaveFrameNavigationState(Frame frame) 252 { 253 var frameState = SessionStateForFrame(frame); 254 frameState["Navigation"] = frame.GetNavigationState(); 255 } 256 } 257 public class SuspensionManagerException : Exception 258 { 259 public SuspensionManagerException() 260 { 261 } 262 263 public SuspensionManagerException(Exception e) 264 : base("SuspensionManager failed", e) 265 { 266 267 } 268 } 269 }