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?
3 Answers 3
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.
-
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– Andy Hunt02/11/2012 19:06:56Commented 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.rlperez– rlperez02/11/2012 19:58:03Commented Feb 11, 2012 at 19:58
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.
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.
-
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?The_Sympathizer– The_Sympathizer12/19/2020 04:03:59Commented 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.).The_Sympathizer– The_Sympathizer12/19/2020 04:13:28Commented 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'ssend()
method. If I make theModel
a member of theView
, then I cannot test theView
without theModel
(tight coupling). I do not have to use a$data
variable here, but it's done for code clarity.Anthony Rutledge– Anthony Rutledge12/20/2020 18:18:24Commented 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?The_Sympathizer– The_Sympathizer12/20/2020 19:33:00Commented 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 theView
. Clean it up (or reject it) and send it to theModel
for processing. The choice of coupling theModel
to, or uncoupling theModel
from, theView
is one of testability vs execution efficiency, not functionality.Anthony Rutledge– Anthony Rutledge12/20/2020 19:46:58Commented Dec 20, 2020 at 19:46
Controller
is sending data to aView
, do you have a separateModel
?