Chapter 9. Controllers and Actions

Each time a request comes in to your ASP.NET MVC application, it's dealt with by a controller. The controller is the boss: it can do anything it likes to service that request. It can issue any set of commands to the underlying model tier or database, and it can choose to render any view back to the visitor. It's a .NET class into which you can add any logic needed to handle the request.

In this chapter, you'll get familiar with all the most frequently used capabilities of controllers. We'll start with a quick discussion of the relevant architectural principles, and then look at your options for receiving input and producing output, and how it all fits neatly with unit testing. This knowledge will prepare you for Chapter 10, in which you'll dig much deeper into the framework's internals and learn various ways to customize how your controllers operate.

An Overview

Let's recap exactly what role controllers play in MVC architecture. MVC is all about keeping things simple and organized via separation of concerns. In particular, MVC aims to keep separate three main areas of responsibility:

  • Business or domain logic and data storage (model)

  • Application logic (controller)

  • Presentation logic (view)

This particular arrangement is chosen because it works very well for the kind of business applications that most of us are building today.

Controllers are responsible for application logic, which includes receiving user input, issuing commands to and retrieving data from the domain model, and moving the user around between different UIs. You can think of controllers as a bridge between the Web and your domain model, since the whole purpose of your application is to let end users interact with your domain model.

Domain model logic—the processes and rules that represent your business—is a separate concern, so don't mix model logic into your controllers. If you do, you'll lose track of which code is supposed to model the true reality of your business, and which is just the design of the web application feature you're building today. You might get away with that in a small application, but to scale up in complexity, separation of concerns is the key.

Comparisons with ASP.NET Web Forms

There are some similarities between ASP.NET MVC's controllers and the ASPX pages in traditional Web Forms. For example, both are the point of interaction with the end user, and both hold application logic. In other ways, they are conceptually quite different—for example:

You can't separate a Web Forms ASPX page from its code-behind class—the two only work together, cooperating to implement both application logic and presentation logic (e.g., when data-binding), both being concerned with every single button and label. But ASP.NET MVC controllers are different: they are cleanly separated from any particular UI (i.e., view), and are abstract representations of a set of user interactions, purely holding application logic. This abstraction helps you to keep controller code simple, so your application logic stays easier to understand and maintain in isolation.

Web Forms ASPX pages (and their code-behind classes) have a one-to-one association with a particular UI screen. In ASP.NET MVC, a controller isn't tied to a particular view, so it can deal with a request by returning any one of several different UIs—whatever is required by your application logic.

Of course, the real test of the MVC Framework is how well it actually helps you to get your job done and build great software. Let's now explore the technical details, considering exactly how controllers are implemented and what you can do with one.

All Controllers Implement IController

In ASP.NET MVC, controllers are .NET classes. The key requirement on them is that they must implement the IController interface. It's not much to ask—here's the full interface definition:

public interface IController
{
    void Execute(RequestContext requestContext);
}

The "hello world" controller example is therefore

public class HelloWorldController : IController
{
    public void Execute(RequestContext requestContext)
    {
        requestContext.HttpContext.Response.Write("Hello, world!");
    }
}

If your routing configuration includes the default Route entry (i.e., the one matching {controller}/{action}/{id}), then you can invoke this controller by starting up your application (press F5) and then visiting /HelloWorld, as shown in Figure 9-1.

Output from HelloWorldController

Figure 9-1. Output from HelloWorldController

This is hardly impressive, but of course you could put any application logic into that Execute() method.

The Controller Base Class

In practice, you'll very rarely implement IController directly or write an Execute() method. That's because the MVC Framework comes with a standard base class for controllers, System.Web.Mvc.Controller (which implements IController on your behalf). This is much more powerful than a bare-metal IController—it introduces the following facilities:

  • Action methods: Your controller's behavior is partitioned into multiple methods (instead of having just one single Execute() method). Each action method is exposed on a different URL, and is invoked with parameters extracted from the incoming request.

  • Action results: You have the option to return an object describing the intended result of an action (e.g., rendering a view, or redirecting to a different URL or action method), which is then carried out on your behalf. The separation between specifying results and executing them simplifies unit testing considerably.

  • Filters: You can encapsulate reusable behaviors (e.g., authentication or output caching) as filters, and then tag each behavior onto one or more controllers or action methods by putting an [Attribute] in your source code.

This chapter and the next chapter cover all of these features in detail. Of course, you've already seen and worked with many controllers and action methods in earlier chapters, but to illustrate the preceding points, consider this:

[OutputCache(Duration=600, VaryByParam="*")]
public class DemoController : Controller
{
    public ViewResult ShowGreeting()
    {
        ViewData["Greeting"] = "Hello, world!";
        return View("MyView");
    }
}

This simple controller class, DemoController, makes use of all three features mentioned previously.

  • Since it's derived from the standard Controller base class, all its public methods are action methods, so they can be invoked from the Web. The URL for each action method is determined by your routing configuration. With the default routing configuration, you can invoke ShowGreeting() by requesting /Demo/ShowGreeting.

  • ShowGreeting() generates and returns an action result object by calling View(). This particular ViewResult object instructs the framework to render the view template stored at /Views/Demo/MyView.aspx, supplying it with values from the ViewData collection. The view will merge those values into its template, producing and delivering a finished page of HTML.

  • It has a filter attribute, [OutputCache]. This caches and reuses the controller's output for a specified duration (in this example, 600 seconds, or 10 minutes). Since the attribute is attached to the DemoController class itself, it applies to all action methods on DemoController. Alternatively, you can attach filters to individual action methods, as you'll learn later in the chapter.

Note

When you create a controller class by right-clicking your project name or the /Controllers folder and choosing Add

The Controller Base Class

Besides System.Web.Mvc.Controller, the MVC Framework also ships with one other standard base class for controllers, System.Web.Mvc.AsyncController, which inherits from System.Web.Mvc.Controller. Not surprisingly, by inheriting from this subclass you can enable asynchronous request handling, which in some (relatively uncommon) scenarios can provide significant performance benefits. You'll learn more about this in the next chapter.

As with so many programming technologies, controller code tends to follow a basic pattern of input

The Controller Base Class

Receiving Input

Controllers frequently need to access incoming data, such as query string values, form values, and parameters parsed from the incoming URL by the routing system. There are three main ways to access that data. You can extract it from a set of context objects, you can have the data passed as parameters to your action method, or you can directly invoke the framework's model binding feature. We'll now consider each of these techniques.

Getting Data from Context Objects

The most direct way to get hold of incoming data is to fetch it yourself. When your controllers are derived from the framework's Controller base class, you can use its properties, including Request, Response, RouteData, HttpContext, and Server, to access GET and POST values, HTTP headers, cookie information, and basically everything else that the framework knows about the request.[56]

An action method can retrieve data from many sources—for example:

public ActionResult RenameProduct()
{
    // Access various properties from context objects
    string userName = User.Identity.Name;
    string serverName = Server.MachineName;
    string clientIP = Request.UserHostAddress;
    DateTime dateStamp = HttpContext.Timestamp;
    AuditRequest(userName, serverName, clientIP, dateStamp, "Renaming product");

    // Retrieve posted data from Request.Form
    string oldProductName = Request.Form["OldName"];
    string newProductName = Request.Form["NewName"];
    bool result = AttemptProductRename(oldProductName, newProductName);

    ViewData["RenameResult"] = result;
    return View("ProductRenamed");
}

The most commonly used properties include those shown in Table 9-1.

Table 9-1. Commonly Used Context Objects

Property

Type

Description

Request.QueryString

NameValueCollection

GET variables sent with this request

Request.Form

NameValueCollection

POST variables sent with this request

Request.Cookies

HttpCookieCollection

Cookies sent by the browser with this request

Request.HttpMethod

string

The HTTP method (verb, e.g., GET or POST) used for this request

Request.Headers

NameValueCollection

The full set of HTTP headers sent with this request

Request.Url

Uri

The URL requested

Request.UserHostAddress

string

The IP address of the user making this request

RouteData.Route

RouteBase

The chosen RouteTable.Routes entry for this request

RouteData.Values

RouteValueDictionary

Active route parameters (either extracted from the URL, or default values)

HttpContext.Application

HttpApplicationStateBase

Application state store

HttpContext.Cache

Cache

Application cache store

HttpContext.Items

IDictionary

State store for the current request

HttpContext.Session

HttpSessionStateBase

State store for the visitor's session

User

IPrincipal

Authentication information about the logged-in user

TempData

TempDataDictionary

Temporary data items stored for the current user (more about this later)

You can explore the vast range of available request context information using IntelliSense (in an action method, type this. and browse the pop-up), and of course on MSDN (look up System.Web.Mvc.Controller and its base classes, or System.Web.Mvc.ControllerContext).

Using Action Method Parameters

As you've seen in previous chapters, action methods can take parameters. This is often a neater way to receive incoming data than manually extracting it from context objects. If you can make an action method pure—i.e., make it depend only on its parameters, without touching any external context data[57]—then it becomes much easier to understand at a glance.

For example, instead of writing this:

public ActionResult ShowWeatherForecast()
{
    string city = RouteData.Values["city"];
    DateTime forDate = DateTime.Parse(Request.Form["forDate"]);
    // ... implement weather forecast here ...
}

you can just write this:

public ActionResult ShowWeatherForecast(string city, DateTime forDate)
{
    // ... implement weather forecast here ...
}

To supply values for your parameters, the MVC Framework scans several context objects, including Request.QueryString, Request.Form, and RouteData.Values, to find matching key/value pairs. The keys are treated case insensitively, so the parameter city can be populated from Request.Form["City"]. (To recap, RouteData.Values is the set of curly brace parameters extracted by the routing system from the incoming URL, plus any default route parameters.)

Parameters Objects Are Instantiated Using Value Providers and Model Binders

Behind the scenes, when your controller is invoking one of its action methods and is trying to find suitable values for the method's parameters, it obtains values using framework components called value providers and model binders.

Value providers represent the supply of data items available to your controller. There are built-in value providers that fetch items from Request.Form, Request.QueryString, Request.Files, and RouteData.Values. Then, model binders take all these data items and try to map them onto whatever type of parameter your method takes. The built-in default model binder can create and populate objects of any .NET type, including collections and your own custom types. You saw an example of all this working together at the end of Chapter 6, when you allowed administrators to post a form containing multiple fields and received all this data as a single Product object.

To learn how these powerful, convention-based mechanisms work, including how different context objects are prioritized, how the system works recursively to populate entire collections and object graphs, and how it can use model metadata to validate incoming values, refer to the coverage of value providers and model binding in Chapter 11.

Optional and Compulsory Parameters

If the framework can't find any match for a particular parameter, it will try to supply null for that parameter. This is fine for reference/nullable types (such as string), but for value types (such as int or DateTime) you'll get an exception.[58] Here's another way to think about it:

  • Value-type parameters are inherently compulsory. To make them optional, either specify a default value (see below) or change the parameter type to something nullable (such as int? or DateTime?) so the framework can pass null if no value is available.

  • Reference-type parameters are inherently optional. To make them compulsory (i.e., to ensure that a non-null value is passed), you must add some code to the top of the action method to reject null values. For example, if the value equals null, throw an ArgumentNullException.

I'm not talking about UI validation here—if your intention is to provide the end user with feedback about certain form fields being required, see the "Validation" section in Chapter 12.

Specifying Default Parameter Values

In the previous chapter, you learned how you can configure the routing system to pass default values for parameters when no value is given in the request. But you wouldn't usually want to create a whole new routing entry just to configure a default value for a single action method, because there's a much quicker and simpler alternative: you can use the [DefaultValue] attribute on the action method parameter itself—for example:

public ActionResult Search(string query, [DefaultValue(1)] int page)
{
    // ...
}

Now, the framework will still use the normal system of value providers and model binders in an attempt to populate the page parameter with a value from the incoming request, but this time if no value is found, it will pass the value 1 instead of throwing an exception.

If you're using Visual Studio 2010 (even if you're targeting .NET 3.5), you can use the new C# optional parameter syntax instead, which achieves the same result while looking slightly more elegant:

public ActionResult Search(string query, int page = 1)
{
    // ...
}

When using default parameter values, here are a few points you should bear in mind:

This only works with literal values of simple types such as int, string, and enumerations, because .NET's attributes and optional parameters only allow values that are compile-time constants. You can't write [DefaultValue(new MyType(...))], nor can you refer to static fields as in string myParam = string.Empty (note that string.Empty is a read-only static value, not a compiler constant, so that at runtime there is only a single instance of it in memory). In particular, you can't specify a default value for a DateTime parameter, because C# has no syntax to represent date literals. This is not a serious problem, however, because you can always work around it by accepting null (making your parameter type nullable if necessary) and then adding code to the top of your action method that replaces any incoming null values with your desired default.

If the incoming request does contain a value for your parameter, but the value can't be converted to the parameter type (e.g., for an int parameter, any value that can't be parsed as an int), then the framework will pass your default value rather than throwing an exception. If you need to detect and handle this situation, refer to the coverage of validation in Chapter 12.

The [DefaultValue] attribute and the C# optional parameter syntaxes are usually equivalent, but not always. They rely on different underlying reflection APIs, so there is a possibility for inconsistency. In particular, if you want to set up a default value for a custom enum parameter, you will have to use [DefaultValue] because the C# optional parameter syntax won't work.[59]

Parameters You Can't Bind To

For completeness, it's worth noting that action methods aren't allowed to have out or ref parameters. It wouldn't make any sense if they did. ASP.NET MVC will simply throw an exception if it sees such a parameter.

Invoking Model Binding Manually in an Action Method

In data entry scenarios, it's fairly common to set up a <form> that includes separate fields for each property on a model object. When you receive the submitted form data, you might copy each incoming value into the relevant object property—for example:

public ActionResult SubmitEditedProduct()
{
    Product product = LoadProductByID(int.Parse(Request.Form["ProductID"]));

    product.Name = Request.Form["Name"];
    product.Description = Request.Form["Description"];
    product.Price = double.Parse(Request.Form["Price"]);

    CommitChanges(product);
    return RedirectToAction("List");
}

Most of that code is boring and predictable. Fortunately, just as you can use model binding to receive fully populated objects as action method parameters, you can also invoke model binding explicitly to update the properties on any model object you've already created.

For example, you could simplify the preceding action method as follows:

public ActionResult SubmitEditedProduct(int productID)
{
    Product product = LoadProductByID(productID);
    UpdateModel(product);

    CommitChanges(product);
    return RedirectToAction("List");
}

To complete this discussion, compare that code to the following. It's almost the same, but uses model binding implicitly:

public ActionResult SubmitEditedProduct(Product product)
{
    CommitChanges(product);
    return RedirectToAction("List");
}

Implicit model binding usually permits cleaner, more readable code. However, explicit model binding gives you more control over how the model objects are initially instantiated.

Producing Output

After a controller has received a request and processed it in some way (typically involving the domain model layer), it usually needs to generate some response for the user. There are three main types of responses that a controller may issue:

  • It may return HTML by rendering a view.

  • It may issue an HTTP redirection (often to another action method).

  • It may write some other data to the response's output stream (maybe textual data, such as XML or JSON, or maybe a binary file).

This part of the chapter examines your options for accomplishing each of these.

Understanding the ActionResult Concept

If you create a bare-metal IController class (i.e., if you implement IController directly, and do not derive from System.Web.Mvc.Controller), then you can generate a response any way you like by working directly with controllerContext.HttpContext.Response. For example, you can transmit HTML or issue HTTP redirections:

public class BareMetalController : IController
{
    public void Execute(RequestContext requestContext)
    {
        requestContext.HttpContext.Response.Write("I <b>love</b> HTML!");
        // ... or ...
        requestContext.HttpContext.Response.Redirect("/Some/Other/Url");
    }
}

It's simple, and it works. You could do the exact same thing with controllers derived from the Controller base class, too, by working directly with the Response property:

public class SimpleController : Controller
{
    public void MyActionMethod()
    {
        Response.Write("I'll never stop using the <blink>blink</blink> tag");
        // ... or ...
        Response.Redirect("/Some/Other/Url");
        // ... or ...
        Response.TransmitFile(@"c:filessomefile.zip");
    }
}

This does work—you can do it[60]—but it awkwardly mixes low-level details of HTML generation and working with HTTP directly into your application logic, undermining your separation of concerns. Plus, it would be inconvenient for unit testing, because you'd have to mock the Response object and record what method calls and parameters it received.

To get around this awkwardness, the MVC Framework separates stating your intentions from executing those intentions. Here's how it goes:

  • In an action method, avoid working directly with Response where possible. Instead, return an object derived from the ActionResult base class, which describes your intentions for what kind of response to issue (e.g., to render a particular view, or to redirect to a particular action method).

  • All ActionResult objects have a method called ExecuteResult(); in fact, that's the only method on the ActionResult base class. After your action has run, the framework calls ExecuteResult() on the action result, and this actually performs the designated response by working directly with Response.

This all helps to keep your controller code tidy—there's a concise API for generating typical ActionResult objects (e.g., to render a view), and you can also create custom ActionResult subclasses if you want to make new response patterns easy to reuse across your whole application. Unit tests that call your actions can then be simpler, too: they can simply invoke the action method and then make observations about the result object, not having to mock Response or parse any stream of data sent to it.

Note

In design pattern terms, the system of action results is an example of the command pattern. This pattern describes scenarios where you store and pass around objects that describe operations to be performed. See http://en.wikipedia.org/wiki/Command_pattern for more details.

Table 9-2 shows the framework's built-in action result types. They're all subclasses of ActionResult.

Table 9-2. ASP.NET MVC's Built-In ActionResult Types

Result Object Type

Purpose

Examples of Use

ViewResult

Renders the nominated or default view template.

return View();

return View("MyView", modelObject);

PartialViewResult

Renders the nominated or default partial view template.

return PartialView();

return PartialView("MyPartial",

modelObject);

RedirectToRouteResult

Issues an HTTP 302 redirection to an action method or specific route entry, generating a URL according to your routing configuration.

return

RedirectToAction("SomeOtherAction",

"SomeController");

return

RedirectToRoute("MyNamedRoute");

RedirectResult

Issues an HTTP 302 redirection to an arbitrary URL.

return

Redirect ("http://www.example.com");

ContentResult

Returns raw textual data to the browser, optionally setting a content-type header.

return Content(rssString,

"application/rss+xml");

FileResult

Transmits binary data (such as a file from disk or a byte array in memory) directly to the browser.

return File(@"c: eport.pdf",

"application/pdf");

JsonResult

Serializes a .NET object in JSON format and sends it as the response.

return Json(someObject);

JavaScriptResult

Sends a snippet of JavaScript source code that should be executed by the browser. This is only intended for use in Ajax scenarios (described in Chapter 14).

return

JavaScript("$('#myelem').hide();");

HttpUnauthorizedResult

