In an MVVM architecture, models should be kept free of UI-related logic. They represent pure data that is retrieved from memory via a service. I see a challenge when the user interface needs to respond to changes in the internal state of a model, but the model itself should not implement notification mechanisms such as INotifyPropertyChanged or other UI framework-specific behaviors.
Example
Consider a mail application. A folder contains many messages. The FolderViewModel exposes an observable collection of these messages to the view:
public class FolderViewModel(IService service) : ObservableObject
{
public ObservableCollection<Message> Messages { get; } = service.LoadMessagesForFolder(/*...*/);
}
Each Message is a plain domain model:
public class Message
{
public string Subject { get; set; }
public bool IsRead { get; set; }
public bool IsFlagged { get; set; }
}
From the UI, I can add or remove messages easily via commands on the FolderViewModel.
However, when interacting with a message, the following two problems arise:
- The user interface is not updated when a message is changed: Since
Messageis a POCO, it does not implementINotifyPropertyChanged; changes such as marking as read or toggling a flag are not propagated to the user interface. - Commands cannot be bound clean and easily to the parent of the list view item (e.g., in WinUI): The command for a Delete button on each message item must be bound to itself.
Attempted solution
My workaround has been to create an item-viewmodel that wraps the model, exposes observable properties for the UI, and forwards commands:
public partial class MessageItemViewModel(Message model) : ObservableObject
{
private readonly Message _model = model;
[ObservableProperty]
public partial bool IsRead { get; set; } = model.IsRead;
[ObservableProperty]
public partial bool IsFlagged { get; set; } = model.IsFlagged;
// commands for setting flag, etc.
}
Although this works, it raises several serious concerns:
- Every relevant model property now exists twice (once in the model, once in the wrapper) which leads to code duplication.
- Large collections of wrapped items increase memory usage.
- Wrappers often require services, but must also be created manually because they have their model as a parameter. This does not allow services to be injected via the DI container.
- When item view models subscribe to external events or domain changes, there is a potential risk of leaks.
The question
What is the recommended MVVM approach to reflect changes in model state in the user interface? Is there a clean pattern commonly used in MVVM applications to handle this scenario?