MVC, MVVM, MVP Compare by Examples using C#

MVC, MVVM, MVP are architecture patterns designed to reduce complexity of presentation layer. The advantage the this separation of concern includes:

1) Business and data layer more manageable and testable.
2) Developers are able to work in parallel on different components
3) Code reuse and share become easier

Some people comparing these design layer separations with making Sandwichs, which makes things easier.


MVC (Model-View-Controller)

MVC has been supported by many languages: MVC.net, Java, Ruby on Rails, etc

MVC Diagram

MVC Layers:

Model: Data and business logic

View: UI presented to the user

Controller: Get requests from View, pass the requests to model to get data, and pass the model data back to the view.

View Model: You also need to create View Models from Models to presenting more comoplicated data.

MVC is a one-way data-binding cycle:
View->Controller->Model->View.

With Visual Studio Scaffolding code generation in MVC.net, it's very easy to work on MVC patten. For MVVM and MVP, you need to build the pattern by yourself.

Let's take a look of Microsoft MVC framework Core with Entity Framework code example. This just display a name on UI.

Model: The model define data structure, and data validation

namespace Example.Models
{
       public class Member
       {
              [Key]
              public int Id { get; set; }

              [StringLength(30, ErrorMessage = "{0} must be between {2} to {1} characters.", MinimumLength = 5)]
              public string Name { get; set; }
       }
}

View: The view displays model data, passes user action back to another action of controller when a button is clicked.

@model Example.Models.Member

@{
    ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
    <h4>Member</h4>
    <hr />
    <dl class="dl-horizontal">
    <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>
    <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>
    </dl>
</div>
<div>
    <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a>|
        <a asp-action="Index" >Back to List</a>
</div>

Controller: The controller "Details" action get data from model or update the model, and pass model data back to the View

       
        // GET: Members/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var member = await _context.Member
                .FirstOrDefaultAsync(m => m.Id == id);
            if (member == null)
            {
                return NotFound();
            }

            return View(member);
        }

MVVM (Model-View-View-Model)

MVVM is created by Microsoft, used by WPF projects, also Xamarin mobile projects

MVVM Diagram

MVVM Layers:

Model: Data and business logic

View: UI Presentation

View Model: Abstraction of the view that expose interface of properties and events.

The View interact with View Model instead of interact with View, so UI and business logic are seperated. View and View Model interact through data binding instead of code, make View Model class testable though unit testing.

MVVM is mostly depending on databinding in Xamal UI code, so it minimized Xamal code behind logic, UI and View Model becomes lose coupled. View Model implementing INofiyPropertyChanged to get data change from UI, and ICommand to get event from UI.

This is a simple example to search and edit people's names. Enter Id, click "Get Member" button to search. Modify the name and click "Save Member" button to save.

Model: The model implement INotifyPropertyChanged interface and RaisePropertyChanged when properties are set.

    public class Member : INotifyPropertyChanged
    {
        string _Name;
        public string Name
        {
            get
            {
                return _Name;
            }
            set
            {
                if (_Name != value)
                {
                    _Name = value;
                    RaisePropertyChanged("Name");
                }
            }
        }
    }

View Model: The View Model also implement INotifyPropertyChanged interface, it has PropertyChanged event handler to process the event. The ICommand properties bind to button click event from the View.

class MemberViewModel : INotifyPropertyChanged
{
    List _modelList = new List
    {
        new MemberModel{Id=1, Name="First Member" },
        new MemberModel{Id=2, Name="Second Member" },
        new MemberModel{Id=3, Name="Thired Member" }
    };

    private int _id { get; set; }
    private MemberModel _member;
    private ICommand _getMemberCommand;
    private ICommand _saveMemberCommand;

    public event PropertyChangedEventHandler PropertyChanged;

    public int Id
    {
        get { return _id; }
        set
        {
            if (value != _id)
            {
                _id = value;
                OnPropertyChanged("Id");
            }
        }
    }

    public MemberModel CurrentMember
    {
        get { return _member; }
        set
        {
            if (value != _member)
            {
                _member = value;
                OnPropertyChanged("CurrentMember");
            }
        }
    }

    public ICommand SaveMemberCommand
    {
        get
        {
            if (_saveMemberCommand == null)
            {
                _saveMemberCommand = new BindCommand(
                param => SaveMember()
                );
            }
            return _saveMemberCommand;
        }
    }

    public ICommand GetMemberCommand
    {
        get
        {
            if (_getMemberCommand == null)
            {
                _getMemberCommand = new BindCommand(
                param => GetMember(),
                param => Id > 0
                );
            }
            return _getMemberCommand;
        }
    }

    private void GetMember()
    {
        MemberModel sel = _modelList.Where(o => o.Id == Id)
                            .FirstOrDefault();
        CurrentMember = sel;
    }

    private void SaveMember()
    {
        _modelList.Where(o => o.Id == CurrentMember.Id).ToList().ForEach(o => o.Name = CurrentMember.Name);
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;

        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
}

Helper class for ICommand binding:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace MemberMng.Helper
{
    class BindCommand : ICommand
    {
        readonly Action<object>  _execute;
        readonly Predicate<object>  _canExecute;

        public BindCommand(Action<object> execute) : this(execute, null)
        {
        }
 
        public BindCommand(Action<object> execute, Predicate<object>  canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }
    }
}

View: The View use Binding syntax to bind values and events to View Model

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MemberMng.View"
                    xmlns:localModel="clr-namespace:MemberMng.Model"
                    xmlns:localViewModel="clr-namespace:MemberMng.ViewModel">
    <DataTemplate DataType="{x:Type localModel:MemberModel}">
        <Border BorderBrush="Black" BorderThickness="1" Padding="20">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition  Height="40" />
                    <RowDefinition  Height="40" />
                 </Grid.RowDefinitions>

                <TextBlock Grid.Column="0" Grid.Row="0"  Text="ID" VerticalAlignment="Center" Margin="5" />
                <TextBox Grid.Row="0" Grid.Column="1" IsEnabled="False" Text="{Binding Id}" Margin="5" Width="150" />

                <TextBlock Grid.Column="0" Grid.Row="1"  Text="Name" VerticalAlignment="Center" Margin="5" />
                <TextBox Grid.Row="1" Grid.Column="1"  Text="{Binding Name}" Margin="5" Width="150" />

            </Grid>
        </Border>
    </DataTemplate>

    <DataTemplate DataType="{x:Type localViewModel:MemberViewModel}">
        <DockPanel Margin="20">
            <DockPanel DockPanel.Dock="Top">
                <TextBlock Margin="10,2" DockPanel.Dock="Left" Text="Enter Id" VerticalAlignment="Center" />

                <TextBox Margin="10,2" Width="50" VerticalAlignment="Center" Text="{Binding Path=Id, UpdateSourceTrigger=PropertyChanged}" />

                <Button Content="Save Member" DockPanel.Dock="Right" Margin="10,2" VerticalAlignment="Center"
                        Command="{Binding Path=SaveMemberCommand}" Width="100" />

                <Button Content="Get Member" DockPanel.Dock="Right" Margin="10,2" VerticalAlignment="Center"
                        Command="{Binding Path=GetMemberCommand}" IsDefault="True" Width="100" />
            </DockPanel>

            <ContentControl Margin="20,10" Content="{Binding Path=CurrentMember}" />
        </DockPanel>
    </DataTemplate>
</ResourceDictionary>

MVP (Model-View-Presentor)

MVP pattern is also widely used in different programing languages like MVC.



Model: Data and Business logic

View: User interface

Presenter: Interact with Model and View

MVP has 2 different flavors: Passive View and Supervising Controller.

Passive View is more clean and manageable, it's more commonly used. In this approach View and Model always use Presenter to communicate.

Supervising Controller approach has some compromise, that allow View and Model binding directly sometimes.

The following example is using MVP Passive View approach.

This is a very simple example to show get a person's name when a button clicked and display.

Model: Defined a interface and a class. Expose a method to get a person's name


namespace MVPExample.Models
{
    public interface IModel
    {
        string getName();
    }
}


namespace MVPExample.Models
{
    public class Model : IModel
    {
        string _name = "Tim Hortons";
        public string getName()
        {
            return _name ;
        }
    }
}

View: Define a interface and expose a method get and display a person's name on UI

namespace MVPExample
{
    public interface IView
    {
        String NameTextBox { get; set; }
    }
}

namespace MVPExample
{
    public partial class Default : System.Web.UI.Page, IView
    {
        public string NameTextBox
        {
            get
            {
                return lblName.Text;
            }
            set
            {
                lblName.Text = value;
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void btnGetName_Click(object sender, EventArgs e)
        {
            PresenterCls presenter = new PresenterCls(this, new Model());
            presenter.BindNameTextBox();
        }
    }
}

Presenter: Bind View and Model, get data from the Model and display to the View.

namespace MVPExample.Presenter
{
    public class PresenterCls
    {
        private IView view;
        private IModel model;
        public PresenterCls(IView view, IModel model)
        {
            this.view = view;
            this.model= model;
        }
        public void BindNameTextBox()
        {
            view.NameTextBox = model.getName();
        }
    }
}