Sets the response HTTP status code to 401 (meaning "not authorized"), which causes the active authentication mechanism (Forms Authentication or Windows Authentication) to ask the visitor to log in.

return new HttpUnauthorizedResult();

EmptyResult

Does nothing.

return new EmptyResult();

Next, you'll learn in more detail about how to use each of these, and finally see an example of how to create your own custom ActionResult type.

Returning HTML by Rendering a View

Most action methods are supposed to return some HTML to the browser. To do this, you render a view template, which means returning an action result of type ViewResult—for example:

public class AdminController : Controller
{
    public ViewResult Index()
    {
        return View("Homepage");
        // Or, equivalently: return new ViewResult { ViewName = "Homepage" };
    }
}

Note

This action method specifically declares that it returns an instance of ViewResult. It would work just the same if instead the method return type was ActionResult (the base class for all action results). In fact, some ASP.NET MVC programmers declare all their action methods as returning a nonspecific ActionResult, even if they know for sure that it will always return one particular subclass. However, it's a well-established principle in object-oriented programming that methods should return the most specific type they can (as well as accepting the most general parameter types they can). Following this principle expresses your intentions most clearly, makes the code easier to skim-read, and increases convenience for any other code that calls your action method (e.g., other action methods, or unit tests).

The call to View() generates a ViewResult object. When executing that result, the MVC Framework's built-in view engine, WebFormViewEngine, will by default look in a sequence of places to find the view template. If you're using areas (as described in Chapter 8), it will try to find one of the following files, stopping when the first one is discovered:

  1. /Areas/AreaName/Views/ControllerName/ViewName.aspx

  2. /Areas/AreaName/Views/ControllerName/ViewName.ascx

  3. /Areas/AreaName/Views/Shared/ViewName.aspx

  4. /Areas/AreaName/Views/Shared/ViewName.ascx

If the current request isn't associated with any area, or if no file was found in any of the preceding locations, WebFormViewEngine will then consider the following:

  1. /Views/ControllerName/ViewName.aspx

  2. /Views/ControllerName/ViewName.ascx

  3. /Views/Shared/ViewName.aspx

  4. /Views/Shared/ViewName.ascx

Note

For more details about how this naming convention is implemented and how you can customize it, see the "Implementing a Custom View Engine" section in Chapter 11.

So, in this example, assuming you're not using areas, the first place it will look is /Views/Admin/Homepage.aspx (notice that the Controller suffix on the controller class name is removed—that's the controller naming convention at work). Taking the convention-over-configuration approach a step further, you can omit a view name altogether—for example:

public class AdminController : Controller
{
    public ViewResult Index()
    {
        return View();
        // Or, equivalently: return new ViewResult();
    }
}

and the framework will use the name of the current action method instead (technically, it determines this by looking at RouteData.Values["action"]). So, in this example, the first place it will look for a view template is /Views/Admin/Index.aspx.

There are several other overrides on the controller's View() method—they correspond to setting different properties on the resulting ViewResult object. For example, you can specify an explicit master page name, or an explicit IView instance (discussed in Chapter 13).

Rendering a View by Path

You've seen how to render a view according ASP.NET MVC's naming and folder conventions, but you can also bypass those conventions and supply an explicit path to a specific view template—for example:

public class AdminController : Controller
{
    public ViewResult Index()
    {
        return View("~/path/to/some/view.aspx");
    }
}

Note that full paths must start with / or ~/, and must include a file name extension (usually .aspx). Unless you've registered a custom view engine, the file you reference must be an ASPX view page.

Passing a ViewData Dictionary and a Model Object

As you know, controllers and views are totally different, independent things. Unlike in traditional ASP.NET Web Forms, where the code-behind logic is deeply intertwined with the ASPX markup, the MVC Framework enforces a strict separation between application logic and presentation logic. Controllers supply data to their views, but views do not directly talk back to controllers. This separation of concerns is a key factor in MVC's tidiness, simplicity, and testability.

The mechanism for controller-to-view data transfer is ViewData. The Controller base class has a property called ViewData, of type ViewDataDictionary. You've seen ViewDataDictionary at work in many examples earlier in the book, but you might not yet have seen clearly all the different ways you can prepare ViewData and dispatch it from your controller. Let's consider your options.

Treating ViewData As a Loosely Typed Dictionary

The first way of working with ViewData uses dictionary semantics (i.e., key/value pairs). For example, populate ViewData as follows:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public ViewResult ShowPersonDetails()
{
    Person someguy = new Person { Name = "Steve", Age = 108 };
    ViewData["person"] = someguy;
    ViewData["message"] = "Hello";
    return View();  // ...or specify a view name, e.g. return View("SomeNamedView");
}

First, you fill the controller's ViewData collection with name/value pairs, and then you render a view. The framework will pass along the ViewData collection, so you can access its values in the view template, like this:

<%: ViewData["message"] %>, world!
The person's name is <%: ((Person)ViewData["person"]).Name %>
The person's age is <%: ((Person)ViewData["person"]).Age %>

Dictionary semantics are very flexible and convenient because you can send any collection of objects and pick them out by name. You don't have to declare them in advance; it's the same sort of convenience that you get with loosely typed programming languages.

The drawback to using ViewData as a loosely typed dictionary is that when you're writing the view, you don't get any IntelliSense to help you pick values from the collection. You have to know what keys to expect (in this example, person and message), and unless you're simply rendering a primitive type such as a string, you have to perform explicit manual typecasts. Of course, neither Visual Studio nor the compiler can help you here; there's no formal specification of what items should be in the dictionary (it isn't even determined until runtime).

Tip

If you do choose to use ViewData as a loosely typed dictionary, you can avoid using "magic strings" such as "person" and "message" as keys, and instead either use an enum of possible keys (and then reference ViewData[MyViewDataKeys.Person.ToString()]) or have a class containing const string values for each of the keys you're using. Then, if you have Visual Studio 2010 or a refactoring tool such as ReSharper, you can quickly and unambiguously locate all references to any given key in both C# source code and ASPX files.

Sending a Strongly Typed Object in ViewData.Model

ViewDataDictionary has a special property called Model. You can assign any.NET object to that property by writing ViewData.Model = myObject; in your action method, or as a shortcut you can pass myObject as a parameter to View()—for example:

public ViewResult ShowPersonDetails()
{
    Person someguy = new Person { Name = "Steve", Age = 108 };
    return View(someguy); // Implicitly assigns 'someguy' to ViewData.Model
    // ... or specify a view name, e.g. return View(someguy,"SomeNamedView");
}

Now you can access ViewData.Model in the view template:

The person's name is <%: ((Person)Model).Name %>
The person's age is <%: ((Person)Model).Age %>

Note

In a view template, you can write Model as a shorthand way of referencing ViewData.Model. However, code in an action method must refer to the object as ViewData.Model.

But hang on, that's hardly an improvement. We've given up the flexibility of passing multiple objects in a dictionary, and still have to do ugly typecasts. The real benefit arrives when you use a strongly typed view page.

I'll discuss the meaning and technical implementation of strongly typed views in some detail in Chapter 11—here I'll just give the overview. When you create a new view template (right-click inside an action method, and then choose Add View), you're given the option to create a strongly typed view by specifying what type of model object you want to render. The type you choose determines the type of the view's Model property. If you choose the type Person, you'll no longer need the ugly typecasts on Model, and you'll get IntelliSense (see Figure 9-2).

Strongly typed view data allows for IntelliSense while editing a view template

Figure 9-2. Strongly typed view data allows for IntelliSense while editing a view template

As a C# programmer, you no doubt appreciate the benefits of strong typing. The drawback, though, is that you're limited to sending only one object in ViewData.Model, which is awkward if you want to display a few status messages or other values at the same time as your Person object. To send multiple strongly typed objects, you'll need to create a wrapper class—for example:

public class ShowPersonViewModel
{
    public Person Person { get; set; }
    public string StatusMessage { get; set; }
    public int CurrentPageNumber { get; set; }
}

and then use this as the model type for a strongly typed view. Model classes that exist only to send particular combinations of data from a controller to a view (like ShowPersonViewModel) are often called view models to distinguish them from domain models.

Combining Both Approaches

ViewDataDictionary gives you maximum flexibility by letting you use both loosely typed and strongly typed techniques at the same time. This can avoid the need for view model classes. You can pass one primary strongly typed object using the Model property, plus an arbitrary dictionary of other values—for example:

public ViewResult ShowPersonDetails()
{
    Person someguy = new Person { Name = "Steve", Age = 108 };
    ViewData["message"] = "Hello";
    ViewData["currentPageNumber"] = 6;
    return View(someguy); // Implicitly assigns 'someguy' to ViewData.Model
    // or specify an explicit view name, e.g. return View(someguy,"SomeNamedView");
}

and then access them in your view template:

<%: ViewData["message"] %>, world!
The person's name is <%: Model.Name %>
The person's age is <%: Model.Age %>
You're on page <%: ViewData["currentPageNumber"] %>

In theory, this is a neat balance of strongly typed robustness and loosely typed flexibility. But in practice, I've noticed that most ASP.NET MVC developers place such a high value on compile-time checking, IntelliSense, and easy refactoring, that they usually consider it well worth the effort to create view model classes whenever needed, and completely avoid using ViewDataDictionary's loosely typed dictionary features.

There's more to learn about how ViewDataDictionary works and its more advanced features, but this has more to do with views than controllers, so we'll come back to it in Chapter 11.

Passing a Dynamic Object As ViewData.Model

If you're using .NET 4 and Visual Studio 2010, you have one further option: by declaring your view page to inherit from ViewPage<dynamic>, you'll get a dynamically typed model variable and can therefore read its properties without typecasts and without declaring them in advance. In fact, Visual Studio 2010's Add View pop-up configures your views to inherit from ViewPage<dynamic> by default unless you specify some other model type.

To use this, notice that the ExpandoObject type in .NET 4 uses dynamic language features to let you assign any combination of properties—for example:

public ViewResult ShowPersonDetails()
{
    dynamic model = new ExpandoObject();
    model.Message = "Hello";
    model.Person = new Person { Name = "Steve", Age = 108 };
    return View(model);
}

Now if your view's model type is set to dynamic (which, as you know, is the default in Visual Studio 2010), it can access these properties using a very simple syntax:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<%: Model.Message %>, world!
The person's name is <%: Model.Person.Name %>
The person's age is <%: Model.Person.Age %>

This is very reminiscent of Ruby on Rails, which of course uses dynamic typing everywhere, including its view templates.

The syntax in this example looks good, but bear in mind that in terms of maintainability, it's identical to using ViewDataDictionary as a loosely typed dictionary with string literals as keys. You won't get any IntelliSense when reading data from Model, and you won't be able to rename or refactor its properties or locate their references automatically. Most C# developers will prefer to stick to the strongly typed view model approach described previously.

Performing Redirections

Frequently, you may not want a certain action method to send back HTML. Instead, you may want it to hand over control to some other action method.

Consider an example: after some SaveRecord() action method saves some data to the database, you want to display a grid of all the records (for which you already have another action method called Index()). You have three options:

  • Render the grid as a direct result of your SaveRecord() action method, duplicating the code that's already in Index() (clearly, that's bad news).

  • From your SaveRecord() method, invoke the Index() method directly:

    public ViewResult SaveRecord(int itemId, string newName)
    {
        // Get the domain model to save the data
        DomainModel.SaveUpdatedRecord(itemId, newName);
    
        // Now render the grid of all items
        return Index();
    }

    That reduces code duplication. However, this can cause a few things to break—for example, if Index() tries to render its default view, it will actually render the default view for the SaveRecord action, because RouteData.Values["action"] will still equal SaveRecord.

  • From your SaveRecord() method, redirect to the Index action:

    public RedirectToRouteResult SaveRecord(int itemId, string newName)
    {
        // Get the domain model to save the data
        DomainModel.SaveUpdatedRecord(itemId, newName);
    
         // Now render to the grid of all items
         return RedirectToAction("Index");
    }

    This issues an HTTP 302 redirection to the Index action, causing the browser to perform a brand new GET request[61] to /ControllerName/Index, changing the URL displayed in its location bar.

