Chapter 4. Controller basics

This chapter covers

  • Understanding the controller anatomy
  • Storyboarding an application
  • Mapping the presentation model
  • Using input from the browser
  • Passing view metadata
  • Testing the controller

The focus of the Model-View-Controller pattern is the controller. With this pattern, every request is handled by a controller and rendered by a view. Without the controller, presentation and business logic would move to the view, as we’ve seen with Web Forms.

With the ASP.NET MVC Framework, every request routes to a controller, which is simply a class that implements the IController interface. Microsoft provides the base class System.Web.Mvc.Controller to make creating a controller easy. Which controller base class you choose isn’t crucial because most request processing goes into executing the ActionResult, which is the type that each action returns.

An action is a method on the controller class that handles a particular request. This method can take zero or many parameters, but by the time the action method finishes executing, there ought to be one or many objects ready to be sent to the view, and the name of the view should be selected if the view doesn’t follow the convention of having the same name as the action. Beyond that, the developer is in complete control when implementing a controller and its actions.

This chapter will explore controllers that use many actions and inherit from the System.Web.Mvc.Controller base class. Chapter 9 will cover advanced topics regarding controllers. Let’s dive into controller anatomy.

4.1. The anatomy of a controller

At its most basic level, a controller is simply a class that implements the IController interface. But most of the time your controllers will inherit from the System.Web. Mvc.Controller class rather than directly implementing IController.

Controller classes contain one or more methods that act as actions. An action method is used to serve a single HTTP request; each action can take zero or many parameters and usually returns an ActionResult. Parameters are passed to the action method using the model binding infrastructure. By making use of these binders to do the heavy lifting, the controller action is free to focus on controlling application logic rather than translating user input to concrete classes.

A well-written action should have a clear purpose and a single responsibility. That responsibility is to accept input from the browser and coordinate the flow of the application. Along the way, the action should rely on application services to perform tasks such as executing business logic, performing data access, or file I/O.

Listing 4.1 shows a simple controller with a single action. This is a trivial example—we’ll tackle more complex scenarios later.

Listing 4.1. SimpleController, which populates ViewData and renders a view
using System.Web.Mvc;

namespace MvcInAction.Controllers
{
public class SimpleController : Controller
{
public ActionResult Hello()
{
ViewData.Add("greeting", "Hello Readers!");
return View();
}
}
}

Creating an action begins by ensuring that the method is public and returns ActionResult. If the method isn’t public, it won’t be called. Once that’s set up, we can push some objects into ViewData and call the View() method with the name of the view that should render. That’s the meat and potatoes of what it means to be an action method.

Now that we’ve defined the makeup of a controller, we’ll look at how a controller implements an application’s storyboard.

 

Note

System.Web.Mvc.Controller is only one option you can choose as a base class for your controllers. It’s often appropriate to create your own layer supertype for all your controllers. This type can inherit from System.Web. Mvc.Controller, implement IController, or derive from any other controller base class.

 

4.2. Storyboarding an application

Action methods exist to coordinate the presentation for a screen or page. This coordination is the glue that holds together the storyboard of the application.

Imagine drawing the flow of application screens on a whiteboard. Each place where a user can provide input, through a form or a click of a button, there are at least two possible outcomes:

  • The input could be correct, satisfying all data type validation and business rules. In this case, the request will be fully processed, and the controller will redirect to the next page.
  • The input could have an error, whether because an invalid date was entered, or the input breaks a business rule. In this situation, the controller needs to render the original page again with the appropriate error messages.

There are some great benefits to implementing controller actions like a storyboard. Actions tend to become smaller and focused, with business logic moving out of the action and into supporting services. As a result, the actions are less complex and easier to test. A lean action should result in two possible outcomes: happy path (a successfully processed request) or an alternate path. If an action starts branching to handle multiple alternate paths, this is a sign that the action method is handling too much, and some effort should be put into designing the storyboard of the application.

Figure 4.1 shows a sample storyboard illustrating how a user would log into a web application and then view some customized content. The action that handles the login form post would decide to redirect the user to the homepage or to re-render the login form with a message saying the user needs to enter a correct username and password combination. Although this seems like an obvious path that needs to be developed, it’s easy to overlook the alternate paths when you don’t storyboard them. This technique helps developers and designers communicate how the screens should work before a single line of code is written.

