The Model-View-Controller pattern

This is just a quick tutorial for those who have never used the MVC pattern, so if you know your stuff, you can stop reading right now. For all the others: Let’s dive in!

You’ve seen them everywhere. Chances are you’ve written them, too. Those big, ugly classes called “MainForm” or “DlgPrintInvoice” and all the others. Thousands of lines of code, containing data holders, business logic, threads, database access, and even GUI code.

The MVC pattern strives to replace this chaos with code divided up into several classes that each do ONE thing right.

Wait, what? I hear you say. So many classes just for a single dialog? Yes, exactly. Because very often you need some piece of code from one dialog in another. And sometimes, you even want to — gasp! — write unit tests. And suddenly, having multiple classes with their own concerns each seems reasonable.

A quick example

So where do we start? Open up your favorite C# IDE and create a simple form with a TextBox and a button. What we want the program to do is this: If the user clicks on the button, a MessageBox should pop up with the text entered in the TextBox reversed. Easy enough.

A first try could look like this:

using System;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public sealed partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Button1Click(object sender, EventArgs e)
        {
            MessageBox.Show(string.Join("", textBox1.Text.Reverse()));
        }
    }
}

Now what’s the problem with that, you ask. And in fact, as long as your programs are as small as this one, there is little need to use “big” patterns like MVC. But small and simple programs tend to become large and complex programs, and suddenly you end up with a 7000 LOC MainForm.cs containing everything but the kitchen sink. So before we add the next few features, let’s cut this little class into multiple ones.

Separating concerns

Let’s see. Which concerns are there? Well, there’s the GUI, obviously. We want the user to see something on the screen and give them something to click on. Then there’s the “business logic”, which in this case amounts to calling the LINQ method .Reverse() and joining the result back into a string. Later on, we’re likely to have a more complex logic that we’ll want to unit test, so better factor out that part first:

using System;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public sealed partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Button1Click(object sender, EventArgs e)
        {
            MessageBox.Show(ReverseString(textBox1.Text));
        }

        private static string ReverseString(string textEnteredByUser)
        {
            return string.Join("", textEnteredByUser.Reverse());
        }
    }
}

Better, but the logic is still contained inside the GUI class. For a clean separation, we’ll want a separate class for our business logic, and we call this kind of class a “model”:

using System.Linq;

namespace WindowsFormsApplication1
{
    public sealed class Model
    {
        public string ReverseString(string textEnteredByUser)
        {
            return string.Join("", textEnteredByUser.Reverse());
        }
    }
}

Here’s how to use it:


using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public sealed partial class Form1 : Form
    {
        private readonly Model _model;

        public Form1(Model model)
        {
            InitializeComponent();
            _model = model;
        }

        private void Button1Click(object sender, EventArgs e)
        {
            MessageBox.Show(_model.ReverseString(textBox1.Text));
        }
    }
}

I sneakily used another pattern here called “Dependency Injection”: The GUI doesn’t create the model itself, but is given a model during construction. This way we can have multiple forms access the same data model.

In order to be able to compile our little program, we have to make a change to Program.cs, too:

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var model = new Model();
            Application.Run(new Form1(model));
        }
    }
} 

Now we can easily write unit tests for our business logic without having to create a whole GUI, simulate button clicks and whatnot. For many software products, this is it already. Just separate the GUI from the business logic, and you’re done. But what if for some reason you want to disable the button while the business logic calculates its answer? Do you put that code into the GUI or into the model? The GUI, of course. But now there is some kind of behavior that your program shows, that you can’t put under test. What now?

The Controller

Enter the third part of the MVC pattern, the Controller. This is the part that reacts to the user doing something and tells both model and GUI what to do in response. But first, let’s turn our GUI into just a GUI and nothing more:

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public sealed partial class Form1 : Form
    {
        public event EventHandler ButtonClicked;

        public Form1()
        {
            InitializeComponent();
        }

        private void Button1Click(object sender, EventArgs e)
        {
            if (ButtonClicked != null)
                ButtonClicked(this, e);
        }

        public string GetTextEnteredByUser()
        {
            return textBox1.Text;
        }

        public void ShowMessageBox(string message)
        {
            MessageBox.Show(message);
        }

        public void SetButtonEnabled(bool enabled)
        {
            button1.Enabled = enabled;
        }
    }
}

Not much to it anymore, and that is the point exactly. Now there is no code left in the GUI that needs to be tested, as it’s just delegating events and member accesses via method calls.

The new Controller class looks like this:

using System;

namespace WindowsFormsApplication1
{
    public sealed class Controller
    {
        private readonly Form1 _gui;
        private readonly Model _model;

        public Controller(Form1 gui, Model model)
        {
            _gui = gui;
            _model = model;
            _gui.ButtonClicked += HandleButtonClicked;
        }

        private void HandleButtonClicked(object sender, EventArgs e)
        {
            _gui.SetButtonEnabled(false);
            string textEnteredByUser = _gui.GetTextEnteredByUser();
            _gui.ShowMessageBox(_model.ReverseString(textEnteredByUser));
            _gui.SetButtonEnabled(true);
        }
    }
}

That’s the part where the behavior of our GUI is encoded. Note that there is absolutely no GUI specific code here, just the raw if-user-does-this-then-do-that rules.

Now it’s just one more change to Program.cs so we can run our program:

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var model = new Model();
            var mainForm = new Form1();
            var controller = new Controller(mainForm, model);
            Application.Run(mainForm);
        }
    }
}

Sometimes, the View knows about the Model too, and observes changes in the Model to update itself automatically. Personally, I prefer this cleanly separated approach with the Controller sitting in between Model and View as a mediator (cue Design Pattern name), with the latter two not knowing about each other. Pro: Cleaner separation, con: More code for simple GUI updates. YMMV.

Preparing for unit tests

Now that all the concerns are cleanly separated, we can think about unit tests. The GUI doesn’t really need to be tested, but if you want to, most frameworks have nice GUI testing capabilities. No problem here. The model can be tested as is, and didn’t even have to be modified during our last step. The controller, however, is a different beast. Currently it depends on the concrete classes Form1 and Model. Bad. We want to be able to inject mock objects for testing, so we’ll need two interfaces:

using System;

namespace WindowsFormsApplication1
{
    public interface IModel
    {
        string ReverseString(string textEnteredByUser);
    }

    public interface IForm1
    {
        event EventHandler ButtonClicked;
        string GetTextEnteredByUser();
        void ShowMessageBox(string message);
        void SetButtonEnabled(bool enabled);
    }
}

Modify the concrete classes like this:

public sealed class Model : IModel
public sealed partial class Form1 : Form, IForm1

And now the controller:


        private readonly IForm1 _gui;
        private readonly IModel _model;        public Controller(IForm1 gui, IModel model)
        {
                ...
        }

Now we can write unit tests for the controller too, and use them to test the user experience. We could write multiple GUIs that all implement the same interface and use one of them as by the user’s preferences, while keeping the same controller and model. We could exchange the controller, or wrap it in a decorator for logging, exception handling, having the GUI run on a different computer… With a nice message handling system running on top of HTML 5, we could even use a web GUI with the same controller and model used for the WinForms GUI. And that is why MVC is a cool thing. As long as your programs are longer than a few dozen lines.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s