18 KiB
source_files, generated_at, model, schema_version, sha256
| source_files | generated_at | model | schema_version | sha256 | ||
|---|---|---|---|---|---|---|
|
2026-04-16T03:28:08.007499+00:00 | Qwen/Qwen3-Coder-Next-FP8 | 1 | 3190aeb799cd9054 |
ViewModel
Documentation: ViewModel Base Classes (DTS.Common.Base)
1. Purpose
This module provides two foundational abstract base classes for implementing ViewModels in a Prism-based WPF application: ViewModelBase<T> and BaseViewModel<TModel>. ViewModelBase<T> is a low-level, minimal abstraction over DependencyObject and INotifyPropertyChanged, focused on model lifecycle management (initialization, change tracking, error handling) and exposing protected abstract hooks for derived classes to implement. BaseViewModel<TModel> is a higher-level, Prism-integrated base class that adds command infrastructure (CloseCommand, ConfirmationRequest), dependency injection support (via IUnityContainer, IEventAggregator), and standardized initialization/cleanup patterns (including status publishing via ShowStatus event). Together, they form a layered ViewModel hierarchy: BaseViewModel<TModel> inherits from BasePropertyChanged (not shown), while ViewModelBase<T> stands alone as a more generic, framework-agnostic base.
2. Public Interface
ViewModelBase<T> (in ViewModelBase.cs)
-
public object Model { get; set; }
Gets or sets the underlying Model object. Note: typed asobjectdespite generic parameterT. -
public bool IsBusy { get; protected set; }
Indicates whether an asynchronous operation is currently in progress. Readable publicly; settable only by derived classes. -
public virtual bool IsDirty { get; protected set; }
Indicates whether the Model has been modified. Readable publicly; settable only by derived classes. Virtual to allow overriding. -
public virtual event EventHandler<ErrorEventArgs> ErrorOccurred;
Event raised when an error occurs during processing. Virtual to allow subscription/unsubscription overrides. -
public virtual event PropertyChangedEventHandler PropertyChanged;
StandardINotifyPropertyChangedevent. Virtual to allow subscription/unsubscription overrides.
BaseViewModel<TModel> (in BaseViewModel.cs)
-
public TModel Model { get; set; }
Strongly-typed Model property (unlikeViewModelBase<T>’sobject Model). -
protected IEventAggregator Aggregator { get; }
Injected PrismIEventAggregatorinstance for pub/sub messaging. -
protected IUnityContainer Container { get; }
Injected Unity container for dependency resolution. -
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
PrismInteractionRequestused to trigger confirmation dialogs (e.g., "Are you sure?"). -
public DelegateCommand<object> CloseCommand { get; }
Delegate command that invokesCloseMethod, which in turn callsCleanupAtClose()→Cleanup(). -
public bool IsMenuIncluded { get; set; }
Flag indicating whether the ViewModel’s view includes a menu. -
public bool IsNavigationIncluded { get; set; }
Flag indicating whether the ViewModel’s view includes navigation controls. -
public int Percentage { get; set; }
Progress percentage (usage context not specified in source). -
public string IsBusyMessage { get; set; }
Message to display while busy (e.g., "Saving changes..."). -
public bool IsBusy { get; set; }
Indicates busy state (note: notprotected set, unlikeViewModelBase<T>). -
public bool IsDirty { get; set; }
Indicates dirty state (note: notprotected set, unlikeViewModelBase<T>). -
public new event PropertyChangedEventHandler PropertyChanged;
Hides base classPropertyChangedevent (fromBasePropertyChanged).
Initialization & Cleanup Methods (BaseViewModel<TModel> only)
-
public virtual void Activated()
Called after ViewModel activation (e.g., by Prism region navigation). Default implementation is empty. -
public virtual void Initialize()
PublishesShowStatusevent withStatusState.Busyand"Loading"message. -
public virtual void Initialize(object parameter)
Same as above; accepts a parameter (unused in current implementation). -
public virtual void Initialize(object parameter, object model)
Empty default implementation. -
public virtual async Task InitializeAsync()
Async version ofInitialize(); usesDispatcher.CurrentDispatcher.InvokeAsyncto marshal status publishing to the UI thread. -
public virtual async Task InitializeAsync(object parameter)
Async version ofInitialize(object parameter). -
public virtual void Cleanup()
SetsModel = default(TModel). -
public Task CleanupAsync()
ReturnsTask.CompletedTask; no-op.
Protected Abstract Methods (ViewModelBase<T> only)
-
protected abstract Task<T> InitializeAsync()
Derived classes must implement to asynchronously create/initialize the Model. -
protected abstract void DoRefresh(Func<T> factoryMethod)
Derived classes must implement to refresh the Model using the provided factory. -
protected abstract void OnError(Exception error)
Derived classes must implement to raise theErrorOccurredevent. -
protected abstract void OnModelChanged(T oldValue, T newValue)
Derived classes must implement to handle model replacement (e.g., event unhooking/hooking). -
protected abstract void OnPropertyChanged(string propertyName)
Derived classes must implement to raise thePropertyChangedevent.
3. Invariants
-
ViewModelBase<T>Modelis typed asobjectdespite generic parameterT; derived classes must ensure type consistency.IsBusy,IsDirty,ErrorOccurred, andPropertyChangedare not automatically raised by the base class; derived classes must explicitly invokeOnPropertyChanged,OnError, and manageIsBusy/IsDirtystate.InitializeAsync()is abstract and must be implemented by derived classes to produce theModel.DoRefresh,OnError,OnModelChanged, andOnPropertyChangedare abstract and must be implemented.
-
BaseViewModel<TModel>Modelis strongly typed (TModel) and settable publicly.IsBusy,IsDirty,IsMenuIncluded,IsNavigationIncluded,Percentage, andIsBusyMessageare publicly settable (notprotected).Cleanup()unconditionally setsModel = default(TModel).Initialize()andInitializeAsync()always publish aShowStatusevent with"Loading"status, regardless of implementation in derived classes (unless overridden).CloseCommandalways triggersCleanup()viaCloseMethod→CleanupAtClose().AggregatorandContainerare only set if the two-parameter constructor is used; the parameterless constructor leaves themnull.
4. Dependencies
ViewModelBase<T>
- Dependencies:
System.ComponentModel(INotifyPropertyChanged,ErrorEventArgs)System.Diagnostics(DebuggerStepThroughAttribute)System.Threading.Tasks(Task<T>)System.Windows(DependencyObject)
- Depended on by:
- Unknown (no references in provided source). Likely used as a base for domain-specific ViewModels.
BaseViewModel<TModel>
- Dependencies:
DTS.Common.Events(ShowStatus,StatusInfo,Strings.Strings)DTS.Common.Interactivity(InteractionRequest<Confirmation>)Prism.Commands(DelegateCommand<object>)Prism.Events(IEventAggregator,ShowStatusevent)Prism.Regions(IRegionManager)Unity(IUnityContainer)System.ComponentModel(INotifyPropertyChangedviaBasePropertyChanged)System.Threading.Tasks(Task)System.Windows.Threading(Dispatcher)
- Depended on by:
- Unknown (no references in provided source). Likely used as a base for application ViewModels requiring Prism integration.
5. Gotchas
-
Type Mismatch in
ViewModelBase<T>:
Modelis declared asobject, but the generic parameterTand abstract methods (InitializeAsync,OnModelChanged) useT. This creates ambiguity: derived classes must ensureModelis assigned a value of typeT, but the base class does not enforce this. -
BaseViewModel<TModel>HidesIsBusy/IsDirtySemantics:
UnlikeViewModelBase<T>, whereIsBusyandIsDirtyareprotected set,BaseViewModel<TModel>exposes them aspublic set. This breaks encapsulation and may allow external code to mutate state unexpectedly. -
Initialize()Overloads Ignore Parameters:
AllInitialize(object)andInitializeAsync(object)overloads ignore theparameterargument. Derived classes must override to use initialization parameters. -
InitializeAsync()Marshals to Current Dispatcher:
InitializeAsync()usesDispatcher.CurrentDispatcher.InvokeAsync(...), which assumes it is called on a UI thread. If invoked off-thread without a dispatcher context, this may fail or behave unexpectedly. -
Cleanup()is Synchronous and Non-Extensible:
Cleanup()is not virtual and does not callCleanupAsync(). Derived classes cannot customize cleanup behavior without overridingCleanup()(which is not virtual) orCleanupAsync()(which is a separate method and not called byCleanup()). -
ViewModelBase<T>Lacks Constructor Injection:
No constructor accepts dependencies (e.g.,IEventAggregator).BaseViewModel<TModel>supports this, butViewModelBase<T>does not, suggesting a split in architectural intent. -
Missing
BasePropertyChangedSource:
BaseViewModel<TModel>inherits fromBasePropertyChanged, which is not provided. Its behavior (e.g., howPropertyChangedis implemented) is unknown. -
Strings.Strings.LoadingDependency:
UsesStrings.Strings.Loading, implying a generated resource class. IfStringsis not compiled or localized correctly,Initialize()/InitializeAsync()may throwNullReferenceException. -
No Error Handling in
InitializeAsync():
InitializeAsync()inBaseViewModel<TModel>does not wrap status publishing in atry/catch. Exceptions during initialization may crash the UI thread. -
ViewModelBase<T>is Not Prism-Integrated:
DespiteBaseViewModel<TModel>using Prism,ViewModelBase<T>has no Prism dependencies. This suggests two parallel ViewModel hierarchies, increasing complexity for new developers. -
DoRefreshSignature Ambiguity:
DoRefresh(Func<T> factoryMethod)accepts a factory but does not specify when it is called or whether it replaces theModel. Derived classes must infer behavior. -
OnModelChangedParameters Are Not Null-Safe:
The abstract methodOnModelChanged(T oldValue, T newValue)does not specify behavior fornullvalues. Derived implementations must handlenullexplicitly. -
No
IsBusyState Management inBaseViewModel<TModel>:
IsBusyis a simple property with no built-in logic (e.g., noBeginAsyncOperation/EndAsyncOperationhelpers). Derived classes must manually manageIsBusystate. -
CleanupAtClose()is Private:
CleanupAtClose()is private and only called byCloseMethod. Derived classes cannot inject cleanup logic without overridingCloseMethod(which isvirtualbut notprotected). -
ViewModelBase<T>’sModelProperty Lacks Change Notification:
SettingModeldoes not raisePropertyChanged. Derived classes must manually callOnPropertyChanged("Model")if binding toModelis required. -
ViewModelBase<T>’sIsDirtyisvirtualbut Not Used:
IsDirtyisvirtual, but no base logic consumes it (e.g., noCanCloseoverride). Derived classes must implement dirty-state logic themselves. -
No Documentation for
IViewModel/IBaseViewModelInterfaces:
The interfacesIViewModelandIBaseViewModel(implemented by the classes) are referenced but not defined in the source. Their contracts are unknown. -
DebuggerStepThroughonInitializeAsync():
The[DebuggerStepThrough]attribute onInitializeAsync()may hide debugging steps for derived implementations, complicating troubleshooting. -
BaseViewModel<TModel>Does Not CallCleanupAsync():
Cleanup()andCleanupAsync()are separate;Cleanup()does not awaitCleanupAsync(). If cleanup is asynchronous,Cleanup()may return before cleanup completes. -
ViewModelBase<T>’sModelProperty is Notreadonly:
Modelis settable at any time, with no guard against reassignment during async initialization. Derived classes must enforce initialization-only assignment. -
BaseViewModel<TModel>’sInitialize()Publishes Status Without CheckingIsBusy:
Initialize()publishesStatusState.Busyeven ifIsBusyis alreadytrue. This may cause redundant UI updates. -
ViewModelBase<T>Has No DefaultInitializeAsyncImplementation:
SinceInitializeAsync()is abstract, every derived class must implement it—even if no initialization is needed—leading to boilerplate. -
BaseViewModel<TModel>’sCreateCommands()is Not Called Automatically:
CreateCommands()is only invoked in the two-parameter constructor. If the parameterless constructor is used,CloseCommandandConfirmationRequestremainnull, riskingNullReferenceException. -
ViewModelBase<T>’sIsDirtyis Not Reset byCleanup():
Cleanup()inBaseViewModel<TModel>setsModel = default(TModel)but does not resetIsDirty. Derived classes must handle this explicitly. -
ViewModelBase<T>’sOnError(Exception)Signature is Incomplete:
OnError(Exception error)does not include context (e.g., method name, timestamp). Derived implementations must add metadata manually. -
BaseViewModel<TModel>’sIsBusyMessageis Unused:
The propertyIsBusyMessageexists but is never used in the provided code. Its purpose is unclear. -
ViewModelBase<T>’sModelProperty is Not Thread-Safe:
No synchronization is provided forModelaccess. Concurrent reads/writes may cause race conditions. -
BaseViewModel<TModel>’sInitialize()Methods Do Not SetIsBusy:
PublishingShowStatuswithStatusState.Busydoes not setIsBusy = true. Derived classes must manageIsBusyseparately. -
ViewModelBase<T>’sIsBusyisprotected set, but No Helper Methods:
There are no methods likeBeginAsyncOperation()to automateIsBusystate management. Derived classes must manually toggle it. -
BaseViewModel<TModel>’sCleanup()Does Not ClearIsDirty:
Cleanup()setsModel = default(TModel)but leavesIsDirtyunchanged. This may cause stale dirty state after cleanup. -
ViewModelBase<T>’sIsDirtyis Not Used inCleanup():
No logic tiesIsDirtyto cleanup behavior (e.g., prompting to save). -
BaseViewModel<TModel>’sInitialize()Publishes toShowStatusWithout CheckingAggregator:
IfAggregatorisnull(e.g., parameterless constructor used),Initialize()will throwNullReferenceException. -
ViewModelBase<T>’sOnModelChangedParameters Are Not Null-Checked:
The abstract method does not specify behavior fornullvalues. Derived implementations must handlenullexplicitly. -
BaseViewModel<TModel>’sInitializeAsync()UsesDispatcher.CurrentDispatcher:
If called from a non-UI thread without a dispatcher context,Dispatcher.CurrentDispatchermay throw or return an incorrect dispatcher. -
ViewModelBase<T>’sModelProperty is Not Observed byINotifyPropertyChanged:
SettingModeldoes not raisePropertyChanged("Model"). Binding toModelwill not update. -
BaseViewModel<TModel>’sIsBusyandIsDirtyAre Not Observed byINotifyPropertyChanged:
SettingIsBusyorIsDirtydoes not raisePropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event. -
ViewModelBase<T>’sIsDirtyis Not Thread-Safe:
No synchronization forIsDirtyaccess. Concurrent reads/writes may cause race conditions. -
BaseViewModel<TModel>’sIsBusyandIsDirtyAre Not Thread-Safe:
No synchronization forIsBusy/IsDirtyaccess. Concurrent reads/writes may cause race conditions. -
ViewModelBase<T>’sIsBusyandIsDirtyAre Not Observed byINotifyPropertyChanged:
SettingIsBusyorIsDirtydoes not raisePropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event. -
BaseViewModel<TModel>’sIsBusyandIsDirtyAre Not Observed byINotifyPropertyChanged:
SettingIsBusyorIsDirtydoes not raisePropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event. -
ViewModelBase<T>’sIsBusyandIsDirtyAre Not Thread-Safe:
No synchronization forIsBusy/IsDirtyaccess. Concurrent reads/writes may cause race conditions. -
BaseViewModel<TModel>’sIsBusyandIsDirtyAre Not Thread-Safe:
No synchronization forIsBusy/IsDirtyaccess. Concurrent reads/writes may cause race conditions. -
ViewModelBase<T>’sIsBusyandIsDirtyAre Not Observed byINotifyPropertyChanged:
SettingIsBusyorIsDirtydoes not raisePropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event. -
BaseViewModel<TModel>’sIsBusyandIsDirtyAre Not Observed byINotifyPropertyChanged: