Chapter 19. Lightweight controllers – ASP.NET MVC 2 in Action

Chapter 19. Lightweight controllers

This chapter covers

  • Using lightweight controllers to simplify programming
  • Managing common view data without filter attributes
  • Deriving action results to apply common behavior
  • Using an application bus

Do you remember those swollen and unwieldy Page_Load methods in Web Forms? Those methods can quickly grow out of control and stage a revolt against your code base.

Controller actions are dangerous too. Nestled snugly between the model and view, controllers are an easy place to put decision-making code, and they’re often mistaken for a good place to put that logic. And it’s quite convenient, at first. It just takes two lines of code to build a select list in an action method. And adding a filter attribute to the controller is a simple way to manage global data for a master page.

But these techniques don’t scale with greater complexity. Orchestrating a process to find a particular order, authorize it, transmit it to the shipping service, and email a receipt to the user, before redirecting the client to the confirmation page? That’s too much for our controller to handle.

19.1. Why lightweight controllers?

It’s important to focus on keeping controllers lightweight. Over time, controllers tend to accumulate more code, and large controllers that have many responsibilities are hard to maintain. They also become hard to test. When creating controllers, think about long-term maintainability, testability, and a single responsibility.

19.1.1. Maintainability

As code becomes hard to understand, it becomes hard to change; as code becomes hard to change, it becomes a minefield of errors and rework and headaches. Deep technical analysis must be rendered for each seemingly simple enhancement or bug fix, because the developer is unsure what the ramifications of a given change will be.

 

The single responsibility principle (SRP)

The guiding principle behind keeping a class small and focused is the single responsibility principle (SRP). Basically, SRP states that a class should have one and only one responsibility. Another way to look at it is that a class should have only one reason to change. If you find that a class has the potential to be changed for reasons unrelated to its primary task, that means the class is probably doing too much. A common violation of SRP is mixing data access with business logic. For example, a Customer class probably shouldn’t have a Save() method.

