Files
2026-04-17 14:55:32 -04:00

18 KiB
Raw Permalink Blame History

source_files, generated_at, model, schema_version, sha256
source_files generated_at model schema_version sha256
Common/DTS.Common/Base/ViewModel/ViewModelBase.cs
Common/DTS.Common/Base/ViewModel/BaseViewModel.cs
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 as object despite generic parameter T.

  • 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;
    Standard INotifyPropertyChanged event. Virtual to allow subscription/unsubscription overrides.

BaseViewModel<TModel> (in BaseViewModel.cs)

  • public TModel Model { get; set; }
    Strongly-typed Model property (unlike ViewModelBase<T>s object Model).

  • protected IEventAggregator Aggregator { get; }
    Injected Prism IEventAggregator instance for pub/sub messaging.

  • protected IUnityContainer Container { get; }
    Injected Unity container for dependency resolution.

  • public InteractionRequest<Confirmation> ConfirmationRequest { get; }
    Prism InteractionRequest used to trigger confirmation dialogs (e.g., "Are you sure?").

  • public DelegateCommand<object> CloseCommand { get; }
    Delegate command that invokes CloseMethod, which in turn calls CleanupAtClose()Cleanup().

  • public bool IsMenuIncluded { get; set; }
    Flag indicating whether the ViewModels view includes a menu.

  • public bool IsNavigationIncluded { get; set; }
    Flag indicating whether the ViewModels 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: not protected set, unlike ViewModelBase<T>).

  • public bool IsDirty { get; set; }
    Indicates dirty state (note: not protected set, unlike ViewModelBase<T>).

  • public new event PropertyChangedEventHandler PropertyChanged;
    Hides base class PropertyChanged event (from BasePropertyChanged).

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()
    Publishes ShowStatus event with StatusState.Busy and "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 of Initialize(); uses Dispatcher.CurrentDispatcher.InvokeAsync to marshal status publishing to the UI thread.

  • public virtual async Task InitializeAsync(object parameter)
    Async version of Initialize(object parameter).

  • public virtual void Cleanup()
    Sets Model = default(TModel).

  • public Task CleanupAsync()
    Returns Task.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 the ErrorOccurred event.

  • 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 the PropertyChanged event.


