Model View Controller (MVC)
MVC pattern types:
- Classical MVC
- MVC Model 2 (also known as
JSP Model 2orWeb MVCorASP.NET Core MVC)
The MVC classical pattern was created in the late 1970s to structure user interactions with complex datasets.
Later, it was adopted for the Web and became known as the MVC Model 2 pattern.
In .net world the ASP.NET Core MVC framework is built on top of the MVC Model 2 pattern.
Classical MVC pattern
The Model-View-Controller (MVC) separates an application into three interconnected components - classes Views, Models,Controllers.
Model
In the classical MVC pattern, the Model is a class that represents the application or business logic. It can be as simple as an integer (a model of a counter) or as complex as an object with a subset of classes, representing, for example, a text editor.
Model is not allowed to know, which device it controlled.
Model is not allowed to know, where it gets input from(that's the job of the Controller).
Model can be associated with multiple views and controllers.
View
The view class deals with everything graphical. It requests data from the Model and outputs it to the output device. It displays messages, performs graphical transformations, and handles communication between sub-views.
Viewis an observer to theModel.
In theclassical MVCpattern, theViewis an observer of theModelclass.
Any changes made by theControllerto theModelautomatically trigger aViewupdate.Viewcan be accosiated with multiple controllers, but the only one model.Viewcan both contain and manage interactions betweensub-views.
Controller
The Controller gathers input from the input sensors — keyboard, mouse, whatever — and converts it into a method called against a Model.
ControllercoordinatesViews,Models,Input sensors.
The controller's job is to coordinate theModelsandViewswith theinput sensorsand handle scheduling tasks.Controllercan be accosiated with multiple views, but the only one model.Controllercan both contain and manage interactions betweensub-controller.
The typical interaction cycle in the Model-View-Controller

The typical interaction cycle in the Model-View-Controller (MVC) pattern begins with user input to the device sensors.
Device sensors trigger the Controller, which prepares input and calls methods from the Model.
The Model processes the data passed from the Controller, raising events that its state has changed.
It notifies its dependent Views and Controllers of the change via event messages, sometimes passing data within those messages.
Views then query the Model for its updated state and refresh their output displays as needed.
Controllers may adapt their behavior based on the Model's new state
Code Example (WPF classical MVC)
- Remark: In modern GUI frameworks like
WPForAngular, most of the functionality for interacting withdevice sensors(keyboard, mouse) anddevice output(display) is united within asingle component class. To interact withdevice sensorsor present aviewto the user, it is enough to inherit from such a component class.

Input sensors & output device
The MainWindow class inherits from the Window component class and provides functionality to interact with device sensors or present a view to the user.
MainWindow.xaml.cs
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="220" Width="348">
<Grid Margin="0,40,0,0" HorizontalAlignment="Left" Width="336" Height="153" VerticalAlignment="Top">
<TextBox Name="FirstNameTextBox" HorizontalAlignment="Left" Margin="109,29,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<Button Name="SaveButton" Content="Save" HorizontalAlignment="Left" Margin="251,47,0,0" VerticalAlignment="Top" RenderTransformOrigin="2.07,-0.466" Height="28" Width="60" />
<TextBox Name="SecondNameTextBox" HorizontalAlignment="Left" Margin="109,71,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<Label Name="ChangesLable" Content="Label" HorizontalAlignment="Left" Margin="10,110,0,0" VerticalAlignment="Top" Height="43" Width="301"/>
<Label Content="FirstName:" HorizontalAlignment="Left" Margin="10,24,0,0" VerticalAlignment="Top" Height="28" Width="72"/>
<Label Content="SecondName:" HorizontalAlignment="Left" Margin="10,66,0,0" VerticalAlignment="Top" Height="28" Width="88"/>
</Grid>
</Window>
namespace WpfApp1
{
using PresentationLayer;
using System.Windows;
public partial class MainWindow : Window, IDisposable
{
private readonly UserController userController;
public MainWindow()
{
InitializeComponent();
this.userController = new UserController(this);
}
public void Dispose()
{
this.userController.Dispose();
}
}
}
Model
In the particular example the Model is implemented using the Active Record pattern. It doesn't know anything about the Controller or a View. It contains application and business logic.
The Model class contains events that are raised when the Model is changed.
Views and controllers can subscribe to these events and update themselves or behave accordingly.
namespace DomainLayer
{
using InfrastructureLayer;
using System.ComponentModel.DataAnnotations.Schema;
public class UserModel
{
public event Action? ModelChanged;
public event Action? ModelLoaded;
public event Action? BusinessErrorMessageChanged;
public async Task Create(string firstName, string secondName)
{
this.Id = Guid.NewGuid();
this.SetName(firstName, secondName);
using AppContext appContext = new();
appContext.Users.Add(this);
await appContext.SaveChangesAsync().ConfigureAwait(false);
this.ModelChanged?.Invoke();
}
public async Task LoadLastUser()
{
using AppContext appContext = new();
var user = await appContext.Users.OrderBy(user => user.Id).LastOrDefaultAsync().ConfigureAwait(false);
if (user != null)
{
this.Id = user.Id;
this.FirstName = user.FirstName;
this.SecondName = user.SecondName;
this.ModelLoaded?.Invoke();
}
}
public Guid Id { get; private set; }
public string? FirstName { get; private set; }
public string? SecondName { get; private set; }
[NotMapped]
public string? BusinessErrorMessage { get; private set; }
public void SetBusinessErrorMessage(string message)
{
this.BusinessErrorMessage = message;
this.BusinessErrorMessageChanged?.Invoke();
}
public async Task ChangeName(string firstName, string secondName)
{
this.SetName(firstName, secondName);
using AppContext appContext = new();
appContext.Users.Update(this);
await appContext.SaveChangesAsync().ConfigureAwait(false);
}
private void SetName(string firstName, string secondName)
{
if (string.IsNullOrWhiteSpace(firstName))
{
throw new BusinessException("First Name is required");
}
if (string.IsNullOrWhiteSpace(secondName))
{
throw new BusinessException("Second Name is required");
}
this.FirstName = firstName;
this.SecondName = secondName;
}
}
public class BusinessException : Exception
{
public BusinessException(string message) : base(message) { }
}
}
DB
namespace InfrastructureLayer
{
public class AppContext : DbContext
{
public DbSet<UserModel> Users { get; set; }
public bool DatabaseEnsureCreated()
{
return this.Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var keepAliveConnection = new SqliteConnection("DataSource=test.sqlite;");
keepAliveConnection.Open();
optionsBuilder.UseSqlite(keepAliveConnection);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserModel>().Property(user => user.Id).HasValueGenerator(typeof(SequentialGuidValueGenerator));
}
}
}
using System.Windows;
namespace WpfApp1;
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
using InfrastructureLayer.AppContext appContext = new();
appContext.DatabaseEnsureCreated();
}
}
Controller
The Controller class keeps a reference to the View, to the Model and to the MainWindow classes.
Once the user enters data in the window, it is picked up by the Controller, then the Controller prepares data which is passed to the Model. Then, the Model executes application and business logic on it.
namespace PresentationLayer
{
public class UserController
{
private readonly UserModel userModel;
private readonly UserView userView;
private readonly MainWindow window;
public UserController(MainWindow window)
{
this.window = window;
this.userModel = new UserModel();
this.userView = new UserView(window, userModel);
this.window.SaveButton.Click += SaveButton_Click;
this.window.SourceInitialized += OnSourceInitialized;
}
private async void OnSourceInitialized(object? sender, EventArgs e)
{
await this.LoadLastUser().ConfigureAwait(false);
}
private async void SaveButton_Click(object sender, RoutedEventArgs e)
{
var firstName = window.FirstNameTextBox.Text;
var secondName = window.SecondNameTextBox.Text;
try
{
await userModel.Create(firstName, secondName).ConfigureAwait(false);
}
catch (BusinessException ex)
{
userModel.SetBusinessErrorMessage(ex.Message);
}
}
private async Task LoadLastUser()
{
try
{
await userModel.LoadLastUser().ConfigureAwait(false);
}
catch (BusinessException ex)
{
userModel.SetBusinessErrorMessage(ex.Message);
}
}
public void Dispose()
{
this.window.SourceInitialized -= OnSourceInitialized;
this.window.SaveButton.Click -= SaveButton_Click;
this.userView.Dispose();
}
}
}
View
The View is subscribed to the Model's events. Once the Model raises an event about changes, the View updates GUI accordingly.
namespace PresentationLayer
{
public class UserView : IDisposable
{
private readonly MainWindow window;
private readonly UserModel userModel;
public UserView(MainWindow window, UserModel model)
{
this.window = window;
this.userModel = model;
//
// View subscribes to the Model changes
//
this.userModel.ModelChanged += UserModel_ModelChanged;
this.userModel.ModelLoaded += UserModel_ModelLoaded;
this.userModel.BusinessErrorMessageChanged += UserModel_BusinessErrorMessageChanged;
}
private void UserModel_BusinessErrorMessageChanged()
{
window.Dispatcher.Invoke(() =>
{
window.ChangesLable.Content = this.userModel.BusinessErrorMessage;
});
}
private void UserModel_ModelLoaded()
{
window.Dispatcher.Invoke(() =>
{
window.FirstNameTextBox.Text = this.userModel.FirstName;
window.SecondNameTextBox.Text = this.userModel.SecondName;
window.ChangesLable.Content = $"The last user created has loaded";
});
}
/// <summary>
/// When the model changes, the view updates the UI
/// </summary>
private void UserModel_ModelChanged()
{
window.Dispatcher.Invoke(() =>
{
window.FirstNameTextBox.Text = "";
window.SecondNameTextBox.Text = "";
window.ChangesLable.Content = $"Changes saved. User: {this.userModel.FirstName} {this.userModel.SecondName}";
});
}
public void Dispose()
{
this.userModel.ModelLoaded -= UserModel_ModelLoaded;
this.userModel.ModelChanged -= UserModel_ModelChanged;
this.userModel.BusinessErrorMessageChanged -= UserModel_BusinessErrorMessageChanged;
}
}
}
MVC Model 2 pattern
MVC Model 2 represents an adopted version of the MVC pattern for web applications.
Traditionally, a web page has to send a request to the server to receive new data; that is, the page requests data from the server.
Without additional technologies implemented above WebSockets, long polling, ajax, server-sent events,WebTransport web applications cannot reactively update the UI and in several different places.
This introduces certain simplifications and restrictions to the MVC Model 2 pattern compared to the classical MVC.