In both of the first two options, the user's browser sees this whole process as a single HTTP request, and its address bar stays on /ControllerName/SaveRecord. The user might try to bookmark it, but that will cause an error when they come back (that URL may only be legal when submitting a form). Or, the user might press F5 to refresh the page, which will resubmit the POST request, duplicating the action. Nasty!

That's why the third technique is better. The newly requested page (at /ControllerName/Index) will behave normally under bookmarking and refreshing, and the updated location bar makes much more sense.

Note

In some circles, this technique of redirecting after handling a POST request is referred to as a design pattern called Post/Redirect/Get (see http://en.wikipedia.org/wiki/Post/Redirect/Get).

Redirecting to a Different Action Method

As you've just seen, you can redirect to a different action method as easily as this:

return RedirectToAction("SomeAction");

This returns a RedirectToRouteResult object, which internally uses the routing system's outbound URL-generation features to determine the target URL according to your routing configuration.

If you don't specify a controller (as previously), it's understood to mean "on the same controller," but you can also specify an explicit controller name, and if you wish, you can supply other arbitrary custom routing parameters that affect the URL generated:

return RedirectToAction("Index", "Products", new { color = "Red", page = 2 } );

As always, under the MVC Framework's naming convention, you should just give the controller's routing name (e.g., Products), not its class name (e.g., ProductsController).

Finally, if you're working with named RouteTable.Route entries, you can nominate them by name:

return RedirectToRoute("MyNamedRoute", new { customParam = "SomeValue" });

These URL-generating redirection methods, their many overloads, and how they actually generate URLs according to your routing configuration, were explained in detail in Chapter 8.

Redirecting to a Different URL

If you want to redirect to a literal URL (not using outbound URL generation), then return a RedirectResult object by calling Redirect():

return Redirect("http://www.example.com");

You can use application-relative virtual paths, too:

return Redirect("~/Some/Url/In/My/Application");

Note

Both RedirectToRouteResult and RedirectResult issue HTTP 302 redirections, which means "moved temporarily," just like ASP.NET Web Forms' Response.Redirect() method. The difference between this and a 301 (moved permanently) redirection was discussed in the previous chapter. If you're concerned about search engine optimization (SEO), make sure you're using the correct type of redirection.

Using TempData to Preserve Data Across a Redirection

A redirection causes the browser to submit a totally new HTTP request. So, in the new request, you'll no longer have the same set of request context values, nor access to any other temporary objects you created before the redirection. What if you want to preserve some data across the redirection? Then you should use TempData.

TempData is a new concept introduced with ASP.NET MVC[62]—there's nothing quite like it in ASP.NET Web Forms. Like the Session collection, it stores arbitrary .NET objects for the current visitor, but unlike Session, it automatically removes items from the collection after you read them back. That makes it ideal for short-term data storage across a redirection.

Let's go back to the previous example with SaveRecord and Index. After saving a record, it's polite to confirm to the user that their changes were accepted and stored. But how can the Index() action method know what happened on the previous request? Use TempData like this:

public RedirectToRouteResult SaveRecord(int itemId, string newName)
{
    // Get the domain model to save the data
    DomainModel.SaveUpdatedRecord(itemId, newName);

    // Now redirect to the grid of all items, putting a status message in TempData
    TempData["message"] = "Your changes to " + newName + " have been saved";
return RedirectToAction("Index");
}

Then during the subsequent request, in Index's view, render that value:

<% if(TempData["message"] != null) { %>
    <div class="StatusMessage"><%: TempData["message"] %></div>
<% } %>

Tip

You might like to put a snippet of view code like this into your site-wide master page, and then have a convention that any action can display a message in this area just by populating TempData["message"], regardless of whether the action performs a redirection. Each message will only be displayed once, and will then automatically be ejected from TempData.

Before TempData, the traditional way to do this was to pass the status message as a query string value when performing the redirection. However, TempData is much better: it doesn't result in a massive, ugly URL, and it can store any arbitrary .NET object.

Where TempData Stores Its Data

By default, TempData's underlying data store actually is the Session store, so you mustn't disable session storage if you want to use TempData. Also, if you've configured the session to store its data outside the ASP.NET process (which is recommended for scalability), then any object you store in either TempData or Session has to be serializable.

If you'd rather store TempData contents somewhere other than Session, create a class that implements ITempDataProvider, and then override your controller's CreateTempDataProvider() method, returning an instance of your new provider. The MVC Futures assembly contains a ready-made alternative provider, CookieTempDataProvider, which works by serializing TempData contents out to a browser cookie.

Controlling the Lifetime of TempData Items

TempData watches they keys you use when reading and writing it, so when you read the value TempData["myKey"], it adds myKey to a list of keys that will be ejected at the end of the current request. This means that if you write a value to TempData and then read it back while rendering a view during the same request, the item will not last any longer than that single request. But if you don't read it back during that request, it will stick around until the first future request when it is read back.

If you want to read a value from TempData without causing it to be flagged for ejection at the end of the request, you can use TempDataDictionary's Peek() method—for example:

string messageValue = (string) TempData.Peek("message"); // Does not cause ejection

Or, if you're in the situation where some other code has already read or will read TempData["message"], causing it to be flagged for ejection, but you want to protect that TempData entry so it won't be ejected at the end of the request, you can use TempData's Keep() method—for example:

TempData.Keep("message"); // Ensures "message" won't get ejected after this request

If you want to protect all TempData contents so they are not ejected at the end of the current request, you can call TempData.Keep() without passing a parameter.

Note that Keep() doesn't retain an item in TempData forever—it only ensures that any reads done during the current request don't cause the item's ejection. If you want to store items that should never be removed automatically, use Session instead.

Note

When you perform a redirection using a RedirectResult or a RedirectToRouteResult, they will internally call TempData.Keep() to ensure that all TempData contents are preserved regardless of whether you have read them during the current request. This is mainly for backward compatibility with ASP.NET MVC 1.0, which would always retain TempData contents for exactly one subsequent HTTP request regardless of when the items were accessed. However, you're unlikely to be affected by this special-case behavior, because it would be unusual to write an item to TempData, read it back, and then perform a redirection all in a single request.

Returning Textual Data

Besides HTML, there are many other text-based data formats that your web application might wish to generate. Common examples include

  • XML

  • RSS and ATOM (subsets of XML)

  • JSON (usually for Ajax applications)

  • CSV (usually for exporting tabular data to Excel)

  • Plain text

ASP.NET MVC has special, built-in support for generating JSON data (described later in this chapter), but for all the others, you can use the general purpose ContentResult action result type. To successfully return any text-based data format, there are three things for you to specify:

  • The data itself as a string.

  • The content-type header to send (e.g., text/xml for XML, text/csv for CSV, and application/rss+xml for RSS—you can easily look these up online or pick from the values on the System.Net.Mime.MediaTypeNames class). The browser uses this to decide what to do with the response.

  • Optionally, a text encoding format specified as a System.Text.Encoding object. This describes how to convert the .NET string instance into a sequence of bytes that can be sent over the wire. Examples of encodings include UTF-8 (very common on the Web), ASCII, and ISO-8859-1. If you don't specify a value, the framework will try to select an encoding that the browser claims to support.

A ContentResult lets you specify each of these. To create one, simply call Content()—for example:

public ActionResult GiveMePlainText()
{
    return Content("This is plain text", "text/plain");
    // Or replace "text/plain" with MediaTypeNames.Text.Plain
}

If you're returning text and don't care about the content-type header, you can use the shortcut of returning a string directly from the action method. The framework will convert it to a ContentResult:

public string GiveMePlainText()
{
    return "This is plain text";
}

In fact, if your action method returns an object of any type not derived from ActionResult, the MVC Framework will convert your action method return value to a string (using Convert.ToString(yourReturnValue, CultureInfo.InvariantCulture)) and will construct a ContentResult using that value. This can be handy in some Ajax scenarios; for example, if you simply want to return a Guid or other token to the browser. Note that it will not specify any contentType parameter, so the default (text/html) will be used.

Tip

It's possible to change this behavior of converting result objects to strings. For example, you might decide that action methods should be allowed to return arbitrary domain entities, and that when they do, the object should be packaged and delivered to the browser in some particular way (perhaps varying according to the incoming Accept HTTP header). This could be the basis of a REST application framework. To do this, make a custom action invoker by subclassing ControllerActionInvoker, and override its CreateActionResult() method to implement your desired behavior. Then override your controller's CreateActionInvoker() method, returning an instance of your custom action invoker.

Generating an RSS Feed

As an example of using ContentResult, see how easy it is to create an RSS 2.0 feed. You can construct an XML document using the elegant .NET 3.5 XDocument API, and then send it to the browser using Content()—for example:

class Story { public string Title, Url, Description; }

public ContentResult RSSFeed()
{
    Story[] stories = GetAllStories(); // Fetch them from the database or wherever

    // Build the RSS feed document
    string encoding = Response.ContentEncoding.WebName;
    XDocument rss = new XDocument(new XDeclaration("1.0", encoding, "yes"),
        new XElement("rss", new XAttribute("version", "2.0"),
            new XElement("channel", new XElement("title", "Example RSS 2.0 feed"),
                from story in stories
                select new XElement("item",
                      new XElement("title", story.Title),
                      new XElement("description", story.Description),
                      new XElement("link", story.Url)
                   )
            )
)
    );

    return Content(rss.ToString(), "application/rss+xml");
}

Most modern web browsers recognize application/rss+xml and display the feed in a well-presented human-readable format, or offer to add it to the user's RSS feed reader as a new subscription.

Returning JSON Data

JavaScript Object Notation (JSON) is a general purpose, lightweight, text-based data format that describes arbitrary hierarchical structures. The clever bit is that it is JavaScript code, so it's natively supported by just about every web browser out there (far more easily than XML). For more details, see www.json.org/.

It's most commonly used in Ajax applications for sending objects (including collections and whole graphs of objects) from the server to the browser. ASP.NET MVC has a built-in JsonResult class that takes care of serializing your .NET objects as JSON. You can generate a JsonResult by calling Json()—for example:

class CityData { public string city; public int temperature; }

[HttpPost]
public JsonResult WeatherData()
{
    var citiesArray = new[] {
        new CityData { city = "London", temperature = 68 },
        new CityData { city = "Hong Kong", temperature = 84 }
    };

    return Json(citiesArray);
}

This will transmit citiesArray in JSON format—for example:

[{"city":"London","temperature":68},{"city":"Hong Kong","temperature":84}]

Also, it will set the response's content-type header to application/json. Don't worry if you don't yet understand how to make use of JSON. You'll find further explanations and examples in Chapter 14, demonstrating its use with Ajax.

Note

For security reasons, JsonResult by default will not return any data during GET requests, because that data could then be exposed to third parties via cross-site requests. This is different from the default behavior in ASP.NET MVC 1.0. You'll learn why this change was made, why the risk only affects JSON data, and how you can override this behavior in Chapter 14. Notice that in the previous example, I used [HttpPost] to indicate that the action should only handle POST requests.

Returning JavaScript Commands

Action methods can handle Ajax requests just as easily as they handle regular requests. As you've just learned, an action method can return an arbitrary JSON data structure using JsonResult, and then the client-side code can do whatever it likes with that data.

Sometimes, however, you might like to respond to an Ajax call by directly instructing the browser to execute a certain JavaScript statement. You can do that using the JavaScript() method, which returns an action result of type JavaScriptResult—for example:

public JavaScriptResult SayHello()
{
    return JavaScript("alert('Hello, world!'),");
}

For this to work, your view or its master page must reference the MicrosoftAjax.js and MicrosoftMvcAjax.js JavaScript files as described in Chapter 14. Then you need to reference the SayHello() action using an Ajax.ActionLink() helper instead of a regular Html.ActionLink() helper. For example, add the following to a view:

<%: Ajax.ActionLink("Click me", "SayHello", null) %>

This is like Html.ActionLink() in that it renders a link to the SayHello action. The difference with Ajax.ActionLink() is that instead of triggering a full-page refresh, it performs an asynchronous request (which is also known as Ajax). When the user clicks this particular Ajax link, the preceding JavaScript statement will be fetched from the server and immediately executed, as shown in Figure 9-3.

Sending a JavaScript command from the server to the browser

Figure 9-3. Sending a JavaScript command from the server to the browser

Rather than using JavaScriptResult to display friendly messages, it's more likely that you'll use it to update the HTML DOM of the page being displayed. For example, after an action method that deletes an entity from your database, you might instruct the browser to remove the corresponding DOM element from a list. I'll come back to this, and cover the Ajax.* helpers in more detail, in Chapter 14.

Note

Technically, JavaScriptResult is really just the same as ContentResult, except that JavaScriptResult is hard-coded to set the response's content-type header to application/x-javascript. ASP.NET MVC's built-in Ajax helper script, MicrosoftMvcAjax.js, specifically checks for this content-type header value, and when it finds it, it knows to treat the response as executable JavaScript code rather than text.

Returning Files and Binary Data

What about when you want to send a file to the browser? You might want to cause the browser to open a save-or-open prompt, such as when sending a ZIP file, or you might want the browser to display the content directly in the browser window, as we did at the end of Chapter 6 when sending image data retrieved from the database.

FileResult is the abstract base class for all action results concerned with sending binary data to the browser. ASP.NET MVC comes with three built-in concrete subclasses for you to use:

  • FilePathResult sends a file directly from the server's file system.

  • FileContentResult sends the contents of a byte array (byte[]) in memory.

  • FileStreamResult sends the contents of a System.IO.Stream object that you've already opened from somewhere else.

Normally, you can forget about which FileResult subclass you're using, because all three can be instantiated by calling different overloads of the File() method. Just pick whichever overload of File() fits with what you're trying to do. You'll now see examples of each.

Sending a File Directly from Disk

You can use File() to send a file directly from disk as follows:

public FilePathResult DownloadReport()
{
    string filename = @"c:filessomefile.pdf";
    return File(filename, "application/pdf", "AnnualReport.pdf");
}

This will cause the browser to open a save-or-open prompt, as shown in Figure 9-4.

Internet Explorer's save-or-open prompt

Figure 9-4. Internet Explorer's save-or-open prompt

This overload of File() accepts the parameters listed in Table 9-3.

Table 9-3. Parameters Passed to File() When Transmitting a File Directly from Disk

Parameter

Type

Meaning

filename (required)

string

The path of the file (in the server's file system) to be transmitted.

contentType (required)

string

The MIME type to use as the response's content-type header. The browser will use this MIME type information to decide how to deal with the file. For example, if you specify application/vnd.ms-excel, then the browser should offer to open the file in Microsoft Excel. Likewise, application/pdf responses should be opened in the user's chosen PDF viewer.[a]

fileDownloadName (optional)

string

The content-disposition header value to send with the response. When this parameter is specified, the browser should always pop up a save-or-open prompt for the downloaded file. The browser should treat this value as the file name of the downloaded file, regardless of the URL the file is being downloaded from.

[a] You can find an extensive list of standard MIME types at www.iana.org/assignments/media-types/.

If you omit fileDownloadName and the browser knows how to display your specified MIME type itself (e.g., all browsers know how to display an image/gif file), then the browser should simply display the file itself.

If you omit fileDownloadName and the browser doesn't know how to display your specified MIME type itself (e.g., if you specify application/vnd.ms-excel), then the browser should pop up a save-or-open prompt, guessing a suitable file name based on the current URL (and in Internet Explorer's case, based on the MIME type you've specified). However, the guessed file name will almost certainly make no sense to the user, as it may have an unrelated file name extension such as .mvc, or no extension at all. So, always be sure to specify fileDownloadName when you expect a save-or-open prompt to appear.

Warning

If you specify a fileDownloadName that disagrees with the contentType (e.g., if you specify a file name of AnnualReport.pdf along with a MIME type of application/vnd.ms-excel), then the result will be unpredictable. Firefox 3 will offer to open the file in Excel, yet Internet Explorer 7 will offer to open it in a PDF viewer. If you don't know which MIME type corresponds to the file you're sending, you can specify application/octet-stream instead. This means "some unspecified binary file"—it tells the browser to make its own decision about how to handle the file, usually based on the file name extension.

Sending the Contents of a Byte Array

If you've already got the binary data in memory, you can transmit it using a different overload of File():

public FileContentResult DownloadReport()
{
    byte[] data = ... // Generate or fetch the file contents somehow
    return File(data, "application/pdf", "AnnualReport.pdf");
}

We used this technique at the end of Chapter 6 when sending image data retrieved from the database.

Again, you must specify a contentType, and you may optionally specify a fileDownloadName. The browser will treat these in exactly the same way as described previously.

Sending the Contents of a Stream

Finally, if the data you want to transmit comes from an open System.IO.Stream, you don't have to read it all into memory before sending it back out as a byte array. Instead, you can tell File() to transmit the stream's data as each chunk becomes available:

public FileStreamResult ProxyExampleDotCom()
{
    WebClient wc = new WebClient();
    Stream stream = wc.OpenRead("http://www.example.com/");
    return File(stream, "text/html");
}

Once again, you must specify a contentType parameter and optionally may specify a fileDownloadName. The browser will treat these exactly the same way as described previously.

Creating a Custom Action Result Type

The built-in action result types are sufficient for most situations you'll encounter. Nonetheless, it's easy to create your own action result type by subclassing one of the built-in types, or even by subclassing ActionResult directly. The only method you have to override is ExecuteResult().

If you are doing this so that it's easier to unit test a certain action, then of course be sure to expose enough publicly readable properties for a unit test to inspect your custom action result object and figure out what it's going to do. I'll illustrate this with an example.

Example: Watermarking an Image (and the Concept of Unit Testability Seams)

As a quick diversion, imagine you're building a stock photography-sharing web site. You might frequently need to process image files in various ways, and in particular you might have a number of action methods that return images with text superimposed on to them. This watermark text might be generated dynamically, sometimes stating the name of the photographer, and at other times the price of the image or its licensing details.

If you're writing unit tests for action methods that do this, how will the unit tests be able to check that the correct text was superimposed? Will they invoke the action method, get back the image data, and then use some kind of optical character recognition library to determine what string was superimposed? That might be fun to try, but frankly, it would be madness.

The way to solve this is to introduce a unit testability seam: a gap between the application code that decides what text to superimpose and the remaining code that actually renders the chosen text on to the image data. Your unit tests can squeeze into that gap, only testing the part of the code that decides what text to superimpose, ignoring the untestable part that actually renders text onto the image.

A custom action result is a great way to implement such a unit testability seam, because it allows your action method to specify what it intends to do, without the dirty business of actually doing it. Also, a custom action result makes the watermarking behavior easy to reuse across multiple action methods.

OK, enough discussion—here's the code! The following custom action result overlays some watermark text onto an image, and then transmits the image in PNG format (regardless of what format it started in):

public class WatermarkedImageResult : ActionResult
{
    public string ImageFileName { get; private set; }
    public string WatermarkText { get; private set; }

    public WatermarkedImageResult(string imageFileName, string watermarkText)
    {
        ImageFileName = imageFileName;
        WatermarkText = watermarkText;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        using(var image = Image.FromFile(ImageFileName))
        using(var graphics = Graphics.FromImage(image))
        using(var font = new Font("Arial", 10))
        using(var memoryStream = new MemoryStream())
        {
            // Render the watermark text in bottom-left corner
            var textSize = graphics.MeasureString(WatermarkText, font);
            graphics.DrawString(WatermarkText, font, Brushes.White, 10,
image.Height - textSize.Height − 10);

            // Transmit the image in PNG format (note: must buffer it in
            // memory first due to GDI+ limitation)
            image.Save(memoryStream, ImageFormat.Png);
            var response = context.RequestContext.HttpContext.Response;
            response.ContentType = "image/png";
            response.BinaryWrite(memoryStream.GetBuffer());
        }
    }
}

Using this, you could overlay a timestamp onto an image using an action method, as follows:

public class WatermarkController : Controller
{
    private static string ImagesDirectory = @"c:images";

    public WatermarkedImageResult GetImage(string fileName)
    {
        // For security, only allow image files from a specific directory
        var fullPath = Path.Combine(ImagesDirectory, Path.GetFileName(fileName));

        string watermarkText = "The time is " + DateTime.Now.ToShortTimeString();
        return new WatermarkedImageResult(fullPath, watermarkText);
    }
}

Then display a watermarked image by putting a suitable <img> tag into a view template, as follows:

<img src="<%: Url.Action("GetImage", "Watermark", new {fileName="lemur.jpeg"})%>"/>

This will produce an image such as that shown in Figure 9-5.

Displaying an image with a timestamp watermark

Figure 9-5. Displaying an image with a timestamp watermark

To unit test WatermarkController's GetImage() method, you can write a test that invokes the method, gets the resulting WatermarkedImageResult, and then checks its ImageFileName and WatermarkText properties to see what text is going to be superimposed onto which image file.

Of course, in a real project, you would probably make the code a little more general purpose (instead of hard-coding the font name, size, color, and directory name).

Unit Testing Controllers and Actions

Many parts of the MVC Framework are specifically designed for unit testability. This is especially true for controllers and actions, and that's important because they're the key building blocks of your application. So, what makes them so suitable for unit testing?

  • You can run them outside a web server context (e.g., in NUnit GUI). That's because they access their context objects (Request, Response, Session, etc.) only through abstract base classes (e.g., HttpRequestBase, HttpSessionStateBase), which you can mock. They aren't coupled directly to the traditional ASP.NET concrete implementations (HttpRequest, HttpSessionState), which only work in a web server context. (For the same reason, you can't run ASP.NET Web Forms pages outside a web server context.)

  • You don't have to parse any HTML. To check that a controller is producing the correct output, you can simply check which view template was selected and which ViewData and Model values were being sent. This is all thanks to the strict division between controllers and views.

  • Usually, you don't even have to supply mocks or test doubles for context objects, because parameter binding puts a unit testability seam between your code and the Request object, and the action results system puts a unit testability seam between your code and the Response object.

Note

This section of the chapter is not supposed to imply that you should necessarily unit test every single action method you write. Of course, ASP.NET MVC supports unit testing very well—what else would you expect from a modern framework? However, it's still up to you to find the development methodology that lets you create the best possible software in the finite time available. It's usually very productive to build back-end services and domain classes with unit test-driven development, but for UI code (such as MVC controllers), many developers feel that UI automation testing yields more value than unit testing. We'll come back to this question at the end of the chapter.

Whether you plan to unit test every action method, just some of them, or none of them, it's extremely valuable to understand how to do it and how ASP.NET MVC by design makes a considerable effort to support it. In practice, you'll find there's a natural alignment between code that can be unit tested and cleanly architected code. ASP.NET MVC's carefully planned testability can guide you toward tidy separation of concerns, and everyone appreciates that when maintenance time comes.

How to Arrange, Act, and Assert

To write meaningful unit tests that can be skim-read quickly, many people follow the arrange/act/assert (A/A/A) pattern. First, you arrange a set of objects to describe some scenario, then you act on one of them, and finally you assert that you have the desired result. This translates easily into testing MVC controllers:

  1. Arrange: Instantiate a controller object (in DI scenarios, you might want to supply mock versions of any dependencies as constructor parameters).

  2. Act: Run an action method, passing sample parameters and collecting the ActionResult.

  3. Assert: Assert that the ActionResult describes the expected result.

You only need mocks or test doubles for context objects (e.g., Request, Response, TempData, etc.) if the controller accesses any of them directly. Hopefully that isn't too often.

Testing a Choice of View and ViewData

Here's an incredibly simple controller:

public class SimpleController : Controller
{
public ViewResult Index()
    {
        return View("MyView");
    }
}

You can test that Index() renders the desired view using NUnit, as shown in the following code.

Note

For a description of how to set up NUnit and a "tests" project, see the "Testing" sidebars in Chapter 4. In particular, recall that you'll need references from your test project to System.Web, System.Web.Mvc, System.Web.Routing, System.Web.Abstractions, and your ASP.NET MVC Web Application project itself.

[TestFixture]
public class SimpleControllerTests
{
    [Test]
    public void Index_Renders_MyView()
    {
        // Arrange
        SimpleController controller = new SimpleController();

        // Act
        ViewResult result = controller.Index();

        // Assert
        Assert.IsNotNull(result, "Did not render a view");
        Assert.AreEqual("MyView", result.ViewName);
    }
}

Bear in mind that when an action method renders its default view (i.e., it simply calls return View()), you'll have to accept an empty string value for ViewName. You would rewrite the final Assert call as follows:

Assert.IsEmpty(result.ViewName);

Testing ViewData Values

If your action method uses ViewData—for example:

public ViewResult ShowAge(DateTime birthDate)
{
    // Compute age in full years
    DateTime now = DateTime.Now;
    int age = now.Year - birthDate.Year;
    if((now.Month*100 + now.Day) < (birthDate.Month*100 + birthDate.Day))
        age -= 1; // Haven't had birthday yet this year

    ViewData["age"] = age;
return View();
}

then you can test its contents, too:

[Test]
public void ShowAge_WhenBornSixYearsTwoDaysAgo_DisplaysAge6()
{
    // Arrange
    SimpleController controller = new SimpleController();
    DateTime birthDate = DateTime.Now.AddYears(−6).AddDays(−2);

    // Act
    ViewResult result = controller.ShowAge(birthDate);

    // Assert
    Assert.AreEqual(6, result.ViewData["age"], "Showing wrong age");
}

If your action method passes a strongly typed Model object to the view, then the unit test can find that value at result.ViewData.Model. Note that result.ViewData.Model is of type object, so you'll need to cast it to the expected model type.

Testing Redirections

If you have an action method that performs redirections—for example:

public RedirectToRouteResult RegisterForUpdates(string emailAddress)
{
    if (!IsValidEmail(emailAddress)) // Implement this somewhere
        return RedirectToAction("Register");
    else
    {
        // To do: Perform the registration here
        return RedirectToAction("RegistrationCompleted");
    }
}

then you can test the values in the resulting RedirectToRouteResult object:

[Test]
public void RegisterForUpdates_AcceptsValidEmailAddress()
{
    // Arrange
    string validEmail = "[email protected]";
    SimpleController controller = new SimpleController();

    // Act
    RedirectToRouteResult result = controller.RegisterForUpdates(validEmail);

    // Assert
    Assert.IsNotNull(result, "Should have redirected");
    Assert.AreEqual("RegistrationCompleted", result.RouteValues["action"]);
}
[Test]
public void RegisterForUpdates_RejectsInvalidEmailAddress()
{
    // Arrange
    SimpleController controller = new SimpleController();

    // Act
    RedirectToRouteResult result = controller.RegisterForUpdates("blah");

    // Assert
    Assert.IsNotNull(result, "Should have redirected");
    Assert.AreEqual("Register", result.RouteValues["action"]);
}

More Comments About Unit Testing

Hopefully you can see how the story would work out if you had some other type of ActionResult. Just follow the A/A/A pattern—it all falls into place. Because it's so predictable, I won't include specific examples on other types of ActionResult.

If an action method returns a general ActionResult (rather than a specialized type, such as ViewResult), then your test will need to cast that object to whatever specialized type it expects, and then can make assertions about its properties. If the specialized type of ActionResult might vary according to parameters or context, you can write a separate test for each scenario.

Note

You should realize that when you invoke action methods manually, as in the preceding unit test examples, the method invocation will not run any filters that may be associated with the method or its controller. After all, those filters are just .NET attributes; they have no meaning to the .NET Framework itself. Some developers find this troubling, and wonder how to get their filters to run within their unit tests. However, that would be missing the point! The whole idea of filters is that they are independent of the actions to which they apply—that's what makes filters reusable. When unit testing, you're testing action methods as isolated units; you're not simultaneously testing the infrastructure that surrounds them at runtime. You can also test your filters in isolation (independent of any particular action method) by writing separate unit tests to directly invoke methods such as OnActionExecuting() or OnActionExecuted() on your filters. If instead you want to test your action methods, filters, and everything else working together, you can write UI automation tests (e.g., using WatiN, as described in Chapter 3).

Mocking Context Objects

In some cases, your action methods won't work purely with method parameters and ActionResult values—they may access context objects directly. That's not necessarily a bad thing (that's what context objects are there for), but it means you'll need to supply test doubles or mocks for those context objects during your unit tests. You've seen an example that uses test doubles when testing routes in the previous chapter. This time, we'll focus exclusively on mocks.

Consider the following action method. It uses the Request, Response, and Cookie objects to vary its behavior according to whether the current visitor has been seen before.

public ViewResult Homepage()
{
    if (Request.Cookies["HasVisitedBefore"] == null)
    {
        ViewData["IsFirstVisit"] = true;
        // Set the cookie so we'll remember the visitor next time
        Response.Cookies.Add(new HttpCookie("HasVisitedBefore", bool.TrueString));
    }
    else
        ViewData["IsFirstVisit"] = false;

    return View();
}

This is a very impure method—it relies on a whole bunch of external context objects. To test this, you need to set up working values for those context objects. Fortunately, you can do so with any mocking tool. Here's one possible test written using Moq. It looks bad at first glance, but don't panic—we'll consider some alternatives in a moment!

[Test]
public void Homepage_Recognizes_New_Visitor_And_Sets_Cookie()
{
    // Arrange - first prepare some mock context objects
    var mockContext = new Moq.Mock<HttpContextBase>();
    var mockRequest = new Moq.Mock<HttpRequestBase>();
    var mockResponse = new Moq.Mock<HttpResponseBase>();
    // The following lines define associations between the different mock objects
    // (e.g., tells Moq what value to use for mockContext.Request)
    mockContext.Setup(x => x.Request).Returns(mockRequest.Object);
    mockContext.Setup(x => x.Response).Returns(mockResponse.Object);
    mockRequest.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
    mockResponse.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
    SimpleController controller = new SimpleController();
    var rc = new RequestContext(mockContext.Object, new RouteData());
    controller.ControllerContext = new ControllerContext(rc, controller);

    // Act
    ViewResult result = controller.Homepage();

    // Assert
    Assert.IsEmpty(result.ViewName);
    Assert.IsTrue((bool)result.ViewData["IsFirstVisit"]);
    Assert.AreEqual(1, controller.Response.Cookies.Count);
    Assert.AreEqual(bool.TrueString,
                    controller.Response.Cookies["HasVisitedBefore"].Value);
}

Note

If you're using a version of Moq older than 3.0, you'll need to write Expect instead of Setup. If you're using Moq 4.0 or newer, you can construct the same collection of mocks more declaratively using its new LINQ support. At the time of writing, Moq 4.0 is still in early beta and not complete enough for me to provide any sample code.

If you follow the code through, you'll see that it sets up a mock HttpContext instance, along with the child context objects Request, Response, and so on, and asserts that a HasVisitedBefore cookie gets sent in the response.

However, that ugly avalanche of "arrange" code obscures the meaning of the test. It's a bad unit test—hard to write, and even harder to maintain a few months later. Unfortunately, many ASP.NET MVC developers do in practice write unit tests easily as bad or worse than this, so let's now consider some ways to simplify things.

Reducing the Pain of Mocking

Mocking can be expensive. If you have to set up too many mock context objects—or even worse, chains of mocks that return other mocks—then the test becomes unclear. Just look at the previous unit test example, without looking at the method that it tests. At a glance, what behavior is this unit test supposed to imply? How do you know whether all that mock code is necessary? And how could you possibly write this unit test first (in true test-first TDD style) unless you had memorized the entire MVC Framework source code and could therefore anticipate the associations between the different context objects?

Here are five common ways to mitigate this difficulty and simplify your test code.

Method 1: Make a Reusable Helper That Sets Up a Standard Mock Context

You can factor out much of the logic needed to mock ASP.NET MVC's runtime context so that you can reuse it from one unit test to the next. Each individual unit test can then be much simpler. The way to do this is to define HttpContext, Request, Response, and other context objects, plus the relationships between them, using the API of your chosen mocking tool. If you're using Moq, the following reusable utility class (downloadable from this book's page on the Apress web site) does the job:

public class ContextMocks
{
    public Moq.Mock<HttpContextBase> HttpContext { get; private set; }
    public Moq.Mock<HttpRequestBase> Request { get; private set; }
    public Moq.Mock<HttpResponseBase> Response { get; private set; }
    public RouteData RouteData { get; private set; }

    public ContextMocks(Controller onController)
    {
        // Define all the common context objects, plus relationships between them
        HttpContext = new Moq.Mock<HttpContextBase>();
        Request = new Moq.Mock<HttpRequestBase>();
        Response = new Moq.Mock<HttpResponseBase>();
        HttpContext.Setup(x => x.Request).Returns(Request.Object);
        HttpContext.Setup(x => x.Response).Returns(Response.Object);
        HttpContext.Setup(x => x.Session).Returns(new FakeSessionState());
Request.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
        Response.Setup(x => x.Cookies).Returns(new HttpCookieCollection());
        Request.Setup(x => x.QueryString).Returns(new NameValueCollection());
        Request.Setup(x => x.Form).Returns(new NameValueCollection());

        // Apply the mock context to the supplied controller instance
        RequestContext rc = new RequestContext(HttpContext.Object, new RouteData());
        onController.ControllerContext = new ControllerContext(rc, onController);
    }
    // Use a fake HttpSessionStateBase, because it's hard to mock it with Moq
    private class FakeSessionState : HttpSessionStateBase
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        public override object this[string name]
        {
            get { return items.ContainsKey(name) ? items[name] : null; }
            set { items[name] = value; }
        }
    }
}

Note

This test helper class sets up working implementations of not just Request, Response, and their cookie collections, but also Session, Request.QueryString, and Request.Form. (TempData also works, because the Controller base class sets it up using Session.) You could expand it further to set up mocks for Request.Headers, HttpContext.Application, HttpContext.Cache and so on, and reuse it for all your controller tests.

Using the ContextMocks utility class, you can simplify the previous unit test as follows:

[Test]
public void Homepage_Recognizes_New_Visitor_And_Sets_Cookie()
{
    // Arrange
    var controller = new SimpleController();
    var mocks = new ContextMocks(controller); // Sets up complete mock context

    // Act
    ViewResult result = controller.Homepage();

    // Assert
    Assert.IsEmpty(result.ViewName);
    Assert.IsTrue((bool)result.ViewData["IsFirstVisit"]);
    Assert.AreEqual(1, controller.Response.Cookies.Count);
    Assert.AreEqual(bool.TrueString,
                    controller.Response.Cookies["HasVisitedBefore"].Value);
}

That's much, much more readable. Of course, if you're testing the action's behavior for a new visitor, you should also test its behavior for a returning visitor:

[Test]
public void Homepage_Recognizes_Previous_Visitor()
{
    // Arrange
    var controller = new SimpleController();
    var mocks = new ContextMocks(controller);
    controller.Request.Cookies.Add(new HttpCookie("HasVisitedBefore",
                                                  bool.TrueString));

    // Act
    ViewResult result = controller.Homepage();

    // Assert (this time, demonstrating NUnit's alternative "constraint" syntax)
    Assert.That(result.ViewName, Is.EqualTo("HomePage") | Is.Empty);
    Assert.That((bool)result.ViewData["IsFirstVisit"], Is.False);
}

You can also use the ContextMocks object to simulate extra conditions during the arrange phase (e.g., mocks.Request.Setup(x => x.HttpMethod).Returns("POST")).

Warning

If you follow this approach, it may seem like a good idea to create reusable helpers that configure controllers with mock contexts that simulate specific scenarios under test (e.g., a helper that prepares a controller with a logged-in user using a specific browser and a HasVisitedBefore cookie). Be cautious about building up such an infrastructure, because those sorts of helpers hide the assumptions that each test relies upon, and you'll quickly lose track of what preconditions are required for any given test. The test suite then no longer acts as a set of design specifications, but ends up being just a large collection of random observations. In the long term it's usually better to stick to a single, basic helper that prepares a standard request context, and let each unit test specify its own preconditions separately.

Method 2: Access Dependencies Through Virtual Properties

Sometimes it's helpful to decouple a controller from its external context by encapsulating all access to external context objects inside virtual properties or methods. At runtime, these virtual properties or methods will be called as normal, but in unit tests, you can mock their return values.

This is easiest to understand with an example. You could refactor the Homepage() action method from the previous example as follows:

public ViewResult Homepage()
{
    if (IncomingHasVisitedBeforeCookie == null)
    {
        ViewData["IsFirstVisit"] = true;
        // Set the cookie so we'll remember the visitor next time
        OutgoingHasVisitedBeforeCookie = new HttpCookie("HasVisitedBefore", "True");
    }
    else
ViewData["IsFirstVisit"] = false;

    return View();
}

public virtual HttpCookie IncomingHasVisitedBeforeCookie
{
    get { return Request.Cookies["HasVisitedBefore"]; }
}
public virtual HttpCookie OutgoingHasVisitedBeforeCookie
{
    get { return Response.Cookies["HasVisitedBefore"]; }
    set
    {
        Response.Cookies.Remove("HasVisitedBefore");
        Response.Cookies.Add(value);
    }
}

Note that the behavior is unaffected; the previous unit tests will still pass. The difference is that instead of touching Request and Response directly, our action method now accesses context information through virtual properties.

You can then write a unit test without a large number of mocks, directly intercepting any calls to IncomingHasVisitedBeforeCookie and OutgoingHasVisitedBeforeCookie.

[Test]
public void Homepage_Recognizes_New_Visitor_And_Sets_Cookie()
{
    // Arrange
    var controller = new Moq.Mock<SimpleController> { CallBase = true };
    controller.Setup(x => x.IncomingHasVisitedBeforeCookie)
              .Returns((HttpCookie)null);
    controller.SetupProperty(x => x.OutgoingHasVisitedBeforeCookie);

    // Act
    ViewResult result = controller.Object.Homepage();

    // Assert
    Assert.IsEmpty(result.ViewName);
    Assert.IsTrue((bool)result.ViewData["IsFirstVisit"]);
    Assert.AreEqual("True", controller.Object.OutgoingHasVisitedBeforeCookie.Value);
}

First notice that this time we have actually mocked the controller instance itself, using Moq's CallBase = true option to specify that any methods and properties not specifically overridden should behave as normal and call a real controller instance as they would at runtime.

Then, we've specifically told Moq to override the IncomingHasVisitedBeforeCookie property so that it always returns null, and to treat OutgoingHasVisitedBeforeCookie as a simple property that merely stores and returns any values set to it. Finally, we can make an assertion about the value written to OutgoingHasVisitedBeforeCookie. Moq is able to override these properties because they are both marked as virtual.

This technique can significantly reduce the complexity of working with external context objects, because you can choose exactly where it's convenient to draw a boundary between the controller and the other objects it must access. One possible drawback is that if you measure unit test code coverage, this will come out as less than 100 percent because the mocked properties and methods won't be run during unit tests. So, you should only use the technique if your mocked virtual properties and methods can be kept very simple.

Method 3: Receive Dependencies Using Model Binding

As you saw in the SportsStore example in Chapter 5 (under the heading "Giving Each Visitor a Separate Shopping Cart"), it's possible to use the MVC Framework's model binding system to populate action method parameters using arbitrary logic. In the SportsStore example, we used this technique to supply Cart instances from the Session collection.

Having done this, it's trivial to write a unit test that invokes an action method, passing a Cart object as a parameter as if the model binder had supplied that object. For example, you could test that CartController's Index action sets ViewData.Model to be the current user's cart:

[Test]
public void Index_DisplaysCurrentUsersCart()
{
    // Arrange
    Cart currentUserCart = new Cart();
    CartController controller = new CartController(null);

    // Act
    ViewResult result = controller.Index(currentUserCart, "someReturnUrl");

    // Assert
    Assert.AreSame(currentUserCart, result.ViewData.Model);
}

Notice that this unit test doesn't need to mock Request or Response or simulate Session in any way (even though at runtime the Cart would come from Session), because the action method acts only on its parameters. You could take any collection of context information relevant to your application, model it as a .NET class, and then create a custom binder that can supply these instances to any action method requiring a parameter of that type.

Method 4: Turn Your Dependencies into DI Components

As an alternative to using a custom model binder as a way of supplying context objects to a controller, you could also model them as .NET interfaces and supply concrete instances at runtime using your DI container.

This doesn't eliminate mocking—your unit tests would now have to mock the new interface you had just created—but it could simplify and reduce it. Your interfaces would describe the context data in a convenient way for the controller to consume, so they would no longer need to mock HttpContext, Request, Session, Cookies, and so on.

Method 5: Factor Out Complexity and Don't Unit Test Controllers

This final suggestion brings up broader matters of development methodology and object-oriented design. Over the past few years, many of the best practices and lessons learned from TDD have been collected, refined, and given the name behavior-driven development (BDD). The key observation of BDD is that it's more useful to drive your development process by specifying the behaviors you want to see rather than how those behaviors should be implemented.

For UI code, which for us means controllers in ASP.NET MVC, many behaviors aren't observable in a single call to an action method—they're only observable when the actions, your views, your JavaScript, the user's browser, your routing configuration, and other back-end components are all working together across a series of HTTP requests. Behaviors such as, "If I enter the wrong password too many times, I am no longer allowed to log in" exist only through the interactions across your whole technology stack. By the nature of UI code, the only complete way to specify and automatically verify such behaviors is typically by using UI automation tools (such as WatiN, as described in Chapter 3).

What does this mean for unit testing? Unit testing works brilliantly for back-end code such as services and domain classes because this is often where your complex business logic lives, and because such code tends to have a constrained range of inputs and outputs, so it's naturally easy to unit test. TDD has proven its benefits over and over both for designing and for verifying this type of code.

You could also choose to unit test your UI code (MVC controllers and action methods). But assuming you've factored any significant complexity out into separately tested service and domain classes, unit tests for your simple action methods would be more complex than the action methods themselves, and therefore wouldn't aid their design. Plus, UI automation tests can give you a high level of confidence that the whole system works together, and they detect newly introduced bugs better than action method unit tests ever could. Overall, this is why many in the ASP.NET MVC community have started focusing on BDD-style UI automation testing, and making controller unit testing the exception rather than the rule.

Whatever methodology you choose, make sure that you and your colleagues at least understand how to make use of unit testing, because it's an immensely useful technique when developing many parts of your application, and this is likely to include unit testing controllers from time to time.

Summary

MVC architecture is designed around controllers. Controllers consist of a set of named pieces of functionality known as actions. Each action implements application logic without being responsible for the gritty details of HTML generation, so it can remain simple, clean, and—if you wish—unit testable.

In this chapter, you learned how to create and use controller classes. You saw how to access incoming data through context objects and parameter binding, how to produce output through the action results system, and how you can write tidy unit tests for this.

In the next chapter, you'll go deeper into the MVC Framework's controller infrastructure, learning how to create reusable behaviors that you can tag on as filter attributes, how to implement a custom controller factory or customize action selection logic, and how you can relieve performance bottlenecks and handle very heavy traffic using asynchronous controllers.



[56] All these properties are merely shortcuts into the ControllerContext property. For example, Request is equivalent to ControllerContext.HttpContext.Request.

[57] This is not exactly the same as the definition of a pure function in the theory of functional programming, but it is closely related.

[58] In C#, classes are reference types (held on the heap) and structs are value types (held on the stack). The most commonly used value types include int, bool, and DateTime (but note that string is a reference type). Reference types can be null (the object handle is put into a state that means "no object"), but value types can't be (there is no handle; there's just a block of memory used to hold the object's value).

[59] When writing the assembly metadata, the C# 4 compiler only stores the underlying integral value of your enumeration value, not the value as a member of your enum type, and then at runtime that value doesn't correctly match your action method parameter type.

[60] Well, of course you can't actually display HTML, issue an HTTP redirection, and transmit a binary file all in the same HTTP response. You can only do one thing per response, which is another reason why it's semantically clearer to return an ActionResult than to do a series of things directly to Response.

[61] Strictly speaking, the HTTP specification says that browsers should keep using the same HTTP method to follow up on a 302 redirection, so if SaveRecord was requested with a POST, the browser should also use a POST to request Index. There's a special status code (303) that means "redirect using GET." However, all currently mainstream browsers defy the specification by using a GET request after any 302 redirection. This is convenient, since there isn't such an easy way to issue a 303.

[62] It was originally inspired by :flash in Ruby on Rails and the Flash[] collection in MonoRail.

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

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