Updated for ASP.NET MVC 3, Dino Esposito’s Programming Microsoft ASP.NET MVC, Second Edition, is now available. You can see the book’s “Contents at a Glance” and read its Introduction in this earlier post. In this post we share an excerpt from one of the book’s chapters, Chapter 7, “Design Considerations for ASP.NET MVC Controllers.” Here’s the book part the chapter comes from:
PART II ASP.NET MVC Software Design
CHAPTER 5 Aspects of ASP.NET MVC Applications. . . . . . . . . .189 CHAPTER 6 Securing Your Application . . . . . . . . . . . . . . . . . . . .227 CHAPTER 7 Design Considerations for ASP.NET MVC Controllers . . . .253 CHAPTER 8 Customizing ASP.NET MVC Controllers . . . . . . . . .281 CHAPTER 9 Testing and Testability in ASP.NET MVC . . . . . . . .327
CHAPTER 7
Design Considerations for ASP.NET MVC Controllers
Part of the inhumanity of the computer is that, once it is competently programmed and working smoothly, it is completely honest. —Isaac Asimov
The controller is the central element of any operation you perform in ASP.NET MVC. The controller is responsible for getting posted data, executing the related action and then preparing and requesting the view. More often than not, these apparently simple steps originate a lot of code. Worse yet, similar code ends up being used in similar methods, and similar helper classes sprout up from nowhere.
ASP.NET MVC comes with the promise that it makes it easier for you to write cleaner and more testable code. For sure, ASP.NET MVC is based on some infrastructure that makes this possible and easier than in Web Forms. A lot, however, is left to you—the developer—and to your programming discipline and design vision.
Architecturally speaking, the controller is just the same as the code-behind class in Web Forms. It is part of the presentation layer, and in some way it exists to forward requests to the back end of the application. Without development discipline, the controller can easily grow as messy and inextricable as an old-fashioned code-behind class. So it isn’t just choosing ASP.NET MVC that determines whether you’re safe with regard to code cleanliness and quality.
In this chapter, we’ll explore an approach to ASP.NET MVC design that simplifies the steps you need to mechanize the implementation of the controller classes. The idea is to make the controller an extremely lean and mean class that delegates responsibility rather than orchestrating tasks. This design has an impact on other layers of the application and also on some portions of the ASP.NET MVC infrastructure.
Shaping Up Your Controller
Microsoft Visual Studio makes it easy to create your own controller class. It requires you to right-click on the Controllers folder in the current ASP.NET MVC project and add a new controller class. In a controller class, you’ll have one method per user action that falls under the responsibility of the controller. How do you code an action method?
An action method should collect input data and use it to prepare one or multiple calls to some endpoint exposed by the middle tier of the application. Next, it receives output and ensures that output is in the format that the view needs to receive. Finally, the action method calls out the view engine to render a specific template.
Well, all this work might add up to several lines of code, making even a controller class with just a few methods quite a messy class. The first point—getting input data—is mostly solved for you by the model-binder class. Invoking the view is just one call to a method that triggers the processing of the action result. The core of the action method is in the code that performs the task and prepares data for the view.
Choosing the Right Stereotype
Generally speaking, an action method has two possible roles. It can play the role of a controller, or it can be a coordinator. Where do words like “controller” and “coordinator” come from? Obviously, in this context the word “controller” has nothing to do with an ASP.NET MVC controller class. These words refer to object stereotypes, a concept that comes from a methodology known as Responsibility-Driven Design (RDD). Normally, RDD applies to the design of an object model in the context of a system, but some of its concepts also apply neatly to the relatively simpler problem of modeling the behavior of an action method.
Note For more information about RDD, check out Object Design: Roles, Responsibilities, and Collaborations, by Rebecca Wirfs-Brock and Alan McKean (Addison-Wesley, 2002).
RDD at a Glance
The essence of RDD consists of breaking down a system feature into a number of actions that the system must perform. Next, each of these actions is mapped to an object in the system being designed. Executing the action becomes a specific responsibility of the object. The role of the object depends on the responsibilities it takes on. Table 7-1 describes the key concepts of RDD and defines some of the terms associated with its use.
Table 7-2 summarizes the main classes of responsibility for an object. These are referred to as object role stereotypes.
In RDD, every software component has a role to play in a specific scenario. When using RDD, you employ stereotypes to assign each object its own role. Let’s see how RDD stereotypes can be applied to an action method.
Breaking Down the Execution of a Request
I’ve described some common steps that all action methods should implement. The responsibility of an action method can be broken down as follows:
Both the Controller and Coordinator RDD stereotypes can be used to implement an action method—but they won’t produce the same effects.
Acting as a “Controller”
Let’s consider an action method in the apparently simple place-order use-case. In the real-world, placing an order is never a simple matter of adding a record to the Orders table. It’s an action that involves several steps and objects. It requires querying the databases to find out about the availability of the ordered goods. It might also require an order to be placed to a provider to refill the inventory. Placing an order typically requires checking the credit status of the customer and syncing up with the bank of the customer and the shipping company. Finally, it also involves doing some updates on some database tables. The following pseudo-code gives you an idea of the concrete steps you need to take:
[HttpPost] public ActionResult PlaceOrder(OrderInfo order) { // Input data already mapped thanks to the model binder // Step 1-Check goods availability ... // Step 2-Check credit status of the customer ... // Step 3-Sync up with the shipping company ... // Step 4-Update databases ... // Step 5-Notify the customer ... // Prepare the view model var model = PlaceOrderViewModel { ... }; ... // Invoke next view return View(model); }
Having all these steps coded in the controller at a minimum means that you end up with calls made to the data access layer from the presentation. For simple CRUD (Create, Read, Update, Delete) applications, this is acceptable; but it’s not acceptable for more complex applications.
Even when each of the steps outlined resolves in one or two lines of code, you have quite a long and soon unmanageable method. The RDD Controller stereotype applied to ASP.NET MVC controller classes suggests you should use the previous layout of the code. This is not ideal even for moderately complex applications.
Acting as a “Coordinator”
The RDD Coordinator stereotype suggests that you group all of the steps that form the implementation of the action within a single worker object. From within the action method, you place a single call to the worker and use its output to feed the view-model object. The layout follows.
[HttpPost] public ActionResult PlaceOrder(OrderInfo order) { // Input data already mapped thanks to the model binder // Perform the task invoking a worker service var workerService = new WorkerService(); var response = workerService.PerformSomeTask(); // Prepare the view model var model = PlaceOrderViewModel(response); ... // Invoke next view return View(model); }
The overall structure of the controller method is much simpler now. Solicited by an incoming HTTP request, the action method relays most of the job to other components. I call these compo- nents worker services; in RDD jargon, they look a lot like Controller objects and, in some simple cases, they’re just service providers.
Fat-Free Controllers
ASP.NET MVC is a framework that is designed to be testable and promotes important principles such as separation of concerns (SoC) and Dependency Injection (DI). ASP.NET MVC tells you that an application is separated in a part known as the controller and a part referred to as the view (not to mention the model discussed here). Being forced to create a controller class doesn’t mean you’ll automatically achieve the right level of SoC, and it certainly doesn’t mean that you’re writing testable code. As mentioned in Chapter 1, “ASP.NET MVC Controllers,” ASP.NET MVC gets you off to a good start, but any further (required) layering is up to you.
What I haven’t probably stated clearly enough is that if you don’t pay close attention, you end up with a fat and messy controller class, which certainly isn’t any better than a messy (and justifi- ably despised) code-behind class. So you should aim to create controller classes as lean and mean collections of endpoints and remove any fat from them.
Note According to my standards, I wasn’t precise earlier when I called Dependency Injection a principle. More specifically, DI is just the most popular pattern used to implement the Dependency Inversion Principle, according to which the surface of contact between dependent classes should always be an interface instead of an implementation. Much less known (and understood) than DI in the wild, the Dependency Inversion Principle is the “D” in the popular SOLID acronym that summarizes the five key design principles for writing clean, high-quality code.
Short Is Always Better
If you have a method that is about 100 logical lines long, that code probably includes 10 to 15 lines of comments. Generally, 10 percent is considered to be a fair ratio of code to comments; I’d even go as high as a comment every three logical lines if you want to make sure that you explain clearly the whys and wherefores of what you’re doing and really want to help whomever deals with that piece of code after you.
However, regardless of what you decide the ideal ratio is, my point is that a method that’s 100 lines long makes little sense. You can probably break it into three or four smaller methods, and get rid of some comments too.
I don’t call myself an expert in software metrics, but I usually try to keep my methods below 20 lines—which more or less matches the real estate available in the Visual Studio editor on a normal laptop.
How can you manage to keep the code of action methods as short as possible? Surprisingly enough, applying the RDD Coordinator stereotype is what you must do, but even that’s not always sufficient.
Action Methods Coded as View Model Builders
A method designed to be a coordinator invokes a method on a worker object, has some work done, and gets some data back. This data should simply be packed into a dictionary, or a strongly typed class, and then passed down to the view engine.
The worker class, though, is attempting to bridge the gap between the data model you have on the middle tier—the domain model—and the data model you have in the presentation layer—the view model, or the data being worked on in the view. (By the way, “the data being worked on in the view” is the wording originally used in the MVC paper to define the role of the model.)
If the business objects you invoke on your middle tier return collections or aggregates of domain objects, you probably need to massage this data into view-model objects that faithfully represent the contracted user interface. If you move this work into the controller class, you’re back to square one. The lines of code you cut off by using worker services and the RDD Coordinator stereotype are replaced by just as many lines for building a view model.
To support your efforts in getting fat-free controllers, I recommend a strategy based on the following points:
Let’s see how I envision a worker service class.
Worker Services
A worker service is a helper class that goes hand in hand with the controller. You might reasonably expect to have a distinct worker service class for each controller. On the other hand, the worker service is just an extension of a controller and results from the logical split of the controller behavior pushed by the RDD Coordinator role.
I’m using the word service here to indicate that this class provides a service to callers—it has nothing to do with any technology for implementing services. Figure 7-1 shows an architectural perspective of worker services in ASP.NET MVC.
A worker service is just a matter of design, and design is design regardless of its complexity. So you don’t have to wait for a giant project to experiment with these features. Let’s go through a simple example that shows the power of the worker service approach. Admittedly, it might sound like a lot of work to do for a simple demo, but in the end it costs you just an extra interface—and it scales exceptionally well with complexity of the domain.
Implementing a Worker Service
You can start by creating a WorkerServices folder in your ASP.NET MVC project. Which folders you cre- ate under it is entirely your responsibility. I usually go with one folder for each controller plus an extra folder for interfaces. Figure 7-2 shows a glimpse of a project using this approach.
If you prefer, you can move the WorkerServices section to a separate assembly—it’s your call. As mentioned, you create one worker service for each controller. For the Home controller, you can create the IHomeServices interface and the HomeServices class:
public interface IHomeServices { HomeViewModel GetHomeViewModel(); } public class HomeServices : IHomeServices { private IHomeServices _workerService; public HomeViewModel GetHomeViewModel() { ... } ... }
In the sample application we’re considering, the home page picks up a list of featured dates and renders the time span in days between those days and the current day. On the middle tier, you have a repository that returns information about featured dates such as the date, whether it is absolute or relative (for example, February 8, regardless of the year), and a description for the date. Here’s an example for a featured date object for a domain model:
namespace FatFree.Framework.DomainModel { public class MementoDate { public DateTime Date { get; set; } public String Description { get; set; } public Boolean IsRelative { get; set; } } }
The repository will likely fill up a collection of these objects when querying some database. At any rate, the worker service gets a collection of MementoDate objects and processes them up to the point of obtaining a collection of FeaturedDate objects—a type that belongs to another object model, the view model:
namespace FatFree.ViewModels { public class FeaturedDate { public DateTime Date { get; set; } public Int32 DaysToGo { get; set; } public String Description { get; set; } } }
There are two operations that need be done. First, any relative date must be transformed into an absolute date. Second, the time span between the given date and the current day must be calculated. For example, suppose you want to calculate the distance to the next occurrence of February 8. The target date is different if you’re computing January 2 or March 5.
Here’s a portion of the code in the worker service:
private IDateRepository _repository; ... public HomeViewModel GetHomeViewModel() { // Get featured dates from the middle tier var dates = _repository.GetFeaturedDates(); // Adjust featured dates for the view // for example, calculate distance from now to specified dates var featuredDates = new List<FeaturedDate>(); foreach(var mementoDate in dates) { var fd = new FeaturedDate { Description = mementoDate.Description, Date = mementoDate.IsRelative ? DateTime.Now.Next(mementoDate.Date.Month, mementoDate.Date.Day)
: mementoDate.Date }; fd.DaysToGo = (Int32)(DateTime.Now - fd.Date).TotalDays; featuredDates.Add(fd); } // Package data into the view model as the view engine expects var model = new HomeViewModel { Title = "Memento (BETA)", MessageFormat = "Today is <span class='dateEmphasis'>{0}</s Today = DateTime.Now.ToString("dddd, dd MMMM yyyy"), FeaturedDates = featuredDates }; return model; }
What about the controller? Here is the code you need:
public ActionResult Index() { var model = _workerService.GetHomeViewModel(); return View(model); }
Figure 7-3 shows the sample page in action.
As you can see, there’s no magic behind worker services. As the name suggests, they are worker classes that just break up the code that would logically belong to the processor of the request.
Do We Really Need Controllers?
The code of each controller method will hardly be as simple as what I’ve shown here, which was just one logical line. In real-world scenarios, you might need to pass some input data to the worker service—perhaps use an if statement to quickly rule out some cases, or even further edit the view- model object. This latter scenario might occur when you attempt to gain some reusability and get one worker service method to serve the needs of two or more controllers’ action methods.
To flesh out the code in action methods, use exception handling, null checks, and preconditions. In the end, to keep action methods lean and mean you need to push the RDD Coordinator role to the limit and move any processing logic out of the controller.
Does this mean that you don’t need controllers anymore? Each HTTP request maps to an action method, but you need some plumbing to make the connection. In ASP.NET MVC, the controller is just part of the infrastructure, it shouldn’t contain much of your code and, big surprise, there should be no need to test it. If you consider controllers to be part of the infrastructure, then you take their basic behavior for granted; you need to test your worker services instead.
The Ideal Action Method Code
Let’s top off this discussion by analyzing an ideal fragment of code you should find in your action methods. It uses attributes to handle exceptions and Code Contracts to determine preconditions:
[HandleError(...)] public class DateController : Controller { private readonly IDateServices _workerService; public DateController() : this(new DateServices()) { } public DateController(IDateServices service) { _workerService = service; } [MementoInvalidDateException] [MementoDateExistsException] [HttpPost] public ActionResult Add(DateTime date, String description) { Contract.Requires<ArgumentException>(date > DateTime.MinValue); Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(description); var model = _workerService.AddNewDate(date, description); return View(model); } }
In this example, custom exception attributes are used to catch specific exceptions that might be raised by the worker service. In this case, you don’t need to spoil your code with ifs and null checks. (I have nothing against using if statements, but if I can save myself and my peers a few lines of code and still keep code highly readable, well, by all means I do that.)
Important As an attentive reader, you might have noticed that I completely ignored an important point—how to get ahold of an instance of the worker service. And how does the worker service, in turn, get ahold of an instance of the repository? Techniques and tools to inject dependencies in your code is exactly the next topic. (And in the next chapter, I’ll say more about injection points and related techniques in the entire ASP.NET MVC framework.)