MVC, MVVM, and MVP split UI from domain so you can test and change one without the other. They are presentation patterns, not system architecture. If you need service boundaries, see Architectural Patterns.
MVC — Model–View–Controller
The model is state and rules. The view renders. The controller accepts input and updates the model (and often chooses the next view). In original Smalltalk MVC the view observes the model. In server MVC (ASP.NET, Rails, Laravel) a request hits a controller, which loads a model and returns a view; there is no long-lived observer. Do not mix those two stories.
- Use request/response UIs and simple pages.
- Pain when the controller becomes a dumping ground (HTTP, SQL, and HTML in one method).
MVVM — Model–View–ViewModel
The ViewModel is an adapter the view can bind to: properties, commands, validation state. The view should not call repositories. Two-way binding is common on desktop (WPF) and in some SPA stacks; it is a technique, not the definition. Test the ViewModel without the UI toolkit.
The code-behind calls the repository. The view-model takes the repository instead, and a test calls it without the toolkit.
void OnLoad()
{
Name = repo.Find(id).Name;
}
sealed class CustomerVm
{
public string Name { get; }
public CustomerVm(ICustomers customers, Guid id) => Name = customers.Get(id).Name;
}
- Use rich client UI, or SPA screens with a lot of local state.
- Pain when the ViewModel is a second copy of the database row plus
INotifyPropertyChangednoise, or when you name it MVVM and still put SQL in code-behind.
There is no “MVVP.” That was a typo for MVVM.
MVP — Model–View–Presenter
The presenter talks to a passive view through an interface the presenter owns (ILoginView). The view forwards clicks; the presenter sets fields. Easier to unit-test than MVC with a concrete view. The presenter tends to be chatty and coupled 1:1 with a screen — that is expected.
How to choose
- HTTP page/API: MVC (or endpoint-centric Minimal APIs — still not MVVM).
- WPF / MAUI / rich SPA form: MVVM.
- Legacy WinForms or Android with awkward binding: MVP is still a clean seam.
Example
The action loads a model and returns a view.
public IActionResult Show(Guid id)
{
var model = customers.GetSummary(id);
return View(model);
}
What breaks it
The action runs SQL and builds the HTML. The next change to the markup and the next change to the query land in the same method.
public IActionResult ShowBroken(Guid id)
{
using var cmd = db.CreateCommand();
cmd.CommandText = "SELECT name FROM customer WHERE id = $1";
var name = (string)cmd.ExecuteScalar()!;
return Content("<h1>" + name + "</h1>");
}
Pitfalls
- Calling every frontend “MVC” because there is a folder named
Controllers. - Putting domain rules only in the ViewModel — then the API and a batch job disagree.
- One-way vs two-way binding as a religion; pick what the platform does well.