14

I work with ASP.NET MVC (and other web-based MVC implementations) quite often, but this is something I've never been sure of: Should controller and view communicate?

Of course the controller should be choosing which view to use, but what I mean is should the controller be passing data to the view? In my opinion, if the view is expecting data from the controller, then they're effectively tied together as a (controller, view) pair. Instead, I usually have the view communicate with the model itself and be independent of any controller.

Do I have the right approach, or is this a case of there being no one correct answer? Does the answer change when working in the web versus other environments? Does the answer change when you have the concept of a strongly-typed view (like in ASP.NET MVC) or not?

2
  • This is what the "M" in "MVC" is for--the Model--which represents the data being passed from the Controller to the View. Aug 14, 2014 at 17:02
  • If a Controller is sending data to a View, do you have a separate Model? May 13, 2020 at 0:29

3 Answers 3

8

The controller prepares data which will further be passed to the view for rendering / displaying. It also accepts user input data through a publish-subscribe mechanism or similar. Check out the first diagram on Wikipedia or Martin Fowler's website for more information about MVC.

if the view is expecting data from the controller, then they're effectively tied together as a (controller, view) pair.

While a view generally accepts data, in most MVC frameworks, it does not depend on specific controllers. Exceptions are, for instance, the JavaServer Faces family. Generally speaking, frameworks like Rails, Django or Spring MVC allow you to decouple views from controllers by passing data (the context, commonly a map/dictionary/bag) to a view (where a view is an implementation of the template view pattern).

Does the answer change when you have the concept of a strongly-typed view (like in ASP.NET MVC) or not?

Whether or not your programming language is strongly-typed has no influence on the way your are organizing your application.

2
  • What kind of data is being prepared and passed across? Take a simple example: showing an article by it's ID. Is it the ID that is passed after verification (it might not point to an article), or does the controller get the article from the database and pass that across?
    – Andy Hunt
    Feb 11, 2012 at 19:06
  • If you only pass the ID is your view not doing more work than rendering? It would have to retrieve data which would probably not be in the spirit of the pattern.
    – Rig
    Feb 11, 2012 at 19:58
1

The question you are raising is discussed in my team from time to time. We argue about two approaches, which both have their cons and pros.

The first, argues that the controller may update the view by the following pattern. It listens to both GUI and model events. When a GUI event occurs, it executes the required action in the model, which in turn fires and event. Now the controller is usually updating the view with the required data.

The second approach, argues that the view itself is listening to the model events and updates itself with the data which is either attached to the event or by querying the model.

In the first approach you have more power to the controller which is really controlling everything which is going on your application. The power to decide in which way the view should be updated according to what event is in his hands and this way you are keeping your view pure. However, as you said, this way you have your view and controller coupled.

In the second you are decoupling them but your view is actually controlling itself in some manner.

1

Finally, MVC Explained Well.

Introduction

If your goal is loose coupling and the separation of concerns--and you believe in the Single Responsibility Principle--then the Controller should not pass data directly to the View. No.

Generally speaking, despite the initial learning curve for MVC, most people can understand, and agree, that the Controller is the first step and the View is the last step.

The Front Controller and Controller

All things being equal, the generic purpose of the Controller (see the Command Pattern), not the Front Controller, is to manage the relationship between the Model and the View. The Front Controller will send user input to the correct command--usually a method in a child instance of Controller.

From here, inside a method / function / command of the child Controller instance, is where the user input data must be sent to a method of the Model so that the state of the application can be managed.

Fat Versus Skinny Controllers

Debates often occur about fat versus skinny Controllers, especially when the issue of checking to see if a user is logged-in is considered. The simple resolution to this quandary is to consider user status as part of the application state.

Hence, the logic (or objects) for checking if a user is logged in should reside / be injected high up in the Model inheritance hierarchy. Thus, all child models will have the ability to detect a user's authentication status and respond accordingly.

Continuing on, if the first step (Controller) must directly communicate with the last step (View), then the relationship is tightly coupled and adulterated, which makes the View dependent on the Controller ((Controller-View) + Model) and mixes concerns.

Single Responsibility Principle

The Single Responsibility Principle attempts to give a class one job, and thus only one reason to change. If you have to change the Controller because (1) something about the View has changed, then going strictly by the book, your Controller has too many responsibilities.

Stable View Interfaces

Assuming a stable 'View" interface, changes in the Controller (the first step), or changes in the View (the last step), should be invisible to each other. The interface of the View should remain stable throughout the lifetime of the application: view.render() or $view->render(), or something to that effect. The method should expect an input argument from the Model.

Quick History Lesson

The Smalltalk programming language (the original language MVC was implemented in) passed objects between object, not arrays or maps / associative arrays between objects. Thus, one should think carefully about what kind of data structure you want the View (the presentation logic) to work with.

