What's the role of your ASP.NET MVC controller?
In an ASP.NET MVC application, controllers exist to respond to any requests a user makes from within the user interface. In this regard, controllers are analogous to code-behind classes in Web Forms. Controller methods are nearly the same as postback handlers. Controllers, in the end, belong to the presentation layer of a web application. In a layered architecture, the presentation layer doesn't really do much other than mediating between the user and the back end of the system. Subsequently, the controller doesn't have to implement much work and should be a fairly simple class with a hard-coded structure.
Ideally, a controller method does the following: It defines expected input parameters in the signature, validates input parameters, passes parameters to the application layer, and retrieves only the information it needs to generate the action result.
The stereotype that guides a controller method's behavior is the stereotype of a "coordinator." An analysis methodology known as Responsibility-driven design (RDD) identifies three stereotypes that you can use to describe the expected behavior of a controller method: service provider, controller, and coordinator.
The service provider stereotype indicates a component that performs a very specific task. The controller stereotype indicates a component that governs the execution of a task by orchestrating the action of multiple providers. The coordinator stereotype identifies a component that delegates any action to an external component and is limited to passing input and capturing output. Because an MVC controller is part of the presentation, according to separation of concerns it should behave as a coordinator.
Will the coordinator stereotype lean toward having lean-and-mean methods? Less often than you might think. Consider that when validating input parameters (as processed by model binders) that invoke an external method on the application layer, preparing any response for the view might require a method 30 or more lines long. This is nothing to be scared of; but also nothing to be proud of. These days my design efforts are oriented toward making the code as clean as possible, trivial to read and follow, and to a large extent self documenting. You should aim for simple methods that have a clear and short set of instructions, that make it trivial to understand what's going on, and that provide a clear idea of where to look for drilling down.
Achieving the Design Goal
Which tools can you leverage to achieve this design goal? For parameter validation, I suggest using Code Contracts preconditions. Abstractly speaking, a precondition contract boils down to an if-then-throw statement placed at the very beginning of the method. The main plusses of using contracts are enhanced readability and enabled capabilities of running a static analysis on the code. Static analysis means that a tool in the background of Visual Studio will read your code and contracts, giving feedback about places where contracts are violated, and creating the natural habitat for runtime exceptions.
For executing the expected action, I suggest you create a distinct layer of services that implement the application logic for the use case the controller refers to. All you do is invoke a method on the service layer, passing input data received from the request. The instance of the service class—not necessarily a real Windows Communication Foundation (WCF) service class—ideally will be injected in the controller through an Inversion of Control (IoC) container. In ASP.NET MVC 3, you can wrap the IoC container in a dependency resolver. In MVC 2, you must pass through a controller factory. This option is also applicable to MVC 3. The plus of using dependency injection is enhanced testability of the controller and the other layers down the stack. It also makes the entire solution more flexible and resilient to changes
Designing for Readability
What data should you expect to receive from the service layer? Ideally, you invoke a method that returns exactly the data you intend to pass to the view. This would save the controller method from incorporating any helper code to just prepare the call to the view engine. Taking this approach, however, requires data transfer objects for each possible interaction between presentation and back end.
For large systems, reusing transfer objects at the cost of introducing adapters might be desirable. To keep adapter code out of the controllers, you might want to introduce the concept of a view model builder—a class that gets response from the service layer and returns the view model object ready for the view engine.
By following these steps, a controller class becomes surprisingly easy to read and understand for novices and newly hired developers—and even for expert developers.
Dino Esposito (email@example.com), an architect at IDesign, specializes mainly in ASP.NET, AJAX, and RIA solutions. Dino co-authored Microsoft .NET: Architecting Applications for the Enterprise (Microsoft Press) and is the author of Programming ASP.NET MVC 2 (Microsoft Press).