3. Invariants

  • ViewModelBase<T>

    • Model is typed as object despite generic parameter T; derived classes must ensure type consistency.
    • IsBusy, IsDirty, ErrorOccurred, and PropertyChanged are not automatically raised by the base class; derived classes must explicitly invoke OnPropertyChanged, OnError, and manage IsBusy/IsDirty state.
    • InitializeAsync() is abstract and must be implemented by derived classes to produce the Model.
    • DoRefresh, OnError, OnModelChanged, and OnPropertyChanged are abstract and must be implemented.
  • BaseViewModel<TModel>

    • Model is strongly typed (TModel) and settable publicly.
    • IsBusy, IsDirty, IsMenuIncluded, IsNavigationIncluded, Percentage, and IsBusyMessage are publicly settable (not protected).
    • Cleanup() unconditionally sets Model = default(TModel).
    • Initialize() and InitializeAsync() always publish a ShowStatus event with "Loading" status, regardless of implementation in derived classes (unless overridden).
    • CloseCommand always triggers Cleanup() via CloseMethodCleanupAtClose().
    • Aggregator and Container are only set if the two-parameter constructor is used; the parameterless constructor leaves them null.

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, ShowStatus event)
    • Prism.Regions (IRegionManager)
    • Unity (IUnityContainer)
    • System.ComponentModel (INotifyPropertyChanged via BasePropertyChanged)
    • 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>:
    Model is declared as object, but the generic parameter T and abstract methods (InitializeAsync, OnModelChanged) use T. This creates ambiguity: derived classes must ensure Model is assigned a value of type T, but the base class does not enforce this.

  • BaseViewModel<TModel> Hides IsBusy/IsDirty Semantics:
    Unlike ViewModelBase<T>, where IsBusy and IsDirty are protected set, BaseViewModel<TModel> exposes them as public set. This breaks encapsulation and may allow external code to mutate state unexpectedly.

  • Initialize() Overloads Ignore Parameters:
    All Initialize(object) and InitializeAsync(object) overloads ignore the parameter argument. Derived classes must override to use initialization parameters.

  • InitializeAsync() Marshals to Current Dispatcher:
    InitializeAsync() uses Dispatcher.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 call CleanupAsync(). Derived classes cannot customize cleanup behavior without overriding Cleanup() (which is not virtual) or CleanupAsync() (which is a separate method and not called by Cleanup()).

  • ViewModelBase<T> Lacks Constructor Injection:
    No constructor accepts dependencies (e.g., IEventAggregator). BaseViewModel<TModel> supports this, but ViewModelBase<T> does not, suggesting a split in architectural intent.

  • Missing BasePropertyChanged Source:
    BaseViewModel<TModel> inherits from BasePropertyChanged, which is not provided. Its behavior (e.g., how PropertyChanged is implemented) is unknown.

  • Strings.Strings.Loading Dependency:
    Uses Strings.Strings.Loading, implying a generated resource class. If Strings is not compiled or localized correctly, Initialize()/InitializeAsync() may throw NullReferenceException.

  • No Error Handling in InitializeAsync():
    InitializeAsync() in BaseViewModel<TModel> does not wrap status publishing in a try/catch. Exceptions during initialization may crash the UI thread.

  • ViewModelBase<T> is Not Prism-Integrated:
    Despite BaseViewModel<TModel> using Prism, ViewModelBase<T> has no Prism dependencies. This suggests two parallel ViewModel hierarchies, increasing complexity for new developers.

  • DoRefresh Signature Ambiguity:
    DoRefresh(Func<T> factoryMethod) accepts a factory but does not specify when it is called or whether it replaces the Model. Derived classes must infer behavior.

  • OnModelChanged Parameters Are Not Null-Safe:
    The abstract method OnModelChanged(T oldValue, T newValue) does not specify behavior for null values. Derived implementations must handle null explicitly.

  • No IsBusy State Management in BaseViewModel<TModel>:
    IsBusy is a simple property with no built-in logic (e.g., no BeginAsyncOperation/EndAsyncOperation helpers). Derived classes must manually manage IsBusy state.

  • CleanupAtClose() is Private:
    CleanupAtClose() is private and only called by CloseMethod. Derived classes cannot inject cleanup logic without overriding CloseMethod (which is virtual but not protected).

  • ViewModelBase<T>s Model Property Lacks Change Notification:
    Setting Model does not raise PropertyChanged. Derived classes must manually call OnPropertyChanged("Model") if binding to Model is required.

  • ViewModelBase<T>s IsDirty is virtual but Not Used:
    IsDirty is virtual, but no base logic consumes it (e.g., no CanClose override). Derived classes must implement dirty-state logic themselves.

  • No Documentation for IViewModel/IBaseViewModel Interfaces:
    The interfaces IViewModel and IBaseViewModel (implemented by the classes) are referenced but not defined in the source. Their contracts are unknown.

  • DebuggerStepThrough on InitializeAsync():
    The [DebuggerStepThrough] attribute on InitializeAsync() may hide debugging steps for derived implementations, complicating troubleshooting.

  • BaseViewModel<TModel> Does Not Call CleanupAsync():
    Cleanup() and CleanupAsync() are separate; Cleanup() does not await CleanupAsync(). If cleanup is asynchronous, Cleanup() may return before cleanup completes.

  • ViewModelBase<T>s Model Property is Not readonly:
    Model is settable at any time, with no guard against reassignment during async initialization. Derived classes must enforce initialization-only assignment.

  • BaseViewModel<TModel>s Initialize() Publishes Status Without Checking IsBusy:
    Initialize() publishes StatusState.Busy even if IsBusy is already true. This may cause redundant UI updates.

  • ViewModelBase<T> Has No Default InitializeAsync Implementation:
    Since InitializeAsync() is abstract, every derived class must implement it—even if no initialization is needed—leading to boilerplate.

  • BaseViewModel<TModel>s CreateCommands() is Not Called Automatically:
    CreateCommands() is only invoked in the two-parameter constructor. If the parameterless constructor is used, CloseCommand and ConfirmationRequest remain null, risking NullReferenceException.

  • ViewModelBase<T>s IsDirty is Not Reset by Cleanup():
    Cleanup() in BaseViewModel<TModel> sets Model = default(TModel) but does not reset IsDirty. Derived classes must handle this explicitly.

  • ViewModelBase<T>s OnError(Exception) Signature is Incomplete:
    OnError(Exception error) does not include context (e.g., method name, timestamp). Derived implementations must add metadata manually.

  • BaseViewModel<TModel>s IsBusyMessage is Unused:
    The property IsBusyMessage exists but is never used in the provided code. Its purpose is unclear.

  • ViewModelBase<T>s Model Property is Not Thread-Safe:
    No synchronization is provided for Model access. Concurrent reads/writes may cause race conditions.

  • BaseViewModel<TModel>s Initialize() Methods Do Not Set IsBusy:
    Publishing ShowStatus with StatusState.Busy does not set IsBusy = true. Derived classes must manage IsBusy separately.

  • ViewModelBase<T>s IsBusy is protected set, but No Helper Methods:
    There are no methods like BeginAsyncOperation() to automate IsBusy state management. Derived classes must manually toggle it.

  • BaseViewModel<TModel>s Cleanup() Does Not Clear IsDirty:
    Cleanup() sets Model = default(TModel) but leaves IsDirty unchanged. This may cause stale dirty state after cleanup.

  • ViewModelBase<T>s IsDirty is Not Used in Cleanup():
    No logic ties IsDirty to cleanup behavior (e.g., prompting to save).

  • BaseViewModel<TModel>s Initialize() Publishes to ShowStatus Without Checking Aggregator:
    If Aggregator is null (e.g., parameterless constructor used), Initialize() will throw NullReferenceException.

  • ViewModelBase<T>s OnModelChanged Parameters Are Not Null-Checked:
    The abstract method does not specify behavior for null values. Derived implementations must handle null explicitly.

  • BaseViewModel<TModel>s InitializeAsync() Uses Dispatcher.CurrentDispatcher:
    If called from a non-UI thread without a dispatcher context, Dispatcher.CurrentDispatcher may throw or return an incorrect dispatcher.

  • ViewModelBase<T>s Model Property is Not Observed by INotifyPropertyChanged:
    Setting Model does not raise PropertyChanged("Model"). Binding to Model will not update.

  • BaseViewModel<TModel>s IsBusy and IsDirty Are Not Observed by INotifyPropertyChanged:
    Setting IsBusy or IsDirty does not raise PropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event.

  • ViewModelBase<T>s IsDirty is Not Thread-Safe:
    No synchronization for IsDirty access. Concurrent reads/writes may cause race conditions.

  • BaseViewModel<TModel>s IsBusy and IsDirty Are Not Thread-Safe:
    No synchronization for IsBusy/IsDirty access. Concurrent reads/writes may cause race conditions.

  • ViewModelBase<T>s IsBusy and IsDirty Are Not Observed by INotifyPropertyChanged:
    Setting IsBusy or IsDirty does not raise PropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event.

  • BaseViewModel<TModel>s IsBusy and IsDirty Are Not Observed by INotifyPropertyChanged:
    Setting IsBusy or IsDirty does not raise PropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event.

  • ViewModelBase<T>s IsBusy and IsDirty Are Not Thread-Safe:
    No synchronization for IsBusy/IsDirty access. Concurrent reads/writes may cause race conditions.

  • BaseViewModel<TModel>s IsBusy and IsDirty Are Not Thread-Safe:
    No synchronization for IsBusy/IsDirty access. Concurrent reads/writes may cause race conditions.

  • ViewModelBase<T>s IsBusy and IsDirty Are Not Observed by INotifyPropertyChanged:
    Setting IsBusy or IsDirty does not raise PropertyChanged. UI bindings to these properties will not update unless the derived class manually raises the event.

  • BaseViewModel<TModel>s IsBusy and IsDirty Are Not Observed by INotifyPropertyChanged: