Chapter 5. Controllers

In Chapter 2, we discussed the Model-View-Controller pattern in general and then followed up with how ASP.NET MVC compared with ASP.NET Web Forms. Now it's time to get into a bit more detail about one of the core elements of the three-sided pattern that is MVC — the Controller.

It's probably best to start out with a definition and then dive into detail from there. Keep this definition in the back of your mind as you read this chapter, as it helps to ground the discussion ahead with what a Controller is all about, and what it's supposed to do.

You might want to remember a quick definition: Controllers within the MVC pattern are responsible for responding to user input, often making changes to the Model in response to user input. In this way, Controllers in the MVC pattern are concerned with the flow of the application, working with data coming in, and providing data going out to the relevant View.

HISTORY OF THE CONTROLLER

Originally when MVC was conceived (as discussed in Chapter 2), things like graphical user interfaces (GUIs) with buttons and input boxes didn't exist. The event-driven concept had not been created yet, so there needed to be a different way to "listen" for user input and work with it.

Back then, when the user pressed a key or clicked the screen, a process would "listen," and that process was the Controller. The Controller was responsible for receiving that input, interpreting it and updating whatever data class was required (the Model), and then notifying the user of changes or program updates (the View, which we cover in more detail in Chapter 6).

In the late 1970s and early 1980s, researchers at Xerox PARC (which, coincidentally, was where the MVC pattern was incubated) began working with the notion of the GUI, wherein users "worked" within a virtual "desktop" environment that they could click and drag items around on. This type of interaction with computers was something completely new, and it created a whole new way of thinking about programming.

From this came the idea of event-driven programming — executing program actions based on events fired by a user, such as the click of a mouse or the pressing of a key on the keypad.

Over time, as GUIs became the norm, it became clear that the MVC pattern wasn't entirely appropriate for these new systems. In such a system, the GUI components themselves handle user input. If a button was pressed, it was the button that responded to the mouse click, not a Controller. The button would, in turn, notify any observers or listeners that it had been clicked. Patterns such as the Model-View-Presenter (MVP) proved to be more relevant to these modern systems than the MVC pattern.

ASP.NET Web Forms is an event-based system, which is unique with respect to web application platforms. It has a rich control-based, event-driven programming model that developers code against, providing a nice componentized GUI for the Web. When you click a button, a Button control responds and raises an event on the server indicating that it's been clicked. The beauty of this approach is that it allows the developer to work at a higher level of abstraction when writing code.

Digging under the hood a bit, however, reveals that a lot of work is going on to simulate that componentized event-driven experience. At its core, when you click a button, your browser submits a request to the server containing the state of the controls on the page encapsulated in an encoded hidden input. On the server side, in response to this request, ASP.NET has to rebuild the entire control hierarchy and then interpret that request, using the contents of that request to restore the current state of the application for the current user. All this happens because the Web, by its nature, is stateless. With a rich-client Windows GUI app, there's no need to rebuild the entire screen and control hierarchy every time the user clicks a UI widget, because the app doesn't go away.

With the Web, the state of the app for the user essentially vanishes and then is restored with every click. Well, that's an oversimplification, but the user interface, in the form of HTML, is sent to the browser from the server. This raises the question: "Where is the application?" For most web pages, the application is a dance between client and server, each maintaining a tiny bit of state, perhaps a cookie on the client or chunk of memory on the server, all carefully orchestrated to cover up the Tiny Lie. The Lie is that the Internet and HTTP are stateful protocols.

The underpinning of event-driven programming (the concept of state) is lost when programming for the Web, and many are not willing to embrace the Lie of a "virtually stateful" platform. Given this, the industry has seen the resurgence of the MVC pattern, albeit with a few slight modifications.

Note

One example of such a modification is that in traditional MVC, the Model can "observe" the View via an indirect association to the View. This allows the Model to change itself based on View events. With MVC for the Web, by the time the View is sent to the browser, the Model is generally no longer in memory and does not have the ability to observe events on the View. (Note that you'll see exceptions to this change when this book covers applying AJAX to MVC in Chapter 7.)

With MVC for the Web, the Controller is once again at the forefront. Applying this pattern requires that every user input to a web application simply take the form of a request. For example, with ASP.NET MVC, each request is routed (using routing, discussed in Chapter 4) to a Controller method on that Controller (called an action). The Controller is entirely responsible for interpreting that request, manipulating the Model if necessary, and then selecting a View to send back to the user via the response.

With that bit of theory out of the way, let's dig into ASP.NET MVC's specific implementation of Controllers. The next two sections of this chapter cover the basic abstractions that all Controllers implement. These sections dig under the hood a bit to provide a deep understanding of how Controllers are implemented in ASP.NET MVC, so they might not be as applicable to everyday development. The remaining sections cover the Controller class itself — which is the most important class that an ASP.NET MVC developer must understand — and provide most of the interesting functionality.

DEFINING THE CONTROLLER: THE ICONTROLLER INTERFACE

As discussed in Chapters 2 and 3, among the core focuses of ASP.NET MVC are extensibility and flexibility. When building software this way, it's important to leverage abstraction as much as possible (we discuss this further in Chapter 11) by using interfaces.

For a class to be a Controller in ASP.NET MVC, it must at minimum implement the IController interface, and by convention the name of the class must end with the suffix Controller. The naming convention is actually quite important — and you'll find that there are many of these small rules in play with ASP.NET MVC, which will make your life just a little bit easier by not making you define configuration settings and attributes. Ironically, the IController interface is quite simple given the power it is abstracting:

public interface IController
{
    void Execute(RequestContext requestContext);
}
Code snippet 5-1.txt

It's a simple process really: When a request comes in, the Routing system identifies a Controller, and it calls the Execute method. Let's look at a quick example (which assumes that you are using the default project template and thus have a standard route already configured).

Start by creating a new MVC Web Application (File

DEFINING THE CONTROLLER: THE ICONTROLLER INTERFACE

Next, implement IController by adding IController after the class name and then press Ctrl-period to implement the interface methods (this will stub out the Execute method for you). In the Execute method, have it simply write out Hello World as the response (it's not exactly ground-breaking, but it demonstrates how to write the simplest possible Controller):

using System.Web.Mvc;
using System.Web.Routing;

public class SimpleController : IController
{
    public void Execute(RequestContext requestContext)
    {
        var response = requestContext.HttpContext.Response;
response.Write("<h1>Hello World!</h1>");
    }
}
Code snippet 5-2.txt

Now press Ctrl+F5 to compile the code and start your browser. In the address bar, you'll need to navigate to /simple. Because the port is chosen at random, you may not be able to tell the full URL, but on my machine it's http://localhost:61353/simple. Figure 5-1 shows the result.

FIGURE 5-1

Figure 5.1. FIGURE 5-1

Apart from the large font, this is not exactly breathtaking, but overall the process is pretty simple.

The point of the IController interface is to provide a very simple starting point for anyone who wants to hook in their own Controller framework into ASP.NET MVC. The Controller class, which is covered later in this chapter, layers much more interesting behavior on top of this interface. This is a common extensibility pattern within ASP.NET.

For example, if you're familiar with HTTP handlers, you might have noticed that the IController interface looks very similar to IHttpHandler:

public interface IHttpHandler
{
    void ProcessRequest(HttpContext context);

    bool IsReusable { get; }
}
Code snippet 5-3.txt

Ignoring the IsReusable property for a moment, IController and IHttpHandler are pretty much equivalent in terms of responsibility. The IController.Execute and IHttpHandler.ProcessRequest methods both respond to a request and write some output to a response. The main difference between the two is the amount of contextual information provided to the method. The IController.Execute method receives an instance of RequestContext, which includes not just the HttpContext but also other information relevant to a request for ASP.NET MVC.

The Page class, which is probably the class most familiar to ASP.NET Web Forms developers as it is the default base class for an ASPX page, implements IHttpHandler.

THE CONTROLLERBASE ABSTRACT BASE CLASS

Implementing IController is pretty easy, as you've seen, but really all it's doing is providing a facility for Routing to find your Controller and call Execute. This is the most basic hook into the system that you could ask for, but overall it provides little value to the Controller you're writing. This may be a good thing to you — many custom tool developers don't like it when a system they're trying to customize imposes a lot of restrictions (the ASP.NET Membership system is such a system). Others may like to work a bit closer with the API, and for that there is ControllerBase.

The ControllerBase class is an abstract base class that layers a bit more API surface on top of the IController interface. It provides the TempData and ViewData properties (which are ways of sending data to a View, discussed in Chapter 7), and the Execute method of ControllerBase is responsible for creating the ControllerContext, which provides the MVC-specific context for the current request much the same way that an instance of HttpContext provides the context for ASP.NET in general (providing request and response, URL, and server information, among elements).

This base class is still very lightweight and allows developers to provide extremely customized implementations for their own Controllers, while benefiting from the action filter infrastructure in ASP.NET MVC (ways of filtering and working with request/response data, which are discussed in Chapter 8). What it doesn't provide is the ability to convert actions into method calls. That's where the Controller class comes in.

THE CONTROLLER CLASS AND ACTIONS

In theory, you could build an entire site with classes that simply implement ControllerBase or IController, and it would work. Routing would look for an IController by name and then call Execute, and you would have yourself a very, very basic website.

This approach, however, is akin to working with ASP.NET using raw HttpHandlers — it would work, but you're left to reinvent the wheel and plumb the core framework logic yourself.

Interestingly, ASP.NET MVC itself is layered on top of HTTP handlers as you'll see later, and overall there was no need to make internal plumbing changes to ASP.NET to implement MVC. Instead, the ASP.NET MVC team simply layered this new framework on top of existing ASP.NET extensibility points.

The standard approach to writing a Controller is to have it inherit from the System.Web.Mvc.Controller abstract base class, which implements the ControllerBase base class. The Controller class is intended to serve as the base class for all Controllers, as it provides a lot of nice behaviors to Controllers that derive from it.

Action Methods

All public methods of a class that derive from Controller become action methods, which are callable via an HTTP request (the RPC style of system discussed in Chapter 2). So rather than one monolithic implementation of Execute, you can factor your Controller into action methods, each of which responds to a specific user input.

Let's walk through another simple Controller example, but this time let's add a public method. To get started with this example, open up the previous example and create a new Controller by right-clicking the Controllers folder and selecting Add

PRODUCT TEAM ASIDE
using System;
using System.Web;
using System.Web.Mvc;

public class Simple2Controller : Controller
{
    public void Hello()
    {
        Response.Write("<h1>Hello World Again!</h1>");
    }
}
Code snippet 5-4.txt

Press Ctrl+F5 (or Debug

PRODUCT TEAM ASIDE
FIGURE 5-2

Figure 5.2. FIGURE 5-2

As before, this is not exactly breathtaking, but it is a bit more interesting. Notice that the URL in the address bar directly correlates to the action method of your Controller. If you recall from the previous chapter, the default route for MVC breaks URLs into three main components: /{controller}/{action}/{id}. Let's look at how that applies to this example.

The simple2 portion of the URL corresponds to the Controller name. The MVC framework appends the Controller suffix to the Controller name and locates your Controller class, Simple2Controller.

/simple2/hello
Code snippet 5-5.txt

The last portion of the URL corresponds to the action. The framework locates a public method with this name and attempts to call the method.

Working with Parameters

You can add any number of public methods (which we'll call Actions from here on out to keep with convention) to a Controller class, which will all be callable via this pattern. Actions may also contain parameters. Going back to the previous example, add a new action method that takes in a parameter:

public class Simple2Controller : Controller
{
    public void Goodbye(string name)
    {
        Response.Write("Goodbye"  + HttpUtility.HtmlEncode(name));
    }
}
Code snippet 5-6.txt

This method is callable via the URL:

/simple2/goodbye?name=World

Code snippet 5-7.txt

Notice that you can pass in parameters to an action method by name via the query string. You can also pass in parameters via the URL segments, discoverable by position as defined in your routes (discussed in Chapter 4). For example, the following URL is more aesthetically pleasing to many developers and Internet users:

/simple2/goodbye/world
Code snippet 5-8.txt

which provides more information about what we're looking at.

Working with parameters passed by URL segment requires you to define how Routing will identify these parameters in the URL. Fortunately, the default route (created for you when you click File

PRODUCT TEAM ASIDE

Changing the action method signature a little bit (by renaming the parameter name to id) like so:

public class Simple2Controller : Controller
{
    public void Goodbye(string id)
    {
        Response.Write("Goodbye"  + HttpUtility.HtmlEncode(id));
    }
}
Code snippet 5-10.txt

allows you to call that method using the cleaner URL, and Routing will pass the parameter by structured URL instead of a query string parameter:

/simple2/goodbye/world
Code snippet 5-11.txt

Working with Multiple Parameters

What if you have a method with more than one parameter? This is a very common scenario, and rest assured that you can still use query strings, but if you want to pass both parameters via the URL segments, you'll need to define a new route specifically for this situation.

For example, suppose that you have an action method that calculates the distance between two points:

public void Distance(int x1, int y1, int x2, int y2)
{
    double xSquared = Math.Pow(x2 - x1, 2);
    double ySquared = Math.Pow(y2 - y1, 2);
    Response.Write(Math.Sqrt(xSquared + ySquared));
}
Code snippet 5-12.txt

Using only the default route, the request would need to look like this:

/simple2/distance?x2=1&y2=2&x1=0&y1=0
Code snippet 5-13.txt

You can improve on this situation a bit by defining a route that allows you to specify the parameters in a cleaner format. This code goes inside the RegisterRoutes methods within the Global.asax.cs file, and uses the MapRoute method (discussed in Chapter 4) to define a new route:

routes.MapRoute("distance",
    "simple2/distance/{x1},{y1}/{x2},{y2}",
    new { Controller = "Simple2", action = "Distance" }
);
Code snippet 5-14.txt

Notice that you are using the comma character to separate x and y coordinates. Now this action method is callable via the URL:

/simple2/distance/0,0/1,2
Code snippet 5-15.txt

The presence of commas in a URL might look strange, but Routing is quite powerful! For more on routing, refer to Chapter 4.

Default Parameters

There are many cases in which you'll want to support optional parameters. This took some extra work with ASP.NET MVC 1.0. You could either add a custom route with a default parameter, or you could change the parameter type to accept a nullable value (e.g., int?) and write extra logic to supply the default value.

ASP.NET MVC 2 simplifies this case with default parameters, using the DefaultValueAttribute from the System.ComponentModel namespace. The DefaultValueAttribute lets you specify a parameter value that the controller action will use if it's not contained in the route values.

The controller action in the following listing will respond to both /Dinners/DinnersNearMe/90210 and /Dinners/DinnersNearMe/90210?maxDinners=50. In the first case, the default value of 10 will be used.

public ActionResult DinnersNearMe(string location,
    [DefaultValue(10)]int maxDinners) {
}
Code snippet 5-16.txt

Even better, we can use language support for optional parameters to eliminate the need for the [DefaultValues] attribute. Visual Basic has long had support for optional parameters, and C# 4.0 adds support for optional parameters as well. That allows us to simplify the controller action signature as follows:

public ActionResult DinnersNearMe(string location, int maxDinners = 10) {
}
Code snippet 5-17.txt

THE ACTIONRESULT

In the previous action method examples, the action methods wrote text directly to the HTTP response using Response.Write. While this is certainly a valid way to produce an HTTP response, it isn't the most efficient; it also defeats some of the neat features of ASP.NET such as Master Pages!

As mentioned before, the purpose of the Controller within the MVC pattern is to respond to user input. In ASP.NET MVC, the action method is the granular unit of response to user input. The action method is ultimately responsible for handling a user request and outputting the response that is displayed to the user, which is typically HTML.

The pattern that an action method follows is to do whatever work is asked of it, and at the end, return an instance of a type that derives from the ActionResult abstract base class.

Taking a quick look at the source for the ActionResult abstract base class, we see:

public abstract class ActionResult
{
   public abstract void ExecuteResult(ControllerContext context);
}
Code snippet 5-18.txt

Notice that the class contains a single method, ExecuteResult. If you're familiar with the Command Pattern, this should look familiar to you. Action results represent commands that your action method wants the framework to perform on your behalf.

Action results generally handle framework-level work, while the action method handles your application logic. For example, when a request comes in to display a list of products, your action method will query the database and put together a list of the appropriate products to show. Perhaps it needs to perform some filtering based on business rules within your app. At this point, your action method is completely focused on application logic.

However, once the method is ready to display the list of products to the user, you may not want to have the code within the action method deal directly with the framework-level plumbing, such as writing the list of products to the response. Perhaps you have a template defined that knows how to format a collection of products as HTML. You'd rather not have that information encapsulated in the action method because it would violate the separation of concerns the authors have so carefully cultivated up until this point.

One technique you have at your disposal is to have the action method instantiate an instance of ViewResult (which derives from ActionResult) and give the data to that instance, and then return that instance. At that point, your action method is done with its work, and the action invoker will call the ExecuteResult method on that ViewResult instance, which does the rest. Here's what the code might look like:

public ActionResult ListProducts()
{
  //Pseudo code
  IList<Product> products = SomeRepository.GetProducts();
  ViewData.Model = products;
  return new ViewResult {ViewData = this.ViewData };
}
Code snippet 5-19.txt

In practice, you'll probably never see code that instantiates an ActionResult instance directly like that. Instead, you would use one of the helper methods of the Controller class such as the View method like so:

public ActionResult ListProducts()
{
  //Pseudo code
  IList<Product> products = SomeRepository.GetProducts();
return View(products);
}
Code snippet 5-20.txt

The next chapter covers the ViewResult in more depth and tells how it relates to Views.

Action Result Types

ASP.NET MVC includes several ActionResult types for performing common tasks. These are listed in Table 5-1. Each type is discussed in more detail in the sections that follow.

Table 5.1. Descriptions of ActionResult Types

ACTIONRESULT TYPE

DESCRIPTION

EmptyResult

Represents a null or empty response. It doesn't do anything.

ContentResult

Writes the specified content directly to the response as text.

JsonResult

Serializes the objects it is given into JSON and writes the JSON to the response.

RedirectResult

Redirects the user to the given URL.

RedirectToRouteResult

Redirects the user to a URL specified via Routing parameters.

ViewResult

Calls into a View engine to render a View to the response.

PartialViewResult

This is similar to ViewResult, except it renders a partial View to the response, typically in response to an AJAX request.

FileResult

Serves as the base class for a set of results that writes a binary response to the stream. Useful for returning files to the user.

FilePathResult

Derives from FileResult and writes a file to the response based on a file path.

FileContentResult

Derives from FileResult and writes a byte array to the response.

FileStreamResult

Derives from FileResult and writes a stream to the response.

JavaScriptResult

Used to execute JavaScript code immediately on the client sent from the server.

EmptyResult

As the name implies, the EmptyResult is used to indicate that the framework should do nothing. This follows a common design pattern known as the Null Object pattern, which replaces null references with an instance. In this instance, the ExecuteResult method has an empty implementation. This design pattern was introduced in Martin Fowler's Refactoring book. You can learn more at:

http://martinfowler.com/bliki/refactoring.html.

ContentResult

The ContentResult writes its specified content (via the Content property) to the response. This class also allows for specifying the content encoding (via the ContentEncoding property) and the content type (via the ContentType property).

If the encoding is not specified, then the content encoding for the current HttpResponse instance is used. The default encoding for HttpResponse is specified in the globalization element of web.config.

Likewise, if the content type is not specified, the content type set on the current HttpResponse instance is used. The default content type for HttpResponse is text/html.

FileResult

The FileResult is very similar to the ContentResult except that it is used to write binary content (e.g., a Microsoft Word document on disk or the data from a blob column in SQL Server) to the response. Setting the FileDownloadName property on the result will set the appropriate value for the Content-Disposition header, causing a file download dialog to appear for the user.

Note that FileResult is an abstract base class for three different file result types:

  • FilePathResult

  • FileContentResult

  • FileStreamResult

Usage typically follows the factory pattern in which the specific type returned depends on which overload of the File method (discussed later) is called.

JsonResult

The JsonResult uses the JavaScriptSerializer class to serialize its contents (specified via the Data property) to the JSON (JavaScript Object Notation) format. This is useful for simple AJAX scenarios that have a need for an action method to return data in a format easily consumable by JavaScript.

As for ContentResult, the content encoding and content type for the JsonResult can both be set via properties. The only difference is that the default ContentType is application/json and not text/html for this result.

Note that the JsonResult serializes the entire object graph. Thus, if you give it a ProductCategory object, which has a collection of 20 Product instances, every Product instance will also be serialized and included in the JSON sent to the response. Now imagine if each Product had an Orders collection containing 20 Order instances. As you can imagine, the JSON response can grow huge quickly.

There is currently no way to limit how much to serialize into the JSON, which can be problematic with objects that contain a lot of properties and collections, such as those typically generated by LINQ to SQL. The recommended approach is to create a type that contains the specific information you want included in the JsonResult. This is one situation in which an anonymous type comes in handy.

For example, in the preceding scenario, instead of serializing an instance of ProductCategory, you can use an anonymous object initialize to pass in just the data you need, as the following code sample demonstrates:

public ActionResult PartialJson()
{
       var category = new ProductCategory { Name="Partial"};
       var result = new {
             Name = category.Name,
             ProductCount = category.Products.Count };
       return Json(result);
}
Code snippet 5-21.txt

Note

Note that rather than instantiating a JsonResult directly, this method uses the JSON helper method. We cover helper methods later in this chapter.

In this sample, all you needed was the category name and the product count for the category. So rather than serializing the entire object graph, you pulled the information you needed from the actual object and stored that information in an anonymous type instance named result. You then sent that instance to the response, rather than the entire object graph.

JavaScriptResult

The JavaScriptResult is used to execute JavaScript code on the client sent from the server. For example, when using the built-in AJAX helpers to make a request to an action method, the method could simply return a bit of JavaScript that is immediately executed when it gets to the client:

public ActionResult DoSomething() {
    script s = "$('#some-div').html('Updated!'),";

    return JavaScript(s);
}
Code snippet 5-22.txt

This would be called by the following code:

<%: Ajax.ActionLink("click", "DoSomething", new AjaxOptions()) %>
<div id="some-div"></div>
Code snippet 5-23.txt

This assumes that you've referenced the AJAX libraries and jQuery.

RedirectResult

The RedirectResult performs an HTTP redirect to the specified URL (set via the Url property). Internally, this result calls the HttpResponse.Redirect method, which sets the HTTP status code to "HTTP/1.1 302 Object Moved," causing the browser to immediately issue a new request for the specified URL.

Technically, you could just make a call to Response.Redirect directly within your action method, but using the RedirectResult defers this action until after your action method finishes its work. This is useful for unit testing your action method and helps keep underlying framework details outside of your action method.

RedirectToRouteResult

RedirectToRouteResult performs an HTTP redirect in the same manner as the RedirectResult, but instead of specifying a URL directly, this result uses the Routing API to determine the redirect URL.

Note that there are two convenience methods (defined below) that return a result of this type, RedirectToRoute and RedirectToAction.

ViewResult

The ViewResult is the most widely used action result type. It calls the FindView method of an instance of IViewEngine, returning an instance of IView. The ViewResult then calls the Render method on the IView instance, which renders the output to the response. In general, this will merge the specified View data (the data that the action method has prepared to be displayed in the View) with a template that formats the data for displaying.

The next chapter covers Views in more detail.

PartialViewResult

PartialViewResult works in exactly the same way that the ViewResult does, except that it calls the FindPartialView method to locate a View rather than FindView. It's used to render partial Views such as a ViewUserControl and is useful in partial update scenarios when using AJAX to update a portion of the page with some chunk of HTML.

Action Result Helper Methods

If you take a close look at the default Controller actions in the default ASP.NET MVC project template, you'll notice that the action methods don't actually instantiate instances of ViewResult. For example, here's the code for the About method:

public ActionResult About() {
    ViewData["Title"] = "About Page";
    return View();
}
Code snippet 5-24.txt

Notice that it simply returns the result of a call to the View method. The Controller class contains several convenience methods for returning ActionResult instances. These methods are intended to help make action method implementations a bit more readable and declarative. Instead of creating new instances of action results, it is more common to simply return the result of one of these convenience methods.

These methods are generally named after the action result type that they return, with the Result suffix truncated. Hence the View method returns an instance of ViewResult. Likewise, the Json method returns an instance of JsonResult. The one exception in this case is the RedirectToAction method, which returns an instance of RedirectToRoute.

Table 5-2 lists the existing methods and which types they return.

Table 5.2. Controller Convenience Methods which return ActionResult Instances

METHOD

DESCRIPTION

Redirect

Returns a RedirectResult, which redirects the user to the appropriate URL.

RedirectToAction

Returns a RedirectToRouteResult, which redirects the user to an action using the supplied route values.

RedirectToRoute

Returns a RedirectToRouteResult, which redirects the user to the URL that matches the specified route values.

View

Returns a ViewResult, which renders the View to the response.

PartialView

Returns a PartialViewResult, which renders a partial View to the response.

Content

Returns a ContentResult, which writes the specified content (string) to the response.

File

Returns a class that derives from FileResult, which writes binary content to the response.

Json

Returns a ContentResult containing the output from serializing an object to JSON.

JavaScript

Returns a JavaScriptResult containing JavaScript code that will be immediately executed when returned to the client.

Implicit Action Results

One constant goal with ASP.NET MVC, and software development in general, is to make the intentions of the code as clear as possible. There are times when you have a very simple action method only intended to return a single piece of data. In this case, it is helpful to have your action method signature reflect the information that it returns.

To highlight this point, revisit the Distance method covered earlier in the chapter. In that implementation, you simply wrote the result of the distance calculation directly to the response. Your intention with that method is that it would take in parameters representing two points and then return a double representing the distance between the two points. Let's look at another way you can write that action method:

public double Distance(int x1, int y1, int x2, int y2)
{
    double xSquared = Math.Pow(x2 - x1, 2);
    double ySquared = Math.Pow(y2 - y1, 2);
    return Math.Sqrt(xSquared + ySquared);
}
Code snippet 5-25.txt

Notice that the return type is a double and not a type that derives from ActionResult. This is perfectly acceptable. When ASP.NET MVC calls that method and sees that the return type is not an ActionResult, it automatically instantiates a ContentResult containing the result of the action method and uses that internally as the ActionResult.

One thing to keep in mind is that the ContentResult requires a string value, so the result of your action method needs to be converted to a string first. To do this, ASP.NET MVC calls the ToString method on the result, using the InvariantCulture, before passing it to the ContentResult. If you need to have the result formatted according to a specific culture, you should explicitly return a ContentResult yourself.

In the end, the above method is roughly equivalent to the following method:

public ActionResult Distance(int x1, int y1, int x2, int y2)
{
    double xSquared = Math.Pow(x2 - x1, 2);
    double ySquared = Math.Pow(y2 - y1, 2);
    double distance = Math.Sqrt(xSquared + ySquared);
    return Content(distance.ToString(CultureInfo.InvariantCulture));
}
Code snippet 5-26.txt

The advantages of the first approach are that it makes your intentions slightly more clear, and the method is slightly easier to unit test.

Table 5-3 highlights the various implicit conversions you can expect when writing action methods that do not have a return type of ActionResult.

Table 5.3. Implicit Conversions with Action Methods

RETURN VALUE

DESCRIPTION

Null

The action invoker replaces null results with an instance of EmptyResult. This follows the Null Object Pattern. As a result, implementers writing custom action filters don't have to worry about null action results.

Void

The action invoker treats the action method as if it returned null, and thus an EmptyResult is returned.

Object (anything other than ActionResult)

The action invoker calls ToString using InvariantCulture on the object and wraps the resulting string in a ContentResult instance.

ACTION INVOKER

The previous section made several references to the action invoker without giving any details about it. Well, no more arm waving (or as one author likes to put it: "Jazz Hands")! This section covers the role of a critical element in the ASP.NET MVC request processing chain: the thing that actually invokes the action you're calling — the Action Invoker. Chapter 4 briefly covered how Routing maps a URL to an action method on a Controller class. Diving deeper into the details, you learned that routes themselves do not map anything to Controller actions; they merely parse the incoming request and populate a RouteData instance stored in the current RequestContext.

It's the ControllerActionInvoker, set via the ActionInvoker property on the Controller class, that is responsible for invoking the action method on the Controller based on the current request context. The invoker performs the following tasks:

  • It locates the action method to call.

  • It maps the current route data and requests data by name to the parameters of the action method.

  • It invokes the action method and all of its filters.

  • It calls ExecuteResult on the ActionResult returned by the action method. For methods that do not return an ActionResult, the invoker creates an implicit action result as described in the previous section and calls ExecuteResult on that.

In this next section, you'll take a closer look at how the invoker locates an action method.

How an Action Is Mapped to a Method

The ControllerActionInvoker looks in the route values dictionary associated with the current request context for a value corresponding to the action key. For example, suppose that when looking at the default route, you see the following URL pattern:

{controller}/{action}/{id}
Code snippet 5-27.txt

When a request comes in and matches that route, you populate a dictionary of route values (accessible via the RequestContext) based on this route. For example, if a request comes in for:

/home/list/123
Code snippet 5-28.txt

Routing adds the value list with a key of action to the route values dictionary.

At this point within the request, an action is just a string extracted from the URL; it is not a method. The string represents the name of the action that should handle this request. While it may commonly be represented by a method, the action really is an abstraction. There might be more than one method that can respond to the action name. Or it might be not even be a method but a workflow or some other crazy thing that can handle the action.

The point of this is that, while in the general case an action typically maps to a method, it doesn't have to.

Action Method Selection

Once you've identified the name of the action, the invoker attempts to identify a method that can respond to that action. This is the job of the ControllerActionInvoker.

By default, the invoker simply uses reflection to find a public method on a class that derives from Controller that has the same name (case-insensitive) as the current action. Such a method must meet the following criteria:

  • An action method must not have the NonActionAttribute defined in its inheritance chain.

  • Special methods such as constructors, property accessors, and event accessors cannot be action methods.

  • Methods originally defined on Object (such as ToString) or on Controller (such as Dispose) cannot be action methods.

Like many things within this framework, you can tweak this default behavior.

ActionNameAttribute

Applying the ActionNameAttribute attribute to a method allows you to specify the action that the method handles. For example, suppose that you want to have an action named View, but this would conflict with the View method of Controller. An easy way to work around this issue without having to futz with Routing or method hiding is to do the following:

[ActionName("View")]
public ActionResult ViewSomething(string id)
{
  return View();
}
Code snippet 5-29.txt

The ActionNameAttribute redefines the name of this action as View. Thus, this method is invoked in response to requests for /home/view, but not for /home/viewsomething. In the latter case, as far as the action invoker is concerned, an action method named ViewSomething does not exist.

One consequence of this is that if you're using our conventional approach to locate the View that corresponds to this action, the View should be named after the action, not after the method. In the preceding example (assuming that this is a method of HomeController), you would look for the View ~/Views/Home/View.aspx by default.

This attribute is not required for an action method. Implicitly, the name of a method serves as the action name for that method if this attribute is not applied.

ActionSelectorAttribute

You're not done matching the action to a method yet. Once you've identified all methods of the Controller class that match the current action name, you need to whittle the list down further by looking at all instances of the ActionSelectorAttribute applied to the methods in the list.

This attribute is an abstract base class for attributes that provide fine-grained control over which requests an action method can respond to. The API for this method is quite simple and consists of a single method:

public abstract class ActionSelectorAttribute : Attribute
{
  public abstract bool IsValidForRequest(ControllerContext controllerContext,
     MethodInfo methodInfo);
}
Code snippet 5-30.txt

At this point, the invoker looks for any methods in the list that contain attributes that derive from this attribute and calls the IsValidForRequest method on each attribute. If any attribute returns false, the method that the attribute is applied to is removed from the list of potential action methods for the current request.

At the end, you should be left with one method in the list, which the invoker then invokes. If more than one method can handle the current request, the invoker throws an exception indicating the problem. If no method can handle the request, the invoker calls HandleUnknownAction on the Controller.

The ASP.NET MVC Framework includes two implementations of this base attribute, the AcceptVerbsAttribute and the NonActionAttribute.

AcceptVerbsAttribute

AcceptVerbsAttribute is a concrete implementation of ActionSelectorAttribute that uses the current HTTP request's HTTP method (verb) to determine whether or not a method is the action that should handle the current request. This allows you to have two methods of the same name (with different parameters, of course), both of which are actions but respond to different HTTP verbs.

ASP.NET MVC 2 introduces a more terse syntax for HTTP method restriction with the [HttpPost] and [HttpPut] attributes. These are simple aliases for the previous [AcceptVerbs(HttpVerbs.Post)] and [AcceptVerbs(HttpVerbs.Put)] attributes, but are easier to both type and read.

For example, you may want two versions of the Edit method, one that renders the edit form and the other that handles the request when that form is posted:

[HttpGet]
public ActionResult Edit(string id)
{
  return View();
}

[HttpPost]
public ActionResult Edit(string id, FormCollection form)
{
  //Save the item and redirect...
}
Code snippet 5-31.txt

When a POST request for /home/edit is received, the action invoker creates a list of all methods of the Controller that match the edit action name. In this case, you would end up with a list of two methods. Afterward, the invoker looks at all of the ActionSelectorAttribute instances applied to each method and calls the IsValidForRequest method on each. If each attribute returns true, then the method is considered valid for the current action.

For example, in this case, when you ask the first method if it can handle a POST request, it will respond with false because it only handles GET requests. The second method responds with true because it can handle the POST request, and it is the one selected to handle the action.

If no method is found that meets these criteria, the invoker will call the HandleUnknownAction method on the Controller, supplying the name of the missing action. If more than one action method meeting these criteria is found, an InvalidOperationException is thrown.

Simulating RESTful Verbs

Browsers support two HTTP verbs: GET and POST. However, the REST architectural style also makes use of a few other verbs: DELETE, HEAD, and PUT. ASP.NET MVC 2 allows you to simulate these verbs via the Html.HttpMethodOverride helper method, which accepts the standard HTTP verbs (DELETE, GET, HEAD, POST, and PUT). Internally, this works by sending the verb in an X-HTTP-Method-Override form field.

The behavior of HttpMethodOverride is used by the [AcceptVerbs] attribute as well as the new shorter verb attributes:

  • HttpPostAttribute

  • HttpPutAttribute

  • HttpGetAttribute

  • HttpDeleteAttribute

While the override can only be used when the real request is a POST request, the override value can also be specified in an HTTP header or in a query string value as a name/value pair.

Mapping Parameters

Once the action method is located, the invoker is responsible for mapping values to the parameters of the method. As you've seen earlier in this chapter in the Distance example, route data is one such place where the invoker gets parameter values:

/simple2/distance/0,0/1,2
Code snippet 5-32.txt

However, route data is not the only place that invoker looks for parameter values. For example, in that same example, you saw that you can also pass parameters via the query string:

/simple2/distance?x2=1&y2=2&x1=0&y1=0
Code snippet 5-33.txt

Table 5-4 describes the locations where the invoker looks for parameter values.

Table 5.4. Locations the Action Invoker will check for Route Parameter values

LOCATION

DESCRIPTION

Request.Form collection

This is the posted form, which contains name/value pairs.

Route Data

Specifically in the current RequestContext.RouteData.RouteValues. The route data depends on having a route defined that can map the request URL into the parameters of the action method.

Request.QueryString collection

These are name/value pairs appended to the URL.

Invoking Actions

Once the invoker has mapped values for each parameter of the action method, it is now ready to invoke the action method itself. At this point, the invoker builds up a list of filters associated with the current action method and invokes the filters along with the action method, in the correct order. For more detailed coverage of this, look at Chapter 8, which covers filters in great depth.

Using Asynchronous Controller Actions

ASP.NET MVC 2 includes full support for an asynchronous request pipeline. This is made possible by the introduction of the AsyncController and supporting infrastructure. The purpose of this pipeline is to allow the web server to handle long-running requests — such as those that spend a large amount of time waiting for a network or database operation to complete — while still remaining responsive to other requests. In this regard, asynchronous code is about servicing requests more efficiently than it is about servicing requests more quickly.

To understand the difference between asynchronous and synchronous ASP.NET code, one must first have a basic knowledge of how requests are processed by the server. IIS maintains a collection of idle threads (the thread pool) that are used to service requests. When a request comes in, a thread from the pool is scheduled to process that request. While a thread is processing a request, it cannot be used to process a second request until it has finished with the first. The ability of IIS to service multiple requests simultaneously is based on the assumption that there will be free threads in the pool to process incoming requests.

Now consider an action that makes a network call as part of its execution, and consider that the network call might take two seconds to complete. From the site visitor's point of view, the server takes two seconds to respond to his or her request. In a synchronous world, the thread processing the request is blocked for the two seconds that the network call is taking place. That is, the thread cannot perform useful work for the current request because it's waiting for the network call to complete, but it also can't do any useful work for any other request because it's still scheduled to work on the first request. A thread in this condition is known as a blocked thread. Normally this isn't a problem since the thread pool is large enough to accommodate such scenarios. In large applications that process multiple simultaneous requests, however, this can lead to many threads being blocked waiting for data and not enough idle threads left in the thread pool available for dispatch for servicing new incoming requests. This condition is known as thread starvation, and it can severely affect the performance of a website. See Figure 5-3.

FIGURE 5-3

Figure 5.3. FIGURE 5-3

In an asynchronous pipeline, threads are not blocked waiting for data. When a long-running application like a network call begins, the action is responsible for voluntarily relinquishing control of the thread for the duration of the operation. Essentially, the action tells the thread, "It'll be a while before I can continue, so don't bother waiting for me right now. I'll notify IIS when I get the data I need." The thread returns to the thread pool so that it can handle another request, and the current request is essentially paused while waiting for data. Importantly, while a request is in this state, it is not assigned to any thread from the thread pool, so it is not blocking other requests from being processed. When the action gets the data it needs, it notifies IIS and a free thread from the thread pool is dispatched to continue processing the request. The thread that continues processing the request may or may not be the same thread that originated the request, but the pipeline takes care of this so that developers don't have to worry about it. See Figure 5-4.

FIGURE 5-4

Figure 5.4. FIGURE 5-4

It is important to note that in the previous example, the end user still sees a two-second delay between the time he sends the request and the time he receives a response from the server. This is what is meant by the earlier statement about asynchronous being primarily for efficiency rather than speed. Even though it takes the same amount of time to respond to the user's request regardless of whether the operation is synchronous or asynchronous, in an asynchronous pipeline the server is not blocked from doing other useful work while waiting for the first request to complete.

Choosing Synchronous versus Asynchronous Pipelines

The following are some guidelines for deciding whether to use synchronous or asynchronous pipelines. Note that these are just guidelines and each application will have its own requirements.

Use synchronous pipelines when:

  • The operations are simple or short-running.

  • Simplicity and testability are important.

  • The operations are CPU-bound rather than IO-bound.

Use asynchronous pipelines when:

  • Testing shows that blocking operations are bottlenecking site performance.

  • Parallelism is more important than simplicity of code.

  • The operations are IO-bound rather than CPU-bound.

Since asynchronous pipelines have more infrastructure and overhead than synchronous pipelines, asynchronous code is somewhat more difficult to reason about than synchronous code. Testing such code would require mocking more of the infrastructure, and it would also require taking into account that the code can execute in many different orderings. Finally, it's not really beneficial to convert a CPU-bound operation to an asynchronous operation, since all that does is add overhead to an operation that probably wasn't blocked to begin with. In particular, this means that code that performs CPU-bound work within ThreadPool.QueueUserWorkItem() method will not benefit from an asynchronous pipeline.

Writing Asynchronous Action Methods

Asynchronous actions are written in a similar fashion to standard synchronous actions. In much the same way that the Controller type serves as the base class for synchronous controllers, the AsyncController type serves as the base class for asynchronous controllers. For example, consider a portal site that displays news for a given area. A typical synchronous action might look like this.

public class PortalController : Controller {
    public ActionResult News(string city) {
        NewsService newsService = new NewsService();
        NewsModel news = newsService.GetNews(city);
        return View(news);
    }
}
Code snippet 5-34.txt

Accessing /Portal/News?city=Seattle will show local news for Seattle. This can easily be rewritten as an asynchronous action method as follows:

public class PortalController : AsyncController {
    public void NewsAsync(string city) {
        AsyncManager.OutstandingOperations.Increment();
        NewsService newsService = new NewsService();
        newsService.GetNewsCompleted += (sender, e) => {
            AsyncManager.Parameters["news"] = e.News;
            AsyncManager.OutstandingOperations.Decrement();
        };
        newsService.GetNewsAsync(city);
    }

    public ActionResult NewsCompleted(NewsModel news) {
        return View(news);
    }
}
Code snippet 5-35.txt

Note a few patterns here:

  • Asynchronous controllers subclass is AsyncController rather than Controller. This tells the MVC pipeline to prepare for an asynchronous request.

  • There is no single News() method. It has been decomposed into two methods: NewsAsync() and NewsCompleted(), with the second method returning an ActionResult. This method pair is logically seen as a single action News, so it is accessed using the same URL /Portal/News?city=Seattle.

  • Observe the parameters passed to each method. The parameters passed to NewsAsync() are provided using the normal parameter binding mechanisms, while the parameters passed to NewsCompleted() are provided using the AsyncManager.Parameters dictionary. The NewsService consumed by the NewsAsync() method is an example of a service that exposes methods using an event-based asynchronous pattern (http://msdn.microsoft.com/en-us/library/wewwczdw.aspx).

  • Using AsyncManager.OutstandingOperations notifies the MVC pipeline of how many operations are pending completion. This is necessary since MVC otherwise has no way of knowing what operations were kicked off by the action method or when those operations are complete. When this counter hits zero, the MVC pipeline completes the overall asynchronous operation by calling the NewsCompleted() method.

The MVC Pattern for Asynchronous Actions

If the action name is Sample, then the framework will look for SampleAsync() and SampleCompleted() methods.

View pages should be named Sample.aspx rather than SampleAsync.aspx or SampleCompleted.aspx. (Remember, the action name is Sample.)

  • Normal parameter binding mechanisms are responsible for providing parameters to the SampleAsync() method.

  • Parameters to SampleCompleted() (if any) are provided via the AsyncManager.Parameters dictionary.

  • Use AsyncManager.OutstandingOperations to notify the MVC pipeline of how many operations are pending completion.

  • The SampleCompleted() method is responsible for returning the ActionResult that will eventually be executed.

Performing Multiple Parallel Operations

The true benefit of asynchronous code can be seen when an action wants to perform several operations at a time. For example, a typical portal site would show not simply news, but also sports, weather, stocks, and other information. A synchronous version of such an action method would take the following form:

public class PortalController : Controller {
    public ActionResult Index(string city) {
        NewsService newsService = new NewsService();
        NewsModel newsModel = newsService.GetNews(city);

        WeatherService weatherService = new WeatherService();
        WeatherModel weatherModel = weatherService.GetWeather(city);

        SportsService sportsService = new SportsService();
        SportsModel sportsModel = sportsService.GetScores(city);

        PortalViewModel model = new PortalViewModel {
            News = newsModel,
Weather = weatherModel,
            Sports = sportsModel
        };

        return View(model);
    }
}
Code snippet 5-36.txt

Note that the calls are performed sequentially, so the time required to respond to the user is equal to the sum of the times required to make each individual call. If the calls are 200, 300, and 400 microseconds (μs), then the total action execution time is 900 μs (plus some insignificant overhead).

Similarly, an asynchronous version of that action would take the following form:

public class PortalController : AsyncController {
    public void IndexAsync(string city) {
        AsyncManager.OutstandingOperations.Increment(3);

        NewsService newsService = new NewsService();
        newsService.GetNewsCompleted += (sender, e) => {
            AsyncManager.Parameters["news"] = e.News;
            AsyncManager.OutstandingOperations.Decrement();
        };
        newsService.GetNewsAsync(city);

        WeatherService weatherService = new WeatherService();
        weatherService.GetWeatherCompleted += (sender, e) => {
            AsyncManager.Parameters["weather"] = e.Weather;
            AsyncManager.OutstandingOperations.Decrement();
        };
        weatherService.GetWeatherAsync(city);

        SportsService sportsService = new SportsService();
        sportsService.GetScoresCompleted += (sender, e) => {
            AsyncManager.Parameters["sports"] = e.Scores;
            AsyncManager.OutstandingOperations.Decrement();
        };
        SportsModel sportsModel = sportsService.GetScoresAsync(city);
    }

    public ActionResult IndexCompleted(NewsModel news,
        WeatherModel weather, SportsModel sports) {

        PortalViewModel model = new PortalViewModel {
            News = news,
            Weather = weather,
            Sports = sports
        };

        return View(model);
    }
}
Code snippet 5-37.txt

Note that the operations are all kicked off in parallel at the same time, so the time required to respond to the user is equal to the longest individual call time. If the calls are 200, 300, and 400 μs, then the total action execution time is 400 μs (plus some insignificant overhead).

In both of the above examples, the URL to access the action is /Portal/Index?city=Seattle (or /Portal?city=Seattle, using the default route), and the View page name is Index.aspx (since the action is Index).

Using Filters with Asynchronous Controller Actions

Any filters ([Authorize], [OutputCache], [ActionName], [AcceptVerbs], etc.) should be placed on the ActionAsync() method rather than the ActionCompleted() method. Filters placed on the ActionCompleted() method will be ignored.

[Authorize] // correct
public void ActionAsync() {
    // ...
}

[Authorize] // incorrect
public ActionResult ActionCompleted() {
    // ...
}
Code snippet 5-38.txt

Furthermore, the method pair SampleAsync() and SampleCompleted() must share the same prefix (in this case, Sample), even if an [ActionName] attribute is applied. Consider the following action:

[ActionName("Bravo")]
public void AlphaAsync() {
    // ...
}

public ActionResult AlphaCompleted() {
    // ...
}
Code snippet 5-39.txt

In this example, accessing /controller/Alpha will result in a 404, since the action has been renamed to Bravo. The correct URL is /controller/Bravo. The View page should be named Bravo.aspx.

Timeouts

The default time-out for an asynchronous action is 45 seconds. If the time-out period expires, a TimeoutException will be thrown, and action filters will be able to respond to this from within OnActionExecuted(). [HandleError] can also respond to this exception.

[HandleError(ExceptionType = typeof(TimeoutException))]
Code snippet 5-40.txt

Two attributes are provided to control the time-out period: [AsyncTimeout] and [NoAsyncTimeout]. [AsyncTimeout] specifies a time-out period in milliseconds, and [NoAsyncTimeout] specifies that TimeoutException should never be thrown. Because these attributes are action filters, they can go on an ActionAsync() method to control that individual action, or they can go on the controller to apply to every action within that controller.

[AsyncTimeout(60000)] // this method times out after 60 seconds
public void ActionAsync() {
    // ...
}

[NoAsyncTimeout] // infinite timeout for all actions in controller
public class PortalController : AsyncController {
    // ...
}
Code snippet 5-41.txt

Additional Considerations for Asynchronous Methods

Controllers which derive from AsyncController may mix and match synchronous and asynchronous methods. That is, it is perfectly legal to have methods like Index(), ListAsync(), and ListCompleted(), and the like on the same controller.

The AsyncController will not allow direct access to ActionAsync() or ActionCompleted() methods. That is, the URL to access this action must be /controller/Action rather than /controller/ActionAsync or /controller/ActionCompleted. In particular, this means that RedirectToAction("ActionAsync") is incorrect; use RedirectToAction("Action") instead. The same rule applies to Html.ActionLink() and other APIs that accept action names as parameters.

Synchronous action methods on controllers which derive from AsyncController cannot have an Async or Completed suffix. For example, in an air travel booking site, the following is invalid unless there's a matching ReservationAsync() method.

// will be blocked
public ActionResult ReservationCompleted() {
}
Code snippet 5-42.txt

If you want ReservationCompleted() to be exposed as a standard synchronous method, it needs to be moved to a synchronous controller class, or the method name must be changed. You can restore the original action name using an alias:

[ActionName("ReservationCompleted")]
public ActionResult SomeOtherName() {
}
Code snippet 5-43.txt

If your asynchronous action method calls a service that exposes methods using the BeginMethod()/EndMethod() pattern (http://msdn.microsoft.com/en-us/library/ms228963.aspx), your callback will be executed on a thread that is not under the control of ASP.NET. Some consequences of this are that HttpContext.Current will be null, and there will be race conditions accessing members like AsyncManager.Parameters. To restore HttpContext.Current and eliminate the race condition, call AsyncManager.Sync() from within your callback.

public void NewsAsync(string city) {
    AsyncManager.OutstandingOperations.Increment();
    NewsService newsService = new NewsService();
    newsService.BeginGetNews(city, ar => {
        AsyncManager.Sync(() => {
            AsyncManager.Parameters["news"] =
                newsService.EndGetNews(ar);
            AsyncManager.OutstandingOperations.Decrement();
        });
    }, null);
}
Code snippet 5-44.txt

Alternatively, you can use the AsyncManager.RegisterTask() extension method from MVC Futures, which handles synchronization on your behalf. It also handles incrementing and decrementing the OutstandingOperations counter so that you don't have to.

public void NewsAsync(string city) {
    NewsService newsService = new NewsService();
    AsyncManager.RegisterTask(
        callback =>
            newsService.BeginGetNews(city, callback, null),
        ar => { AsyncManager.Parameters["news"] =
                    newsService.EndGetNews(ar); }
    );
}
Code snippet 5-45.txt

Be sure to reference the Futures assembly and import the Microsoft.Web.Mvc namespace if you want to use this extension method.

You can call AsyncManager.Finish() to force the ActionCompleted() method to be called, even before the OutstandingOperations counter has reached zero.

Html.Action() and Html.RenderAction() can target asynchronous action methods, but they will execute synchronously. That is, the thread servicing the request will not be released back to the thread pool between the calls to ActionAsync() and ActionCompleted().

Note

Remember to drop the Async suffix when passing the action name parameter to Html.Action() or Html.RenderAction().

Passing Data to Actions: The Model Binders

Model Binders are a little bit of magic in the framework that allow you, the developer, following a dash of convention, to work with objects when posting data to your action methods, rather than manually extracting data from the Request.Form collection. Effectively, your form will post an object to an action method.

To see this in action, follow these steps:

  1. Define a simple class here named Product similar to what you would find in a Northwind database, except that you'll make yours extremely simple for the purposes of demonstration. It only has two properties:

    public class Product
    {
      public string ProductName {get;set;}
      public double UnitPrice {get; set;}
    }
    Code snippet 5-46.txt
  2. Add a Controller with a simple action that allows you to view the lone product. Add the lone product to the ViewData and then return the View:

    public class ProductController : Controller
    {
    
      public ActionResult Edit()
      {
        Product product = new Product();
        product.ProductName = "Hanselman Cheese!";
        product.UnitPrice=5.00M;
        return View(product);
      }
    }
    Code snippet 5-47.txt
  3. Now, create an Edit view which is strongly typed to the Product class. You'll have it render a form that displays the current values:

    <p><%: ViewData["Message"] %></p>
    <% using (Html.BeginForm()) { %>
        <p>
            Name: <%: Html.TextBoxFor(model => model.ProductName) %>
            <%: Html.ValidationMessageFor(model => model.ProductName) %>
        </p>
        <p>
            Unit Price: <%: Html.TextBoxFor(model => model.UnitPrice)%>
            <%: Html.ValidationMessageFor(model => model.UnitPrice) %>
       </p>
       <input type="submit" />
    <% } %>
    Code snippet 5-48.txt

    Note

    Notice that we're using strongly typed HTML Helpers (new in ASP.NET MVC 2) to write out our input textboxes and validation messages. We'll look at those in more detail in Chapter 6. You're now ready to work with model binding.

Using UpdateModel to Update the Model

At this point, you should add another action method to respond to the form post. Note that by using the parameterless Html.BeginForm, the form will post to itself. However, you should avoid having logic in your Edit action to check for the postback (which is similar to checking Page.IsPostBack in Web Forms).

Fortunately, you can add another Edit action, which only responds to POST requests using the AcceptVerbsAttribute like so:

[HttpPost]
public ActionResult Edit(Product product)
{
  //...
}
Code snippet 5-49.txt

When two action methods have the same name, the action invoker will try to differentiate the methods based on the attributes applied to the method. Thus, the initial request for /Home/Edit does not match the attribute applied on the second Edit method, so the first one, which has no attribute, will respond. When a POST request is made, the second action method will respond, because it is an exact match for the POST HTTP verb.

Another thing very much worth mentioning here is the action parameter, which is the type of the object we're trying to bind: Product.

When posting a form to this action, the default ModelBinder will do its best to associate values found in Request.Form, Request.QueryString, and Request.Cookies to the properties of Product. There's a lot more to this, of course, and we'll talk about that later in this chapter.

To use the Model Binders to auto-magically bind your Model object, you simple "ask" for it as a parameter to your action:

[HttpPost]
public ActionResult Edit(Product product)
{
  if(ModelState.IsValid){

    //simulate save to the DB
    db.SaveChanges(product);

    ViewData["Message"] = product.ProductName + " Updated";
    return RedirectToAction("Edit");
  }
else
  {
    return View(product);
  }
}
Code snippet 5-50.txt

There are several things to notice about this code:

  1. The Product passed to the action will already be populated by the default ModelBinder, using the values from the Request, if possible.

  2. If the values can't be assigned, the ModelState is set as invalid (IsValid=false) — you'll always want to check this before doing any database work.

  3. If the state is Invalid, you can pass the data back to the View, which will show any validation errors based on the current ModelState (more on this in Chapter 7).

Note that redirecting the request only upon success technically violates the PRG pattern, but because no changes were made to the state of anything when validation failed, it doesn't suffer from most of the problems with not following the PRG pattern. Phil likes to call this loose PRG as opposed to strict PRG.

Validation with Data Annotations

Validation in ASP.NET 1.0 was done by implementing the IDataErrorInfo and writing imperative validation logic. ASP.NET MVC 2 simplifies this quite a bit with Data Annotation-based model validation. Let's retrofit the Product class you used in the previous section to implement a few simple validation rules:

public class Product {
      [Required(ErrorMessage = "The product name must not be empty.")]
      public string ProductName { get; set; }
      [Range(0, double.MaxValue,
             ErrorMessage="The unit price must be larger than 0.00.")]
       public double UnitPrice { get; set; }
}
Code snippet 5-51.txt

Now, if you type −1 for the unit price, you'll see a specific error message in the form.

We'll cover model validation in depth in Chapter 13.

A Word about User Input

We touch on this in Chapter 9 when discussing security, but it bears repeating: Never trust data that comes from your users, ever. Odds are never on your side, and over the life of your application, these input forms will receive stuff could only have originated from twisted hackers hiding in a secret volcano lair. Everything is out to get your app, and it's only a matter of time before it does.

You can help your application in this sense by coding defensively — or assuming that half your users are out to get you. Some specific ways you can do this are:

  • Use whitelists for bound input. You can declare explicitly which values that you want the Model Binders to set for you. You can do this using Include= or Exclude= followed by the names of the controls you wish to bind (there's more on this in Chapter 9).

  • Sanitize the user's input. You can't rely on model binders, however, to sanitize the user's input. If a user enters something like <script>alert('this site suxors!')</script> (or worse), that is exactly what would be bound to your object and sent into the database. This is by design, because many sites allow you to add a certain amount of HTML as part of their feature set (forums, blogs, etc.).

Regarding the second point, sanitizing user input needs to happen on the way back from the database, as opposed to the way in, and as Chapter 9 discusses (but it bears repeating), you should always HTML-encode data that is destined for display to an end user.

One handy way of doing this is sanitizing the data in the Controller. I know, I know! Most people think this is a View's concern — and they would be right. You can quite easily use the HTML Helpers to do this for you:

Html.Encode(ViewData.Model.SuspectData);
Code snippet 5-52.txt

This works in most cases; however, if you're using JSON to work with asynchronous callbacks, you may have an issue on your hands. There are a number of ways to sanitize HTML using JavaScript — including jQuery's .html method and JavaScript's escape method. However, these are a bit heavy-handed and can encode the data in ways that make it unreadable.

For instance, if you use escape on the string "I'm a perfectly safe string":

<div id="escapedDiv"></div>
<script type="text/javascript">
  document.getElementById("escapedDiv ").innerHTML
    = escape("I'm a perfectly safe string");
</script>
Code snippet 5-53.txt

the text you end up with is:

I%27m%20a%20perfectly%20safe%20string
Code snippet 5-54.txt

Eek. That's not very nice to try to read — and there are ways out. You might end up searching the Web for an encoding routine, and next thing you know you've wasted 10 minutes on this.

A different way to handle this encoding issue is by using HttpUtility.HtmlEncode with a JsonResult from your Controller. In this example, you'll use Northwind's Product table:

public ActionResult GetProducts()
{
  using(Northwind.DataContext db = new Northwind.DataContext())
  {
    var result = from p in db.Products
      select new
      {
        Name = HttpUtility.HtmlEncode(p.ProductName),
        Price = p.UnitPrice,
        Description = HttpUtility.HtmlEncode(p.Description),
      };
    return Json(result);
  }
}
Code snippet 5-55.txt

Note

We the authors are using a different class (JsonProduct) to return the serialized Product results — this is a local class we're using so that the JSON serializer doesn't choke on the nullable properties of the Product class.

This will return some nicely escaped data that will help you defend against cross-site scripting attacks.

Summary: Never, ever trust user input. You never know when one of your users will have a name like <script>alert('this site sucks')</script>.

SUMMARY

Controllers are the conductors of an MVC application, tightly orchestrating the interactions of the user, the Model objects, and the Views. They are responsible for responding to user input, manipulating the appropriate Model objects, and then selecting the appropriate View to display back to the user in response to the initial input.

In this chapter, you saw how these responsibilities are met by the ASP.NET MVC Framework's implementation of Controllers. User input is routed via the ASP.NET Routing system to the appropriate Controller. The Controller, via its ControllerActionInvoker, identifies the correct action method to call and invokes that method. The action method, in turn, manipulates the Model, which may correspond to one or more Model objects, and then populates the ViewData before selecting and returning a specific View.

All this should arm you with plenty of information to exercise tight control over your next web application project.

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

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