In many ways, it can be more elegant to pass an object wrapped around an array / map to another object. Why? Any non-view related logic can reside with the object, keeping the View only concerned with presentation logic / templating, not data conditioning logic (such as escaping data as HTML entities when necessary).

Hey, What About the Model?

A method of a child instance of Model should return data to an interfacing method of a View. Thus, by virtue of the Liskov Substitution and Dependency Inversion Principles, you should be able to inject any View object with the correct interface into a Controller and expect it to work just fine (according to defined behavior).

Polymorphism in the House

Thus, if you need a View to respond with HTML, XML, JSON, YAML, plain text, or even just HTTP headers, it will work because the implementation is hidden behind the interface / method of the child View. In effect, you would be using a Strategy pattern (for your views) and polymorphism to get different behaviors from the same View method call (view.render() or $view->render()).

Bottom Line

In effect, to establish the correct coupling relationships, separate concerns, and mind the Single Responsibility principle, one must inject (composition) a Model object and a View object into a Controller object.

Once a child Controller instance has Model and View instances as properties, it can define methods / commands that accept user data from the Front Controller.

The child Model instance will typically (1) establish user authentication status (via inheritance), and (2) validate user input data (encoding checks, filtering / sanitizing, validating).

When the Model method finishes its work, it returns all necessary data to a method of the View (render(), or something similar). All of this is occurring inside a method of the Controller.

Each command / method of the child Controller serves as a top level main line program for each application request.

Bonus: The Observer Pattern

What people forget is that the original MVC architecture was coded in Smalltalk, and was designed for GUI software running on a single computer. Server-side MVC implementations are not structured the same as a desktop application might be.

In an ideal server-side world, a change in the server-side Model's application state would automatically update the application / DOM on the client-side, and vice versa. HA!

The Observer Pattern was designed to allow objects to automatically update one another when certain actions take place by registering with one another. Unfortunately, it is not possible to register client-side View objects with server-side Model objects.

Instead, under normal circumstances, the best that we can do from the server-side is to send new state data (XML, JSON, YAML, text, ...), typically to client-side JavaScript. Hopefully, the original, incoming HTTP request came from an XMLHttpRequest object (an asynchronous HTTP request) so that an entire HTML file (plus any images, CSS, blah, blah, blah ...) does not need to be loaded synchronously.

What about ReSTful APIs?

If you are making a ReSTful API, you only ever send application state data (frequently, JSON or XML). The only question is how you should format and structure the data (typically determined by the incoming HTTP Accept header).

Double Bonus: HTTP Headers

Yes, a very thorough server-side, MVC implementation would be ReSTful and send the appropriate HTTP header and status code before sending state data back to the client.

The original MVC implementation in Smalltalk did not have to consider this at all!

Additionally, HTTP caching should be accounted for.

In short, the first way to create a successful, ReSTful MVC architecture is to solve how you will send the appropriate HTTP headers and HTTP status code back to the client.

If you have not done that work, you are omitting an important aspect of the fundamental architecture of the World Wide Web.

Conclusion

If your Controller is passing data to the View, then you are missing the middle step, an intentionally defined and separate Model. If a Controller is passing data to a View, this does not describe a true MVC architecture. That would be a Controller-View (CV), which is doable, except for violating purist ideals about loose coupling, separation of concerns, single responsibility, substitution, and dependency inversion.

In short, an application that merges the Controller and Model (Codel? Montroller?), then sends some kind of data structure to the View (V), is a software engineering regression. Some might call that an anti-pattern.

Side Issue ==========

(Another issue about Controllers that interest me is that because some content on a website will be public (no login required), but other content will be private, there's an opportunity for efficiency if Controller is sub-classed into PublicController and PrivateController. Then, sub-class these classes accordingly based on 1) The public website, or 2) Content that requires you to be logged in.

Again, the original MVC architecture did not have to deal with the concept of public and private content.

By making these fundamental subclasses, the FrontController can act in a more secure way by denying access to content that requires one to be logged in first. If the instance of the requested Controller is on the "private" list and the requestor is not logged in, then the FrontController should use "common sense" and deny, or redirect.

This check will occur anyway in a flatter Controller hierarchy, but it will have to wait until the desired Model is instantiated, which in my opinion is inefficient. Why should someone who is not logged in be able to instantiate a Model that they cannot use!! Just to have it do a log-in check, and then reject/redirect the request? Dumb. Straight dumb.

It's really dumb when you consider how many objects might have to be instantiated and injected into an instance of a Model, just to in the first few lines of execution determine that NOPE, you're not logged in. ;-)

In my opinion, FrontControllers (the router/dispatcher stuff) should have the option to deny, or redirect to other controllers, without having to instantiate a Model to do it.

Update:

I have received some interesting comments about this answer. A small, practical illustration should make my discourse more clear. This is a bare bones example in PHP because it's easy.

Let's assume the user's address bar shows the following in their browser / user-agent because they are filling out a form.

https://www.example.com/contact

When they hit the send button, the user-agent or XmlHttpRequest object, is instructed to go here:

https://www.example.com/contact/send

abstract class Model
{
    protected Authenticator $authenticator;

    protected function __construct(Authenticator $authenticator) {
        $this->authenticator = $authenticator;
    }

    protected function isLoggedIn(): bool {
        return $this->authenticator->isAuthenticated();
    }
}

class ContactModel extends Model
{
    private Validator $validator; // Throws exceptions
    private Notifier $notifier;   // Sends a type of message, throws exceptions
    private Message $message;     // A specific message, less user input, throws exceptions

    public function __construct(Authenticator $authenticator, Validator $validator, Notifier $notifier, Message $message) {
        parent::__construct($authenticator);
        $this->validator = $validator;
        $this->notifier = $notifier;
        $this->message = $message;
    }

    public function sendEmail(Request $request): array {
        try {
            $cleanData = $this->validator->test($request);
            $message = $this->message->format($cleanData);
            $this->notifier->send($message);
            return ['emailSent' => true];
        } catch(Exception $e) {
            return ['emailSent' => false];                 
        }
    }
}

abstract class View
{
    protected function __construct() {

    }

    abstract public function render():
}

abstract class HttpView extends View
{
    private Response $response;

    protected function __construct(Response $response) {
        $this->response = $response;
    }

    public function render(array $headers) {
        $this->response->addHeaders($headers);
        $this->response->sendHeaders();
    }

    protected function setMessage(string $message) {
        $this->response->setBody($message);
    }

    protected function reply() {
        $this->response->sendBody();
    }
}

abstract class JsonView extends HttpView
{
    public function __construct(Response $response) {
        parent::__construct(Response $response);
    }

    public function render(array $data) {
        $this->setMessage(json_encode($data, JSON_THROW_ON_ERROR));
        parent::render($data['headers']);
        $this->reply();
    }
}

abstract class XmlView extends HttpView
{
    private Template $template;

    public function __construct(Response $response, Template $template) {
        parent::__construct(Response $response);
    }

    public function render(array $data) {
        $this->setMessage($this->template->merge($data['body']));
        parent::render($data['headers']);
        $this->reply();
    }
}

abstract class HtmlView extends HttpView
{
    private Template $template;

    public function __construct(Response $response, Template $template) {
        parent::__construct(Response $response);
        $this->template = $template;
    }

    public function render(array $data) {
        $this->setMessage($this->template->merge($data['body']));
        parent::render($data['headers']);
        $this->reply();
    }
}

/* Which "Contact" view gets instantiated depends on the incoming
   HTTP Accept header! One View for any particular media type.
   Although, one might not need more than JSON in many cases.
*/

class ContactJsonView extends JsonView
{
    public function __construct(Response $response) {
        parent::__construct($response);
    }
}

class ContactXmlView extends XmlView
{
    public function __construct(Response $response, Template $template) {
        parent::__construct($response, $template);
    }
}

class ContactHtmlView extends HtmlView
{
    public function __construct(Response $response, Template $template) {
        parent::__construct($response, $template);
    }
}

// ***********************************

abstract class Controller
{
    protected $model;
    protected $view;

    public function __construct(Model $model, View $view) {
        $this->model = $model;
        $this->view = $view;
    }
}

/*
  Controllers of this side of the family require authentication before
  instantiating an instance of a Model or View.
*/
abstract class PrivateController extends Controller
{
    protected Authenticator $authenticator;

    public function __construct(Model $model, View $view, Authenicator $authenticator) {
        parent::__construct($model, $view);
        $this->authenticator = $authenticator;
    }
}

/*
  Controllers that are publicly accessible. No authentication required.
*/
abstract class PublicController extends Controller
{
    public function __construct(Model $model, View $view) {
        parent::__construct($model, $view);
    }
}

class ContactController extends PublicController
{
    public function __construct(Model $model, View $view) {
        parent::__construct($model, $view);
    }

    // Assume a well organized HTTP Request object is being utilized.

    public function send(Request $request) {
        $data = $this->model->sendEmail($request);
        $this->view->render($data);  // The Model is updating the View

        /* Note, if I had made the Model a property of the View, it
           would go against the idea of dependency injection and making
           code that can be easily tested. By only passing data to the
           View, I can test the view without having to instantiate the Model.

           In this case, $data is the minimum amount of data required
           to be sent back to the user-agent to update the state of the
           application (Model) on the client side. It's up to the client
           to update it's View based on the contents of $data.

           E-mail will not be part of the View. It's more of a side effect in this case.
         */
    }
}