SRP is a core concept of good object-oriented design, and its application can help your code become more maintainable. SRP is sometimes referred to as separation of concerns (SoC). You can read more about SRP/SoC in Bob Martin’s excellent article on the subject, “SRP: The Single Responsibility Principle” (http://mng.bz/34TU).

 

Not only that, but bloat makes understanding how to make a change difficult. Without clear responsibilities, a change could potentially happen anywhere. As developers, we don’t want building software to be a guessing game in which we blindly slap logic into action methods. We want to create a system in which software design exists apart from controllers so that we don’t struggle when working with our source code.

19.1.2. Testability

The best way to ensure it’s easy to work with our source code is to practice test-driven development (TDD). When we do TDD, we work with our source code before it exists. Hard-to-test classes, including controllers, are immediately suspect as flawed.

Testing friction—problems writing tests or with test management—is a clear and convincing indicator that the software’s design has room for improvement. Simple, lightweight controllers are easy to test. We’ll discuss TDD in detail in chapter 26.

19.1.3. Focusing on the controller’s responsibility

A quick way to lighten the controller’s load is to remove responsibilities from it. Consider the burdened action shown in listing 19.1.

Listing 19.1. A heavyweight controller

This action is doing a lot of work—it’s incomprehensible at first glance. You can almost count its jobs by the number of if statements. Beyond its appropriate role as director of the storyboard flow of the user interface, this action is deciding if the Order is appropriate for shipping and determining whether to send the User a notification email . Not only is it doing those things, but it’s also deciding how to do them—it’s determining what it means for an Order to be appropriate for shipping and how the notification email should be sent.

Logic like this—domain logic, business logic—should generally not be in a user interface class like a controller. It violates the SRP, obfuscating both the true intention of the domain and the actual duties of the controller, which is redirecting to the proper action. Testing and maintaining an application written like this is difficult.

 

Cyclomatic complexity: source code viscosity

Cyclomatic complexity is a metric we can use to analyze the complexity of code. The more logical paths a method or function presents, the higher its cyclomatic complexity. To fully understand the implication of a particular procedure, each logical path must be evaluated. For example, each simple if statement presents two paths—one when the condition is true, and another when it’s false. Functions with high cyclomatic complexity are more difficult to test and to understand and have been correlated with increased defect rates.

 

A simple refactoring that can ease this situation is called Refactor Architecture by Tiers. It directs the software designer to move processing logic out of the presentation tier into the business tier. You can read more about this technique at http://www.refactoring.com/catalog/refactorArchitectureByTiers.html.

After we move the logic for shipping an order to an OrderShippingService, our action is much simpler, as shown in listing 19.2.

Listing 19.2. A simpler action after refactoring architecture by tiers
public RedirectToRouteResult Ship(int orderId)
{
var status = _orderShippingService.Ship(orderId);
if (status.Successful)
{
return RedirectToAction("Shipped", "Order", new {orderId});
}
return RedirectToAction("NotShipped", "Order", new {orderId});
}

Everything having to do with shipping the order and sending the notification has been moved out of the controller into a new OrderShippingService class. The controller is left with the single responsibility of deciding where to redirect the client. The new class can fetch the Order, get the User, and do all the rest.

But the result of the refactoring is more than just a move. It’s a semantic break that puts the onus of managing these tasks in the right place. This change has resulted in a clean abstraction that our controller can use to represent what it was doing before. Other logical endpoints can reuse the OrderShippingService, such as other controllers or services that participate in the order shipping process. This new abstraction is clear, and it can change internally without affecting the presentation duties of the controller.

Refactoring doesn’t get much simpler than this, but a simple change can result in significantly lower cyclomatic complexity and can ease the testing effort and maintenance burden associated with a complex controller. In the next sections, we’ll look at other ways of simplifying controllers.

19.2. Managing common view data

Complexity can easily sneak into our controllers by way of filter attributes. Those seemingly harmless attributes can encapsulate vast amounts of data access and processing logic.

We often see filter attributes used to provide common view data, but there’s another technique that can provide the same functionality without relying on attributes. Listing 19.3 shows a controller action using an action filter attribute to add a subtitle to ViewData.

Listing 19.3. Applying an action filter to a controller action
[SubtitleData]
public ActionResult About()
{
return View();
}

Whenever the action in listing 19.3 is invoked, the action filter attribute shown in listing 19.4 will execute.

Listing 19.4. A custom action filter that adds data to the ViewData dictionary

The SubtitleDataAttribute enables page subtitles, uses SubtitleBuilder to retrieve the proper subtitle, and places the subtitle in ViewData. Attributes are special classes that don’t afford the developer much control. They require parameters that are CLR constants (such as string literals, numeric literals, and calls to typeof), so our action filter attribute must be responsible for instantiating any helper classes it needs .

 

Dependencies

When a class we’re writing needs help from another class, our class is dependent on that other class. We call those collaborators dependencies.

Managing dependencies is a responsibility in and of itself. A class is doing too much (and violating the SRP) when it’s responsible for managing its dependencies along with its own behavior.

One common technique to remove this burden is constructor injection—providing the dependency to our class by passing (or injecting) it as a constructor argument. This way, callers know exactly what our class depends on before they can instantiate it. We can also provide dummy implementations of the dependency during testing. The end result is a number of classes with single, focused responsibilities. When applied correctly, this technique transforms our application from a procedural uphill walk to a tightly choreographed ballet of objects.

 

Because SubtitleDataAttribute is responsible for instantiating its helpers in listing 19.4, it has a compile-time coupling to SubtitleBuilder (evidenced by the new keyword). Another drawback to action filter attributes is the work involved in applying them—you must remember to apply them to each action on which they’re needed. One solution to this could be to create a layer supertype controller (a base controller) and apply the filter attribute to that. Then all controllers that wanted the action filter’s behavior could simply derive from that layer supertype.

The problem with relying on inheritance to solve this problem is that it couples our controller to the base type. Inheritance is a compiled condition, which makes runtime changes difficult. And even compile-time changes are hard: if the layer supertype changes, all derivations must change. In cases like these, we favor composition over inheritance.

By extending the default ControllerActionInvoker (mentioned briefly in chapter 9) we can compose action filters at runtime without using attributes on actions, controllers, or a layer supertype controller. In listing 19.5 we extend ControllerActionInvoker to allow us to apply action filters without attributes.

Listing 19.5. Extending ControllerActionInvoker to provide custom action filters

The controller action invoker will take an array of custom action filters as a constructor parameter and apply each of them to the action when it’s invoked .

 

Note

Controllers are instantiated by a special class called DefaultController-Factory, and it’s possible to derive from this class to create our own controller factory. A custom controller factory allows ASP.NET MVC 2 developers to customize the instantiation of controllers.

 

In listing 19.6 we set our new action invoker as the default for each controller when it’s created in the controller factory.

Listing 19.6. Using our custom action invoker with a custom controller factory

We need a factory function to provide an instance for a given type , but because the specific controller type we need won’t be known until runtime, we can’t pass the controller as a dependency to the constructor of our controller factory. Even so, we’ll provide a factory that knows about all the controller types in our system.

 

Inversion of Control

We’ve seen that a class’s dependencies should be managed from outside and not by the dependent class itself. As an application grows, its dependency graph—the tree of objects that depend on each other—can reach a level of complexity that isn’t reasonable for the developer to manually maintain.

Fortunately, utility libraries exist that use reflection, conventions, and configuration to keep track of dependencies in our objects. We can use these libraries to instantiate classes with their entire dependency graphs in place. Doing this, and relinquishing the responsibility of managing our dependencies, is inversion of control (IoC).

Several popular inversion of control libraries are available to .NET developers. We recommend these three:

 

To leverage an IoC tool such as StructureMap in our controller factory, we have to set the factory function to the tool’s instantiating function. This should happen when the application is first started, and we do this in listing 19.7.

Listing 19.7. Setting the factory function to use the IoC tool

In listing 19.7 we first set the controller factory’s static factory function to the IoC tool’s automatic factory method. To use our custom controller factory, we then call the SetControllerFactory method on the ControllerBuilder to replace the default controller factory with our own . Now our controller factory will use our IoC tool to instantiate controllers, our custom invoker, and any action filters.

Finally, we use a special interface and abstract base class to denote the action filters we want to apply. This is shown in listing 19.8.

Listing 19.8. An interface to define our custom filter

Our interface, IAutoActionFilter, implements IActionFilter . BaseAutoAction-Filter implements IAutoActionFilter and provides implementations of its methods that do nothing . These no-op methods will allow further derivations to override only the method they wish to use without having to implement the other method of IActionFilter. It’s a handy shortcut.

In listing 19.9 we get to implement our custom filter, which will replace the attribute-based one in listing 19.4.

Listing 19.9. Our custom, non-attribute-based action filter

In this version of the action filter, we can take the dependency as a constructor parameter (supplied automatically by our IoC tool) . Finally—a clean action filter: testable, lightweight, with managed dependencies and no clunky attributes.

This seems like a lot of work, but once you get the concept in place, adding filter attributes is simple: just derive from BaseAutoActionFilter.

In the next section, we’ll eliminate another pesky attribute from our actions.

19.3. Deriving action results

One possible use for action filter attributes is to perform postprocessing on the View-Data provided by the controller to the view.

In the example code for chapter 18, we had an action filter attribute that used AutoMapper to translate source types to destination types. This filter attribute is shown in listing 19.10.

Listing 19.10. An action filter that uses AutoMapper

By decorating an action method with this attribute, we direct AutoMapper to transform ViewData.Model. This attribute provides critical functionality—it’s quite easy to forget to apply a custom attribute, and our views won’t work if the attribute is missing.

An alternative approach is to return a custom action result that encapsulates this logic rather than using a filter.

Instead of using a filter attribute, what if we derived from ViewResult and created a class that contains the logic of applying an AutoMapper map to ViewData.Model before regular execution? Then we could not only verify that the correct model was initially set, but also verify that AutoMapper will map to the correct destination type. You can create many different action results like this; the key is to expose testable state, which, in this case, is the destination type to which we’ll map.

AutoMappedViewResult, shown in listing 19.11, is created this way.

Listing 19.11. An action result that applies AutoMapper to the model

All this class does is apply a mapping function (defined as a delegate) , which we’ll set to be AutoMapper’s mapping function, to ViewData.Model before continuing on with the regular ViewResult work . We also make sure to expose the destination type so that we can verify it in unit tests. Unlike when using the attribute, we can know for sure that the action is mapping to the correct destination type.

The use of the AutoMappedViewResult is shown in listing 19.12, with a helper function, we can easily use this result in our actions.

Listing 19.12. Using AutoMappedViewResult in an action

Returning the right result is straightforward—it’s like the normal ViewResult, but we have to supply the destination type, CustomerInfo (which is our presentation model) . Our helper function does the heavy ViewData and TempData lifting.

In the next section we’ll lighten our controller even further using an application bus and a simple abstraction around a common controller theme: controlling storyboard flow for success and failure.

19.4. Using an application bus

In large distributed systems, eliminating dependencies isn’t just a good idea, it’s required. Architects designing these systems have learned that they must create a myriad of atomic services that can be reused and composed by several applications, just like application architects design classes to be reused and composed inside programs. But unlike classes inside programs, services shouldn’t be coupled to physical network locations or to specific programming platforms. When a system is composed of services spread across a large network, rather than a shared memory space, extreme flexibility in deployment and configuration is necessary.

The metaphor that best describes the way many distributed systems work is sending and receiving messages. One application will send a command message to a bus. The bus is responsible for, among other things, routing the message to ensure it’s handled by the appropriate recipient. Services share a message schema, but their implementations can vary widely, even as far as being developed on different platforms. As long as the recipient understands the message, the services can work together. They don’t need to depend on each other, just on the bus. Such systems are described as being loosely coupled.

This is a gross oversimplification of message-based, service-oriented architectures, but these distributed systems can provide insight into better ways of designing in-process applications.

What if, instead of depending on an IOrderShippingService, our controller in listing 19.2 sent a message to a bus, as shown in listing 19.13?

Listing 19.13. Sending a message on an application bus

The controller in listing 19.13 doesn’t call a method on IOrderShippingService, but instead sends a ShipOrderMessage to an application bus . The user interface here is completely decoupled from the specific processor of the command. The entire order-shipping process could change, or the responsible interface could change, and our controller would continue working correctly without modification.

The bus, on the other hand, needs a way to associate messages with their specific handlers. A distributed system would need something pretty fancy to route messages to different networked endpoints, but in-process applications can harness the type system and use it as a registry. Consider the simple IHandler<T> interface in listing 19.14.

Listing 19.14. IHandler<T> indicates a type that can handle a message type
public interface IHandler<T>
{
Result Handle(T message);
}

Implementers of this interface declare they can handle a specific message type. When the bus receives a ShipOrderMessage, it can look for an implementation of IHandler<ShipOrderMessage> and, using an IoC tool, instantiate the implementation and call Handle on it, passing in the message. (An example of this is included in the sample code for this chapter.)

For our command message example, we’re using a feature of MvcContrib called the command processor. Listing 19.15 shows a handler for the ShipOrder message. The command processor’s IHandler capability is in the Command<T> base class.

Listing 19.15. Concrete message handler
public class ShipOrderHandler : Command<ShipOrder>
{
readonly IRepository _repository;

public ShipOrderHandler(IRepository repository)
{
_repository = repository;
}

protected override ReturnValue Execute(ShipOrder commandMessage)
{
var order = _repository.GetById<Order>(commandMessage.OrderId);

order.Ship();

_repository.Save(order);

return new ReturnValue().SetValue(order);
}
}

MvcContrib’s command processor knows how to locate handlers, so inheriting from Command<ShipOrder> is all it takes to register the class as a handler for that message. The actual work is done in the Execute method, where the ShipOrderHandler can use its own dependencies as needed.

Although it’s useful to decouple our business logic code from our user interface, this action should only be taken on applications that are medium to large in size. Small applications have no need for this type of separation. Furthermore, this technique hasn’t necessarily simplified our controller. Our cyclomatic complexity remains—we’d still need to test what happens should the result succeed and should it fail.

That’s another abstraction to be extracted: the concept of success or failure can be baked into our bus architecture. We can set up an action result (CommandResult) to handle sending the message, and that action result can also check the result of the message dispatch and execute a nested action result function upon success or failure. But the controller is still responsible for choosing the action results for success and for failure, continuing in its role as the storyboard director.

The complete action result is included in the sample code for this chapter, but you can see a simplified CommandResult in listing 19.16.

Listing 19.16. A command-executing action result

What’s not shown in this listing is the constructor that takes functions that return action results for the success and failure cases. These action results end up as the Success and Failure properties. Otherwise the semantics look the same as our controller in listing 19.13, but armed with this abstraction we can avoid repetitive code in each controller.

Let’s take a final look at our order-shipping action, now using a special helper method to craft the CommandResult, in listing 19.17.

Listing 19.17. Using CommandResult in an action

In our new Ship action, we call a helper method with arguments for the message , the success result , and the failure result . Because we’re writing declarative code to define the message and action results, writing and testing controllers built with these techniques is simple. To test them, all we need to do is check the CommandResult's message and success and failure action results, verifying that the declared results are as expected. The test for this action is included in the sample code for this chapter.

Finally, as a side benefit to sending commands through an application bus, we’ve established a tiny logical pathway through which all business transactions move. We can take advantage of this pathway to set up a gate for stronger validation, auditing, and other cross-cutting concerns.

19.5. Summary

In this chapter, we applied a simple refactoring to remove business logic from the controller and move it into a useful abstraction. By properly managing our dependencies and adhering to object-oriented principles, we’re better equipped to craft well-designed software with functionality that can be easily verified.

We extended ControllerActionInvoker and DefaultControllerFactory to manage action filters. Deriving from ActionResult allowed us to avoid repetitive code while not relying on filter attributes. Finally, we leveraged an application bus to write simple, declarative controller actions.

In the next chapter, you’ll learn the importance and mechanics of creating fullsystem tests for ASP.NET MVC applications.