Figure 4.1. Storyboard of an application’s user interactions

 

The happy path

ASP.NET MVC developers (and developers using other convention-centric frameworks) will often mention the happy path. This refers to the notion that following the MVC Framework’s conventions will make the developer’s experience both enjoyable and relatively painless. The MVC Framework doesn’t require you to adhere to any particular convention, but the further you stray from the happy path, the greater the effort required by the developer. The MvcContrib project (http://mvccontrib.org) provides additional components for the ASP.NET MVC framework that enhance the path, and you’ll certainly find ways to enhance it in your system. Staying on the path gains you a great deal in consistency.

 

4.3. Transforming a model to a view model

A common role for an action is to do the work necessary to mold a domain model into a presentation model for a view, JSON, or other output type. This type of action handles a GET request to the web server and in its simplest form returns HTML to the browser.

For example, the action in listing 4.2 retrieves a collection of user domain model objects and transforms the objects into a presentation model of type UserDisplay[].

Listing 4.2. An action that prepares a presentation model for a view

The Index action relies on a UserRepository class handling all the communication with the database and turning the native database objects into the User collection. Then the action uses Language Integrated Query (LINQ) to minimize the noise in performing this type of transformation.

The last line of the action returns the presentation model to a View helper method, which returns a ViewResult to the MVC Framework. Because a view name wasn’t specified, the framework uses a convention and looks for a view that matches the action name. In this case it would look for a view called Index.

4.4. Accepting input

An action method receives input from the web browser via its method arguments. The controller uses the model binder feature to convert values from web requests into CLR objects that match the names of parameters for the action method. The internals of how this works are covered in chapter 14. For now, it’s important to understand that a convention is used to match form values by their name to the parameter name of an action.

Listing 4.3 shows how an action method can accept values from the HTTP request as parameters.

Listing 4.3. A value object bound to an action from a route value
[HttpGet]
public ActionResult Edit(int Id)
{
User user = UserRepository.GetById(Id);
....
}

The code in listing 4.3 shows a value object being bound from a portion of the URL. The URL containing an Id with the value 4 would be http://localhost/User/Edit/4. The model binder automatically binds this value to the action’s parameter. The action can then use the value to perform its work, as in the GetById method, without having to pull values out of the HttpContext. If an action method directly accesses the Request property to extract user input, this is a sign that the action has too many responsibilities. Actions need to be focused on the storyboard instead of translating input data. Listing 4.4 demonstrates an action method that accepts a complex type as a parameter. ASP.NET MVC will automatically convert the form values into CLR objects by matching on the property names.

Listing 4.4. A complex object bound to an action from a form post
public class UserInput
{
[Required]
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

[HttpPost]
public ActionResult Edit(UserInput input)
{
...
}

In listing 4.4, the form post data is converted into a UserInput object. The Edit action method can accept the complex type as a parameter.

 

Note

Along with the MVC Framework, Microsoft has wrapped some of the ASP.NET code and provided abstract classes to some of the key APIs, such as HttpResponseBase, HttpRequestBase, and most importantly, HttpContextBase. A Google search will reveal how many people have had trouble testing against HttpContext because of its sealed and static members. Providing abstract classes for these key APIs loosens the coupling to them, increasing testability.

 

The resolution of action parameters used in conjunction with model binders makes it easy to craft an action method that takes in information from a web request. We can use the form values, route values, and query string to make the action behavior more dynamic. Again, notice how effortless it is to consume this request data. We don’t have to write any repetitive code to pull these values in. Rather, the ASP.NET MVC Framework finds the correct input value and maps it to the appropriate action parameter.

4.4.1. Handling the successful storyboard path in an action

Now that you understand how actions accept user input, let’s move on to implementing the application’s storyboard.

In the case of accepting user input from a form post, the decision to follow the success or alternate path can be made by data type validation. When the criteria for success are met, the action can coordinate the success activities and control the flow to the next screen or action.

Listing 4.5 shows the implementation of the successful path of the Edit action.

Listing 4.5. The success path in an action

Listing 4.5 shows that the success path is determined by the call to the Model-State.IsValid property . The model binder translates the form post data into the UserInput object and also populates the ModelState object with metadata about the data type validation of the object. When all of the validation passes, the IsValid property is true. In this case, the UpdateUserFromInput method is called.

The UpdateUserFromInput method updates the User object from the input model. Once the update occurs, a success message is put into TempData. TempData allows transient data to be passed between two consecutive requests to the web server. After the user has been redirected to the next action, the contents of TempData will be available to display to the user.

The last line of code in the success path returns a RedirectToRouteResult in order to redirect the user back to the Index action. This approach keeps the action simple and concise.

 

Note

In this book, we focus on complex, long-lasting web applications. In keeping with that, we don’t make compromises to optimize the speed of writing the application. Software engineering is full of trade-offs, and software construction techniques are no exception. If you need a small web application, you can probably get away with putting all the logic in the controller action, but realize that you’re trading off long-term maintainability for short-term coding speed. If the application will have a long life, this is a bad trade-off. The examples in this book are factored for long life and easy maintenance.

 

4.4.2. Using the Post-Redirect-Get pattern

The code in listing 4.5 demonstrates a pattern called Post-Redirect-Get (PRG), first published in 2003 by Michael Jouravlev. You saw this briefly in chapter 1. The pattern is used to prevent some common problems that occur after a user has posted a form to a web server. If a view is rendered directly from a form post, the user may attempt to refresh the browser or bookmark the page, which can cause double form submissions or other erroneous behavior. By redirecting after a form post to a URL that uses a GET request, the problem is eliminated. This makes the user experience consistent and deterministic. This pattern is often recommended when handling form posts.

The screenshots in figures 4.2 and 4.3 demonstrate a form used to collect user input for an edit action. The success path of the action redirects to the Index page, and the page pulls the success message from TempData. The ASP.NET MVC Framework provides the components, like TempData and the RedirectToAction method, to support the PRG pattern.

Figure 4.2. The user edit view

Figure 4.3. The redirected action showing a message from TempData

4.4.3. Handling the failure processing of the action input

Continuing the example of the Edit action, we’ll now look at the alternate path that’s followed when the call to ModelState.IsValid returns false.

If the Username field is left blank when the form is posted to our controller, the automatic validation will fail because the Username property on the UserInput object is decorated with a RequiredAttribute (as shown in listing 4.6). In this case, the model binding infrastructure will automatically add an error message to the Model-State collection, which will cause the IsValid property to return false.

Listing 4.6. The alternate path

Listing 4.6 shows that when the IsValid property returns false (indicating that there’s at least one validation error), the UserInput instance is passed to the View method so that the error message can be rendered on the screen, as shown in figure 4.4.

Figure 4.4. The alternate path showing validation messages

The code for handling the alternate path in the storyboard is quite straightforward. That’s by design. But don’t let yourself be fooled by this simplicity; it’s still important to unit-test your controller actions.

4.5. Testing controllers

The focus of this section is testing controllers. Of the different types of automated testing, we’re concerned with only one type at this point: unit testing. Unit tests are small, scripted tests, usually written in the same language as the production code. They set up and exercise a single component’s function.

Unit tests run quickly because they don’t call out-of-process. In a unit test, dependencies are simulated, so the only production code running is the controller code. For this to be possible, the controllers have to be well designed. A well-designed controller

  • Is loosely coupled with its dependencies
  • Uses dependencies but isn’t in charge of locating or creating those dependencies
  • Has clear responsibilities and only handles logic relevant to serving a web request

A well-designed controller doesn’t do file I/O, database access, web service calls, or thread management. The controller may very well call a dependency that performs these functions, but the controller itself should be responsible only for interacting with the dependency, not for performing the fine-grained work. This is important to testing, because good design and testing go hand in hand. It’s difficult to test poorly designed code.

 

Note

Writing automated tests for all code in a code base is a best practice. It provides great feedback when the test suite is run multiple times per day. If you’re not doing it now, you should start immediately. Several popular, high-quality frameworks for automated testing are available, including NUnit and MbUnit. As of this writing, NBehave, MSTest, and xUnit are also available, but they aren’t as widely adopted as NUnit or MbUnit. All are free (with the exception of MSTest, which requires the purchase of Visual Studio) and they simplify testing code.

 

In this section, we’ll walk through testing a viewless RedirectController for an application that schedules and manages small conferences.

Part of the application’s functionality is to show upcoming conferences as well as the conferences that are immediately next on the schedule. When navigating to http://MyConference.com/next, the application should find the next conference and redirect to the URL that will show details of that conference. This will be our example as we explore how to test our ASP.NET MVC code.

4.5.1. Testing the RedirectController

The RedirectController must identify the next conference, ask for a redirect to the action that can take it from there, and issue the redirect so that the conference can be displayed on the screen. The action method returns a RedirectToRouteResult instance (a subclass of ActionResult) that contains public properties on which assertions can be performed in a test. The RedirectToRouteResult also contains an Execute method that’s used to perform the redirect.

In listing 4.7, we set up a unit test for this code along with fake implementations of the dependencies on which the RedirectController relies.

Listing 4.7. Testing that we redirect to the correct URL

Notice that most of the code listing is test-double code and not the RedirectController test itself. Test doubles are classes that stand in for object dependencies, simulating collaborators so that we can control the test environment. If you’d like more information on test doubles, Roy Osherove has written a very nice book called The Art of Unit Testing.

We have to stub out an IConferenceRepository implementation because calling that interface inside the controller action provides the next conference. How it performs that data query is beyond the scope of this chapter, and it’s irrelevant to the controller. (You can briefly skip ahead to chapter 23 if you’re curious about how to write data access code when using ASP.NET MVC.)

You might think that this is too complex for a single unit test. We’ll see shortly how to reduce the amount of code in the unit-test fixture. Reducing code starts with making dependencies explicit.

4.5.2. Making dependencies explicit

There are only three real lines of code in the RedirectController. Controllers should all be thin, and this is a good example. Only logic related to presenting information to the user belongs in the controller. In this case, the user experiences a redirect; the logic for finding the correct Conference object is a data access issue and doesn’t belong in the controller, so it’s factored into a repository object. The controller demonstrates proper separation of concerns, and it’s easily unit tested because it’s only involved with a single responsibility. We’re able to simulate dependencies using test doubles.

In figure 4.5, you see the unit test passing because we were able to properly simulate this controller’s dependencies and verify that, given the dependencies, the controller will do its job correctly.

Figure 4.5. A controller unit testing passing

4.5.3. Using test doubles, such as stubs and mocks

As far as the controller is concerned, its caller is passing in an implementation of the necessary interface. This interface is a dependency, and the controller makes use of it in an action method. How the dependency is passed in and what class implements the interface are irrelevant. At runtime, a production class will be passed into the controller, but during unit testing, we use stand-in objects, or test doubles, to simulate the behavior of the dependencies.

There are different types of simulated objects, and some of the definitions overlap. In short, the terms fake and test double are generic terms for a nonproduction implementation of an interface or derived class that stands in for the real thing. Stubs are classes that return hard-coded information when they’re called. The ConferenceRepositoryStub shown in listing 4.7 is an example of a stub. A mock is a recorder that remembers arguments passed to it and other details (depending on how it’s programmed) so that we can assert the behavior of the caller later on.

 

Note

Entire books have been written about testing and how to separate code for testing using fakes, stubs, and mocks. If you’re interested in exploring the subject further, we highly recommend reading Michael Feathers’ book, Working Effectively with Legacy Code.

 

One downside to using hand-coded stubs and mocks is that you need to write many lines of code to satisfy an interface implementation that may have six methods. This isn’t the only option, however. A favorite library for automating the creation of mocks and stubs is Rhino Mocks written by Oren Eini (www.ayende.com/projects/rhino-mocks.aspx).

Rhino Mocks drastically reduces the number of lines of code in a unit-test fixture by streamlining the creation of test doubles. If code is designed so that all dependencies are injected into the constructor, as shown in listing 4.8, unit testing becomes easy and soon becomes a repetitive pattern of faking dependencies and writing assertions. Over time, if you employ this technique, you’ll see a marked improvement in the quality of your code.

Listing 4.8. Dependency defined in the constructor
public RedirectController(IConferenceRepository conferenceRepository)
{
_repository = conferenceRepository;
}

Remember how many lines of code we wrote for a stubbed implementation of ICon-ferenceRepository in listing 4.7? Now, examine listing 4.9 and notice how short this code listing is in comparison.

Listing 4.9. Using Rhino Mocks to streamline code for fakes

Rhino Mocks supports setting up dynamic stubs as well as dynamic mocks. The lines with Stub(...) are used so that a stubbing method or property always returns a given object. By using the Rhino Mocks library, we can provide dependency simulations quickly for easy unit testing.

A dynamic mocking library like Rhino Mocks isn’t appropriate in every unit-testing scenario. The usage in listing 4.9 is the bread-and-butter scenario that reduces the amount of setup code inside unit tests. More complex needs can quickly stress the Rhino Mocks API and become hard to read. Although Rhino Mocks supports almost everything you could want to do, the readability of tests is important to maintain. When you need to assert method parameters of dependencies or do something special, don’t be afraid to push Rhino Mocks to the side and leverage a concrete mock or stub to keep the test readable.

4.5.4. Elements of a good controller unit test

This chapter specifically addresses writing unit tests for controller classes. We focus heavily on testing controller classes because test-driving the controllers ensures they’re well designed. It’s unlikely you’ll end up with a bad design if you’re practicing test-driven development. Poorly designed code tends to be untestable, so observable untestability is an objective gauge of poorly designed code.

 

Note

If you’re just getting started with unit testing, you might run into some common pitfalls. This chapter isn’t meant to be an entire course on testing. There are already entire books on that, and we again recommend reading Roy Osherove’s The Art of Unit Testing.

 

A good controller unit test runs fast. We’re talking 2,000 unit tests all running within 10 seconds. How is that possible? .NET code runs quickly, so unit tests wait only for the processor and RAM. Unit tests run code only within the current application domain, so we don’t have to deal with crossing application domain or process boundaries.

We can quickly sabotage this fast test performance by breaking a fundamental rule of unit testing: allowing out-of-process calls. Out-of-process calls are orders of magnitude slower than in-process calls, and test performance will suffer. Ensure that you’re faking out all controller dependencies, and the test will continue to run quickly.

We also want our unit tests to be self-sufficient and isolated. Resist the temptation to refactor repeated code in unit tests and create only test helpers for the cross-cutting concerns. The DRY principle (Don’t Repeat Yourself) doesn’t apply to test code as much as to production code. Rather, keeping test cases isolated and self-contained reduces the change burden when the production code needs to change. Being able to scan a unit test and see the context all in one method makes them more readable.

The tests should also be repeatable. That means no shared global variables for the test result state, and no shared state between tests in general. Keep unit tests isolated in every way, and they’ll be repeatable, order-independent, and stable.

Pay attention to pain—if tests become painful and time consuming to maintain, there’s something wrong. Correctly managed design and tests enable sustained speed of development, whereas poor testing techniques cause development to slow down to the point where testing is abandoned. At that point, it’s back to painstaking, time-intensive manual testing. If you start to think that you could move faster without writing the tests, look for technique errors or bad design in the production code. Get a peer to review the code. Tests should enable development, not slow it down.

4.6. Summary

In the ASP.NET MVC Framework, logic is separated into controllers and actions. Controllers are the center of an MVC presentation layer—they handle all the coordination between the model and the view. Actions can accept parameters and can call for the rendering of a view. Actions aren’t required to have a view, but they commonly do.

When using a view, we have several methods for passing view data, and the preferred method is to use an object that suits our needs. Keep in mind that the default way of adding objects to the view data dictionary might not be best for your situation.

Action parameters are matched by model binders. This leaves the action methods free to concentrate on implementing an application’s storyboard. By focusing on the happy path and the alternate path, you’ll find it easy to spot actions that are taking on too many branches of logic.

Controllers have the potential to become just as large and convoluted as Page_Load methods in Web Forms. But a test-driven development approach and a disciplined separation of concerns can ensure the maintainability of your presentation layer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.208.97