Controller
Controller receives data from the HTTP request, pass it to the Model. When the logic within the Model is executed and the result is received by the Controller, the Controller selects a View to render the Model.
Controllercan be accosiated with multiple views, and multiple models.
If theControllerinteracts with multipleModelsor complexModel, an additionalViewModelcan be created for the specificViewto simplify mapping of data.
work with the model, and ultimately select a view to render.
Model
The Model has the same responsibilities as in the classical MVC pattern, except there is no need to create events and notify the Controller about changes done in the Model, as the Controller receives a result from the Model within the same HTTP request-response.
View
Views are responsible for presenting content through the user interface. In ASP.NET Core, the Razor view engine is used to convert .NET and HTML markup code into an HTML web page.
Code Example
View
@{
ViewData["Title"] = "User";
}
@model DomainLayer.UserModel
<h2>User</h2>
Id: @Model.Id <br />
Name: @Model.Name <br />
Controller + Model
namespace WebApplication1.Controllers
{
using DomainLayer;
using Microsoft.AspNetCore.Mvc;
public class UserController : Controller
{
[HttpGet]
public IActionResult Index(int userId)
{
var model = UserModel.LoadUser(userId);
return View(model);
}
}
}
namespace DomainLayer
{
public class UserModel
{
public int Id { get; set; }
public string Name { get; set; }
public static UserModel LoadUser(int userId)
{
// Load from the DB ..
return new UserModel { Id = userId, Name = "John Doe" };
}
}
}