// Meanwhile, in the HALL OF JUSTICE!! (index.php)
// -------------------------------------------------------------

try {

    require 'Autolaoder.php';
    $errorHandler = new ErrorHandler();    

    $validator = new HttpRequestValidator();
    $validator->test(); // Testing the header info, not the true user info yet

    $request = new HttpRequest($validator->getCleanData());
    $response = new HttpResponse(); // Must be populated with data

    $model = new ContactModel(new Authenticator(), new ContactValidator(), new EmailNotifier(), new ContactMessage());

    $view;
    $acceptHeader = $request->getHeader('Accept');

    if ($acceptHeader === 'application/json') {
        $view = new ContactJsonView($response);
    } elsif ($acceptHeader === 'application/xml') {
        $view = new ContactXmlView($response, new ContactXmlTemplate());
    } else {
        $view = new ContactHtmlView($response, new ContactHtmlTemplate());
    }

    $controller = new ContractController($model, view);
    $controller->send($request);
} catch(Throwable $e) {
    // Run error sequence. 
}

Final Summary

In a PHP MVC application, URL rewriting will occur (Apache, or other) first so that there can be a single point of entry (index.php) into the app.

The code here at the end is, in part, what would really be going on in the index.php before instantiating a FrontController, and inside FrontController itself.

Note that I have made a very, very oversimplified solution here at the end. The FrontController should be able to DYNAMICALLY determine the correct Controller from the request URL https://www.example.com/contact/send, by using 'contact' (or anything validated in that position) as the Controller, and 'send' (or anything validated in that position) as the action / command / menthod.

A more modular solution would have a a Router class to examine the request URL, break it down, verify the route is valid with for the app, and then find the correct controller, limited by a correctly corresponding HTTP request method (GET, POST, PUT, PATCH, DELETE, HEAD, ... whatever). One must not only ensure that the Controller exists, but that also the method on the Controller exists, too!

If a truly ReSTful MVC implementation is desired, then the View must be able to be dynamically determined as well. This is feasible because if the HTTP Accept header has been sent by the client, it should indicate the representation of the data it wants in reply. Can you have a default View? Can you ever know for certain that you will always get asked for JSON or HTML? No. It's up to you to support the media types that your clients are allowed to receive, and that you want to provide.

If the client asks for a media-type that you do not support, throw an exception and respond with an error (and correct error code). I have seen some very creative error pages! ;-)

All in all, this is why dependency injection containers are very useful. Keeping track of class dependencies, and use of the new keyword, can happen in just one place if you use a dependency injection container.

You should have an autoloader. You should have error handling. You should have input validation (encoding, sanitization, validation). You should have a sessions solution (not implemented here). You should have a modular FrontController, and use of the Relfection API can be useful in determining if Classes and methods exists. PHP has built in methods for such things, but use what's best for your use case.

11
  • I'm not quite sure I get this. From this: "When the Model method finishes its work, it returns all necessary data to a method of the View (render(), or something similar). All of this is occurring inside a method of the Controller." it sounds like the Controller still needs to capture the return data from the Model and then pass that on to the View. But you say that controllers should not pass data to views! Moreover, isn't having Controller as a bridge between Model and View with no direct communication from the Model to View an MVP, not MVC, pattern? Dec 19, 2020 at 4:03
  • Also, I've been looking at this: martinfowler.com/eaaDev/uiArchs.html#ModelViewController and it suggests View should be an observer of Model, so there would be no need for Controller to call a "render()" method (though then it also says that Model should be fully ignorant of UI layer ... yet you need to register that observer! Gaah, this stuff is confusing as hell.). Dec 19, 2020 at 4:13
  • @The_Sympathizer I've added a rudimentary example of a ReSTful MVC setup. The ball has to get rolling somewhere! ;-) Models can't execute themselves. The action / command of the controller is kind of acting like a mainline for valid requests. See ContactController above and it's send() method. If I make the Model a member of the View, then I cannot test the View without the Model (tight coupling). I do not have to use a $data variable here, but it's done for code clarity. Dec 20, 2020 at 18:18
  • Thanks. I still have a few more questions. 1. Where is the "front controller" in this? Is that the part that you have marked as going into index.php, and is not a class? 2. So yes, the Controller does pass data along to the View, but I guess the relevant point is that the controller doesn't do anything to it, it simply hands it along, right? Dec 20, 2020 at 19:33
  • @The_Sympathizer Let's start with number 2. ;-) The OP's question didn't really specify which "data" he was talking about passing to the View, but that $data variable can easily be replaced by nesting method calls. When I say passing "data", I'm talking about that which comes from the user / client. No, don't pass user data to the View. Clean it up (or reject it) and send it to the Model for processing. The choice of coupling the Model to, or uncoupling the Model from, the View is one of testability vs execution efficiency, not functionality. Dec 20, 2020 at 19:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.