CSLA.NET 6.0.0
CSLA .NET is a software development framework that helps you build a reusable, maintainable object-oriented business layer for your app.
UndoableBase.cs
Go to the documentation of this file.
1//-----------------------------------------------------------------------
2// <copyright file="UndoableBase.cs" company="Marimer LLC">
3// Copyright (c) Marimer LLC. All rights reserved.
4// Website: https://www.lhotka.net/cslanet/
5// </copyright>
6// <summary>Implements n-level undo capabilities as</summary>
7//-----------------------------------------------------------------------
8using System;
9using System.Collections.Generic;
10using System.IO;
11using System.ComponentModel;
12using Csla.Properties;
13using Csla.Reflection;
16using System.Linq;
17
18namespace Csla.Core
19{
24 [Serializable()]
25 public abstract class UndoableBase : Csla.Core.BindableBase,
27 {
28 // keep a stack of object state values.
29 [NotUndoable()]
30 private readonly Stack<byte[]> _stateStack = new();
31 [NotUndoable]
32 private bool _bindingEdit;
33 [NotUndoable]
34 private ApplicationContext _applicationContext;
35
37
42 {
43 get => _applicationContext;
44 set
45 {
46 _applicationContext = value;
48 }
49 }
50
55 protected virtual void OnApplicationContextSet()
56 { }
57
61 protected UndoableBase()
62 {
63
64 }
65
71 [EditorBrowsable(EditorBrowsableState.Never)]
72 protected bool BindingEdit
73 {
74 get
75 {
76 return _bindingEdit;
77 }
78 set
79 {
80 _bindingEdit = value;
81 }
82 }
83
85 {
86 get { return EditLevel; }
87 }
88
92 [EditorBrowsable(EditorBrowsableState.Never)]
93 protected int EditLevel
94 {
95 get { return _stateStack.Count; }
96 }
97
98 void IUndoableObject.CopyState(int parentEditLevel, bool parentBindingEdit)
99 {
100 if (!parentBindingEdit)
101 CopyState(parentEditLevel);
102 }
103
104 void IUndoableObject.UndoChanges(int parentEditLevel, bool parentBindingEdit)
105 {
106 if (!parentBindingEdit)
107 UndoChanges(parentEditLevel);
108 }
109
110 void IUndoableObject.AcceptChanges(int parentEditLevel, bool parentBindingEdit)
111 {
112 if (!parentBindingEdit)
113 AcceptChanges(parentEditLevel);
114 }
115
120 [EditorBrowsable(EditorBrowsableState.Advanced)]
121 protected virtual void CopyingState()
122 {
123 }
124
129 [EditorBrowsable(EditorBrowsableState.Advanced)]
130 protected virtual void CopyStateComplete()
131 {
132 }
133
138 [EditorBrowsable(EditorBrowsableState.Never)]
139 protected internal void CopyState(int parentEditLevel)
140 {
141 CopyingState();
142
143 Type currentType = this.GetType();
144 var state = new MobileDictionary<string, object>();
145
146 if (this.EditLevel + 1 > parentEditLevel)
147 throw new UndoException(string.Format(Resources.EditLevelMismatchException, "CopyState"), this.GetType().Name, null, this.EditLevel, parentEditLevel - 1);
148
149 do
150 {
151 var currentTypeName = currentType.FullName;
152 // get the list of fields in this type
153 List<DynamicMemberHandle> handlers =
154 UndoableHandler.GetCachedFieldHandlers(currentType);
155 foreach (var h in handlers)
156 {
157 var value = h.DynamicMemberGet(this);
158 var fieldName = GetFieldName(currentTypeName, h.MemberName);
159
160 if (typeof(IUndoableObject).IsAssignableFrom(h.MemberType))
161 {
162 if (value == null)
163 {
164 // variable has no value - store that fact
165 state.Add(fieldName, null);
166 }
167 else
168 {
169 // this is a child object, cascade the call
170 ((IUndoableObject)value).CopyState(this.EditLevel + 1, BindingEdit);
171 }
172 }
173 else if (value is IMobileObject)
174 {
175 // this is a mobile object, store the serialized value
176 using MemoryStream buffer = new MemoryStream();
177 var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext);
178 formatter.Serialize(buffer, value);
179 state.Add(fieldName, buffer.ToArray());
180 }
181 else
182 {
183 // this is a normal field, simply trap the value
184 state.Add(fieldName, value);
185 }
186 }
187
188 currentType = currentType.BaseType;
189 } while (currentType != typeof(UndoableBase));
190
191 // serialize the state and stack it
192 using (MemoryStream buffer = new MemoryStream())
193 {
194 var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext);
195 formatter.Serialize(buffer, state);
196 _stateStack.Push(buffer.ToArray());
197 }
199 }
200
205 [EditorBrowsable(EditorBrowsableState.Advanced)]
206 protected virtual void UndoChangesComplete()
207 {
208 }
209
214 [EditorBrowsable(EditorBrowsableState.Advanced)]
215 protected virtual void UndoingChanges()
216 {
217 }
218
229 [EditorBrowsable(EditorBrowsableState.Never)]
230 protected internal void UndoChanges(int parentEditLevel)
231 {
233
234 // if we are a child object we might be asked to
235 // undo below the level of stacked states,
236 // so just do nothing in that case
237 if (EditLevel > 0)
238 {
239 if (this.EditLevel - 1 != parentEditLevel)
240 throw new UndoException(string.Format(Resources.EditLevelMismatchException, "UndoChanges"), this.GetType().Name, null, this.EditLevel, parentEditLevel + 1);
241
243 using (MemoryStream buffer = new MemoryStream(_stateStack.Pop()))
244 {
245 buffer.Position = 0;
246 var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext);
247 state = (MobileDictionary<string, object>)formatter.Deserialize(buffer);
248 }
249
250 Type currentType = this.GetType();
251
252 do
253 {
254 var currentTypeName = currentType.FullName;
255
256 // get the list of fields in this type
257 List<DynamicMemberHandle> handlers = UndoableHandler.GetCachedFieldHandlers(currentType);
258 foreach (var h in handlers)
259 {
260 // the field is undoable, so restore its value
261 var value = h.DynamicMemberGet(this);
262 var fieldName = GetFieldName(currentTypeName, h.MemberName);
263
264 if (typeof(IUndoableObject).IsAssignableFrom(h.MemberType))
265 {
266 // this is a child object
267 // see if the previous value was empty
268 //if (state.Contains(h.MemberName))
269 if (state.Contains(fieldName))
270 {
271 // previous value was empty - restore to empty
272 h.DynamicMemberSet(this, null);
273 }
274 else
275 {
276 // make sure the variable has a value
277 if (value != null)
278 {
279 // this is a child object, cascade the call.
280 ((IUndoableObject)value).UndoChanges(this.EditLevel, BindingEdit);
281 }
282 }
283 }
284 else if (value is IMobileObject && state[fieldName] != null)
285 {
286 // this is a mobile object, deserialize the value
287 using MemoryStream buffer = new MemoryStream((byte[])state[fieldName]);
288 buffer.Position = 0;
289 var formatter = SerializationFormatterFactory.GetFormatter(ApplicationContext);
290 var obj = formatter.Deserialize(buffer);
291 h.DynamicMemberSet(this, obj);
292 }
293 else
294 {
295 // this is a regular field, restore its value
296 h.DynamicMemberSet(this, state[fieldName]);
297 }
298 }
299
300 currentType = currentType.BaseType;
301 } while (currentType != typeof(UndoableBase));
302 }
304 }
305
310 [EditorBrowsable(EditorBrowsableState.Advanced)]
311 protected virtual void AcceptingChanges()
312 {
313 }
314
319 [EditorBrowsable(EditorBrowsableState.Advanced)]
320 protected virtual void AcceptChangesComplete()
321 {
322 }
323
333 [EditorBrowsable(EditorBrowsableState.Never)]
334 protected internal void AcceptChanges(int parentEditLevel)
335 {
337
338 if (this.EditLevel - 1 != parentEditLevel)
339 throw new UndoException(string.Format(Resources.EditLevelMismatchException, "AcceptChanges"), this.GetType().Name, null, this.EditLevel, parentEditLevel + 1);
340
341 if (EditLevel > 0)
342 {
343 _stateStack.Pop();
344 Type currentType = this.GetType();
345
346 do
347 {
348 // get the list of fields in this type
349 List<DynamicMemberHandle> handlers = UndoableHandler.GetCachedFieldHandlers(currentType);
350 foreach (var h in handlers)
351 {
352 // the field is undoable so see if it is a child object
353 if (typeof(Csla.Core.IUndoableObject).IsAssignableFrom(h.MemberType))
354 {
355 object value = h.DynamicMemberGet(this);
356 // make sure the variable has a value
357 if (value != null)
358 {
359 // it is a child object so cascade the call
360 ((Core.IUndoableObject)value).AcceptChanges(this.EditLevel, BindingEdit);
361 }
362 }
363 }
364
365 currentType = currentType.BaseType;
366 } while (currentType != typeof(UndoableBase));
367 }
369 }
370
377 private static string GetFieldName(string typeName, string memberName)
378 {
379 return typeName + "." + memberName;
380 }
381
382 internal static void ResetChildEditLevel(IUndoableObject child, int parentEditLevel, bool bindingEdit)
383 {
384 int targetLevel = parentEditLevel;
385 if (bindingEdit && targetLevel > 0 && child is not FieldManager.FieldDataManager)
386 targetLevel--;
387 // if item's edit level is too high,
388 // reduce it to match list
389 while (child.EditLevel > targetLevel)
390 child.AcceptChanges(targetLevel, false);
391 // if item's edit level is too low,
392 // increase it to match list
393 while (child.EditLevel < targetLevel)
394 child.CopyState(targetLevel, false);
395 }
396
407 protected override void OnGetState(SerializationInfo info, StateMode mode)
408 {
409 if (mode != StateMode.Undo)
410 {
411 info.AddValue("_bindingEdit", _bindingEdit);
412 if (_stateStack.Count > 0)
413 {
414 var stackArray = _stateStack.ToArray();
415 info.AddValue("_stateStack", stackArray);
416 }
417 }
418 base.OnGetState(info, mode);
419 }
420
431 protected override void OnSetState(SerializationInfo info, StateMode mode)
432 {
433 if (mode != StateMode.Undo)
434 {
435 _bindingEdit = info.GetValue<bool>("_bindingEdit");
436 if (info.Values.ContainsKey("_stateStack"))
437 {
438 var stackArray = info.GetValue<byte[][]>("_stateStack");
439 _stateStack.Clear();
440 foreach (var item in stackArray.Reverse())
441 _stateStack.Push(item);
442 }
443 }
444 base.OnSetState(info, mode);
445 }
446 }
447}
Provides consistent context information between the client and server DataPortal objects.
This class implements INotifyPropertyChanged and INotifyPropertyChanging in a serialization-safe mann...
Definition: BindableBase.cs:23
Defines a dictionary that can be serialized through the SerializationFormatterFactory....
bool Contains(K key)
Gets a value indicating whether the dictionary contains the specified key value.
Exception indicating a problem with the use of the n-level undo feature in CSLA .NET.
Implements n-level undo capabilities as described in Chapters 2 and 3.
Definition: UndoableBase.cs:27
bool BindingEdit
Gets or sets a value indicating whether n-level undo was invoked through IEditableObject.
Definition: UndoableBase.cs:73
virtual void CopyingState()
This method is invoked before the CopyState operation begins.
virtual void UndoingChanges()
This method is invoked after the UndoChanges operation is complete.
virtual void CopyStateComplete()
This method is invoked after the CopyState operation is complete.
virtual void AcceptChangesComplete()
This method is invoked after the AcceptChanges operation is complete.
override void OnGetState(SerializationInfo info, StateMode mode)
Override this method to insert your field values into the MobileFormatter serialzation stream.
UndoableBase()
Creates an instance of the type.
Definition: UndoableBase.cs:61
override void OnSetState(SerializationInfo info, StateMode mode)
Override this method to retrieve your field values from the MobileFormatter serialzation stream.
virtual void OnApplicationContextSet()
Method invoked after ApplicationContext is available.
Definition: UndoableBase.cs:55
virtual void UndoChangesComplete()
This method is invoked before the UndoChanges operation begins.
virtual void AcceptingChanges()
This method is invoked before the AcceptChanges operation begins.
A strongly-typed resource class, for looking up localized strings, etc.
static string EditLevelMismatchException
Looks up a localized string similar to Edit level mismatch in {0}.
Object containing the serialization data for a specific object.
Dictionary< string, FieldData > Values
Dictionary containg field data.
void AddValue(string name, object value)
Adds a value to the serialization stream.
Defines the methods required to participate in n-level undo within the CSLA .NET framework.
void CopyState(int parentEditLevel, bool parentBindingEdit)
Copies the state of the object and places the copy onto the state stack.
int EditLevel
Gets the current edit level of the object.
Implement if a class requires access to the CSLA ApplicationContext type.
ApplicationContext ApplicationContext
Gets or sets the current ApplicationContext object.
Interface to be implemented by any object that supports serialization by the SerializationFormatterFa...
StateMode
Indicates the reason the MobileFormatter functionality has been invoked.
Definition: StateMode.cs:20
@ Serializable
Prevents updating or inserting until the transaction is complete.