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.
Csla.Xaml.Shared/ViewModelBase.cs
Go to the documentation of this file.
1//-----------------------------------------------------------------------
2// <copyright file="ViewModelBase.cs" company="Marimer LLC">
3// Copyright (c) Marimer LLC. All rights reserved.
4// Website: https://cslanet.com
5// </copyright>
6// <summary>Base class used to create ViewModel objects that</summary>
7//-----------------------------------------------------------------------
8using System;
9using System.Collections;
10using System.Collections.Specialized;
11using System.ComponentModel;
12using System.ComponentModel.DataAnnotations;
13using System.Threading.Tasks;
14using System.Windows;
15using Csla.Core;
16using Csla.Rules;
17#if WINDOWS_UWP
18using Windows.UI.Xaml;
19#endif
20
21#if ANDROID
22namespace Csla.Axml
23#elif IOS
24namespace Csla.Iosui
25#else
26namespace Csla.Xaml
27#endif
28{
34#if ANDROID || IOS || XAMARIN
35 public abstract class ViewModelBase<T> : INotifyPropertyChanged, IViewModel
36#else
37 public abstract class ViewModelBase<T> : DependencyObject,
38 INotifyPropertyChanged, IViewModel
39#endif
40 {
42
43#if ANDROID || IOS || XAMARIN || WINDOWS_UWP
44 private T _model;
48 public T Model
49 {
50 get { return _model; }
51 set
52 {
53 if (!ReferenceEquals(value, _model))
54 {
55 var oldValue = _model;
56 _model = value;
57 this.OnModelChanged((T)oldValue, _model);
58 OnPropertyChanged(nameof(Model));
59 }
60 }
61 }
62#else
66 public static readonly DependencyProperty ModelProperty =
67 DependencyProperty.Register("Model", typeof(T), typeof(ViewModelBase<T>),
68 new PropertyMetadata((o, e) =>
69 {
70 var viewmodel = (ViewModelBase<T>)o;
71 viewmodel.OnModelChanged((T)e.OldValue, (T)e.NewValue);
72 }));
73
77 public T Model
78 {
79 get { return (T)GetValue(ModelProperty); }
80 set { SetValue(ModelProperty, value); }
81 }
82#endif
83
89#if ANDROID || IOS || XAMARIN
90 public bool ManageObjectLifetimeProperty;
91#else
92 public static readonly DependencyProperty ManageObjectLifetimeProperty =
93 DependencyProperty.Register("ManageObjectLifetime", typeof(bool),
94 typeof(ViewModelBase<T>), new PropertyMetadata(true));
95#endif
101 [Browsable(false)]
102 [Display(AutoGenerateField = false)]
103 [ScaffoldColumn(false)]
104 public bool ManageObjectLifetime
105 {
106#if ANDROID || IOS || XAMARIN
107 get { return (bool)ManageObjectLifetimeProperty; }
108 set { ManageObjectLifetimeProperty = value; }
109#else
110 get { return (bool)GetValue(ManageObjectLifetimeProperty); }
111 set { SetValue(ManageObjectLifetimeProperty, value); }
112#endif
113 }
114
115 private bool _isBusy;
116
121 public bool IsBusy
122 {
123 get { return _isBusy; }
124 protected set
125 {
126 if (value != _isBusy)
127 {
128 _isBusy = value;
129 OnPropertyChanged(nameof(IsBusy));
130 OnSetProperties();
131 }
132 }
133 }
134
135 private bool _isDirty;
136
141 public virtual bool IsDirty
142 {
143 get
144 {
145 return _isDirty;
146 }
147 protected set
148 {
149 if (_isDirty != value)
150 {
151 _isDirty = value;
152 OnPropertyChanged(nameof(IsDirty));
153 }
154 }
155 }
156
157 private bool _isValid;
158
163 public virtual bool IsValid
164 {
165 get
166 {
167 return _isValid;
168 }
169 protected set
170 {
171 if (_isValid != value)
172 {
173 _isValid = value;
174 OnPropertyChanged(nameof(IsValid));
175 }
176 }
177 }
178
179 private bool _canSave = false;
180
185 public virtual bool CanSave
186 {
187 get
188 {
189 return _canSave;
190 }
191 protected set
192 {
193 if (_canSave != value)
194 {
195 _canSave = value;
196 OnPropertyChanged(nameof(CanSave));
197 }
198 }
199 }
200
201 private bool _canCancel = false;
202
207 public virtual bool CanCancel
208 {
209 get
210 {
211 return _canCancel;
212 }
213 protected set
214 {
215 if (_canCancel != value)
216 {
217 _canCancel = value;
218 OnPropertyChanged(nameof(CanCancel));
219 }
220 }
221 }
222
223 private bool _canCreate = false;
224
230 public virtual bool CanCreate
231 {
232 get
233 {
234 return _canCreate;
235 }
236 protected set
237 {
238 if (_canCreate != value)
239 {
240 _canCreate = value;
241 OnPropertyChanged(nameof(CanCreate));
242 }
243 }
244 }
245
246 private bool _canDelete = false;
247
252 public virtual bool CanDelete
253 {
254 get
255 {
256 return _canDelete;
257 }
258 protected set
259 {
260 if (_canDelete != value)
261 {
262 _canDelete = value;
263 OnPropertyChanged(nameof(CanDelete));
264 }
265 }
266 }
267
268 private bool _canFetch = false;
269
275 public virtual bool CanFetch
276 {
277 get
278 {
279 return _canFetch;
280 }
281 protected set
282 {
283 if (_canFetch != value)
284 {
285 _canFetch = value;
286 OnPropertyChanged(nameof(CanFetch));
287 }
288 }
289 }
290
291 private bool _canRemove = false;
292
297 public virtual bool CanRemove
298 {
299 get
300 {
301 return _canRemove;
302 }
303 protected set
304 {
305 if (_canRemove != value)
306 {
307 _canRemove = value;
308 OnPropertyChanged(nameof(CanRemove));
309 }
310 }
311 }
312
313 private bool _canAddNew = false;
314
319 public virtual bool CanAddNew
320 {
321 get
322 {
323 return _canAddNew;
324 }
325 protected set
326 {
327 if (_canAddNew != value)
328 {
329 _canAddNew = value;
330 OnPropertyChanged(nameof(CanAddNew));
331 }
332 }
333 }
334
335 private void SetProperties()
336 {
337 bool isObjectBusy = false;
338 if (Model is INotifyBusy busyObject && busyObject.IsBusy)
339 isObjectBusy = true;
340
341 // Does Model instance implement ITrackStatus
342 if (Model is ITrackStatus targetObject)
343 {
344 var canDeleteInstance = BusinessRules.HasPermission(ApplicationContext, AuthorizationActions.DeleteObject, targetObject);
345
346 IsDirty = targetObject.IsDirty;
347 IsValid = targetObject.IsValid;
348 CanSave = CanEditObject && targetObject.IsSavable && !isObjectBusy;
349 CanCancel = CanEditObject && targetObject.IsDirty && !isObjectBusy;
350 CanCreate = CanCreateObject && !targetObject.IsDirty && !isObjectBusy;
351 CanDelete = CanDeleteObject && !isObjectBusy && canDeleteInstance;
352 CanFetch = CanGetObject && !targetObject.IsDirty && !isObjectBusy;
353
354 // Set properties for List
355 if (Model is ICollection list)
356 {
357 Type itemType = Utilities.GetChildItemType(Model.GetType());
358 if (itemType == null)
359 {
360 CanAddNew = false;
361 CanRemove = false;
362 }
363 else
364 {
365 CanRemove = BusinessRules.HasPermission(ApplicationContext, AuthorizationActions.DeleteObject, itemType) &&
366 list.Count > 0 && !isObjectBusy;
367 CanAddNew = BusinessRules.HasPermission(ApplicationContext, AuthorizationActions.CreateObject, itemType) &&
368 !isObjectBusy;
369 }
370 }
371 else
372 {
373 CanRemove = false;
374 CanAddNew = false;
375 }
376 }
377
378 // Else if Model instance implement ICollection
379 else if (Model is ICollection list)
380 {
381 Type itemType = Utilities.GetChildItemType(Model.GetType());
382 if (itemType == null)
383 {
384 CanAddNew = false;
385 CanRemove = false;
386 }
387 else
388 {
389 CanRemove = BusinessRules.HasPermission(ApplicationContext, AuthorizationActions.DeleteObject, itemType) &&
390 list.Count > 0 && !isObjectBusy;
391 CanAddNew = BusinessRules.HasPermission(ApplicationContext, AuthorizationActions.CreateObject, itemType) &&
392 !isObjectBusy;
393 }
394 }
395 else
396 {
397 IsDirty = false;
398 IsValid = false;
399 CanCancel = false;
400 CanCreate = CanCreateObject;
401 CanDelete = false;
402 CanFetch = CanGetObject && !IsBusy;
403 CanSave = false;
404 CanRemove = false;
405 CanAddNew = false;
406 }
407 }
408
409 private bool _canCreateObject;
410
415 public virtual bool CanCreateObject
416 {
417 get
418 {
419 SetPropertiesAtObjectLevel();
420 return _canCreateObject;
421 }
422 protected set
423 {
424 if (_canCreateObject != value)
425 {
426 _canCreateObject = value;
427 OnPropertyChanged(nameof(CanCreateObject));
428 }
429 }
430 }
431
432 private bool _canGetObject;
433
438 public virtual bool CanGetObject
439 {
440 get
441 {
442 SetPropertiesAtObjectLevel();
443 return _canGetObject;
444 }
445 protected set
446 {
447 if (_canGetObject != value)
448 {
449 _canGetObject = value;
450 OnPropertyChanged(nameof(CanGetObject));
451 }
452 }
453 }
454
455 private bool _canEditObject;
456
462 public virtual bool CanEditObject
463 {
464 get
465 {
466 SetPropertiesAtObjectLevel();
467 return _canEditObject;
468 }
469 protected set
470 {
471 if (_canEditObject != value)
472 {
473 _canEditObject = value;
474 OnPropertyChanged(nameof(CanEditObject));
475 }
476 }
477 }
478
479 private bool _canDeleteObject;
480
486 public virtual bool CanDeleteObject
487 {
488 get
489 {
490 SetPropertiesAtObjectLevel();
491 return _canDeleteObject;
492 }
493 protected set
494 {
495 if (_canDeleteObject != value)
496 {
497 _canDeleteObject = value;
498 OnPropertyChanged(nameof(CanDeleteObject));
499 }
500 }
501 }
502
503 private bool ObjectPropertiesSet;
504
509 private void SetPropertiesAtObjectLevel()
510 {
511 if (ObjectPropertiesSet)
512 return;
513 ObjectPropertiesSet = true;
514
515 Type sourceType = typeof(T);
516
517 CanCreateObject = BusinessRules.HasPermission(ApplicationContext, Rules.AuthorizationActions.CreateObject, sourceType);
518 CanGetObject = BusinessRules.HasPermission(ApplicationContext, Rules.AuthorizationActions.GetObject, sourceType);
519 CanEditObject = BusinessRules.HasPermission(ApplicationContext, Rules.AuthorizationActions.EditObject, sourceType);
520 CanDeleteObject = BusinessRules.HasPermission(ApplicationContext, Rules.AuthorizationActions.DeleteObject, sourceType);
521
522 // call SetProperties to set "instance" values
523 OnSetProperties();
524 }
525
531 public virtual async Task<T> RefreshAsync<F>(Func<Task<T>> factory)
532 {
533 T result = default;
534 try
535 {
536 IsBusy = true;
537 result = await factory.Invoke();
538 Model = result;
539 }
540 finally
541 {
542 IsBusy = false;
543 }
544 return result;
545 }
546
551 public virtual async Task<T> SaveAsync()
552 {
553 try
554 {
555 UnhookChangedEvents(Model);
556 var savable = Model as ISavable;
557 if (ManageObjectLifetime)
558 {
559 // clone the object if possible
560 if (Model is ICloneable clonable)
561 savable = (ISavable)clonable.Clone();
562
563 //apply changes
564 if (savable is ISupportUndo undoable)
565 undoable.ApplyEdit();
566 }
567
568 IsBusy = true;
569 Model = (T)await savable.SaveAsync();
570 }
571 finally
572 {
573 HookChangedEvents(Model);
574 IsBusy = false;
575 }
576 return Model;
577 }
578
583 protected virtual void DoCancel()
584 {
585 if (ManageObjectLifetime)
586 {
587 if (Model is ISupportUndo undo)
588 {
589 UnhookChangedEvents(Model);
590 try
591 {
592 undo.CancelEdit();
593 undo.BeginEdit();
594 }
595 finally
596 {
597 HookChangedEvents(Model);
598 OnSetProperties();
599 }
600 }
601 }
602 }
603
604#if (ANDROID || IOS) || XAMARIN
609 protected virtual void BeginAddNew()
610 {
611#if ANDROID || IOS
612 var ibl = (Model as System.ComponentModel.IBindingList);
613#else
614 var ibl = (Model as IBindingList);
615#endif
616 if (ibl != null)
617 {
618 ibl.AddNew();
619 }
620 else
621 {
622 // else try to use as IObservableBindingList
623 var iobl = ((IObservableBindingList)Model);
624 iobl.AddNew();
625 }
626 OnSetProperties();
627 }
628#else
633 protected virtual object DoAddNew()
634 {
635 object result;
636 // typically use ObserableCollection
637 if (Model is IObservableBindingList iobl)
638 {
639 result = iobl.AddNew();
640 }
641 else
642 {
643 // else try to use as BindingList
644 var ibl = ((IBindingList)Model);
645 result = ibl.AddNew();
646 }
647 OnSetProperties();
648 return result;
649 }
650#endif
651
656 protected virtual void DoRemove(object item)
657 {
658 ((IList)Model).Remove(item);
659 OnSetProperties();
660 }
661
666 protected virtual void DoDelete()
667 {
668 ((IEditableBusinessObject)Model).Delete();
669 }
670
678 protected virtual void OnModelChanged(T oldValue, T newValue)
679 {
680 if (ReferenceEquals(oldValue, newValue)) return;
681
682 if (ManageObjectLifetime && newValue is ISupportUndo undo)
683 undo.BeginEdit();
684
685 // unhook events from old value
686 if (oldValue != null)
687 {
688 UnhookChangedEvents(oldValue);
689
690 if (oldValue is INotifyBusy nb)
691 nb.BusyChanged -= Model_BusyChanged;
692 }
693
694 // hook events on new value
695 if (newValue != null)
696 {
697 HookChangedEvents(newValue);
698
699 if (newValue is INotifyBusy nb)
700 nb.BusyChanged += Model_BusyChanged;
701 }
702
703 OnSetProperties();
704 }
705
710 protected void UnhookChangedEvents(T model)
711 {
712 if (model is INotifyPropertyChanged npc)
713 npc.PropertyChanged -= Model_PropertyChanged;
714
715 if (model is INotifyChildChanged ncc)
716 ncc.ChildChanged -= Model_ChildChanged;
717
718 if (model is INotifyCollectionChanged cc)
719 cc.CollectionChanged -= Model_CollectionChanged;
720 }
721
726 private void HookChangedEvents(T model)
727 {
728 if (model is INotifyPropertyChanged npc)
729 npc.PropertyChanged += Model_PropertyChanged;
730
731 if (model is INotifyChildChanged ncc)
732 ncc.ChildChanged += Model_ChildChanged;
733
734 if (model is INotifyCollectionChanged cc)
735 cc.CollectionChanged += Model_CollectionChanged;
736 }
737
742 protected virtual void OnSetProperties()
743 {
744 SetProperties();
745 }
746
747 private void Model_BusyChanged(object sender, BusyChangedEventArgs e)
748 {
749 // only set busy state for entire object. Ignore busy state based
750 // on asynch rules being active
751 if (string.IsNullOrEmpty(e.PropertyName))
752 IsBusy = e.Busy;
753 else
754 OnSetProperties();
755 }
756
757 private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
758 {
759 OnSetProperties();
760 }
761
762 private void Model_ChildChanged(object sender, ChildChangedEventArgs e)
763 {
764 OnSetProperties();
765 }
766
767 private void Model_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
768 {
769 OnSetProperties();
770 }
771
772 object IViewModel.Model
773 {
774 get { return Model; }
775 set { Model = (T)value; }
776 }
777
781 public event PropertyChangedEventHandler PropertyChanged;
782
787 protected virtual void OnPropertyChanged(string propertyName)
788 {
789 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
790 }
791 }
792}
Provides consistent context information between the client and server DataPortal objects.
Event arguments for the BusyChanged event.
string PropertyName
Property for which the Busy value has changed.
Contains event data about the changed child object.
Tracks the business rules for a business object.
static bool HasPermission(ApplicationContext applicationContext, AuthorizationActions action, Type objectType)
Checks per-type authorization rules.
ApplicationContextManager for WPF applications
static ApplicationContext GetApplicationContext()
Gets the current ApplicationContext.
Base class used to create ViewModel objects that implement their own commands/verbs/actions.
virtual void DoRemove(object item)
Removes an item from the Model (if it is a collection).
PropertyChangedEventHandler PropertyChanged
Event raised when a property changes.
virtual void OnModelChanged(T oldValue, T newValue)
Invoked when the Model changes, allowing event handlers to be unhooked from the old object and hooked...
virtual void OnSetProperties()
Override this method to hook into to logic of setting properties when model is changed or edited.
virtual void DoCancel()
Cancels changes made to the model if ManagedObjectLifetime is true.
virtual void DoDelete()
Marks the Model for deletion (if it is an editable root object).
void UnhookChangedEvents(T model)
Unhooks changed event handlers from the model.
virtual async Task< T > SaveAsync()
Saves the Model, first committing changes if ManagedObjectLifetime is true.
virtual object DoAddNew()
Adds a new item to the Model (if it is a collection).
virtual void OnPropertyChanged(string propertyName)
Raise the PropertyChanged event.
Defines the common methods required by all editable CSLA single objects.
Interface defining an object that notifies when it is busy executing an asynchronous operation.
Definition: INotifyBusy.cs:17
bool IsBusy
Gets a value indicating whether the object, or any of the object's child objects, are busy running an...
Definition: INotifyBusy.cs:29
Implemented by classes that notify when a child object has changed.
Defines additional elements for an ObservableCollection as required by CSLA .NET.
Specifies that the object can save itself.
Definition: ISavableT.cs:19
Task< object > SaveAsync()
Saves the object to the database.
Define the common methods used by the UI to interact with n-level undo.
Definition: ISupportUndo.cs:25
Defines the common properties required objects that track their own status.
Definition: ITrackStatus.cs:17
Defines a CSLA .NET viewmodel object.
AuthorizationActions
Authorization actions.
@ PropertyChanged
Called from PropertyHasChanged event on BO but not including cascade calls by AffectedProperties