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.
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.
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.
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
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.
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
.
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.
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.
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
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
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.
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
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
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.
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
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.
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 |
---|---|
Represents a null or empty response. It doesn't do anything. | |
| Writes the specified content directly to the response as text. |
| Serializes the objects it is given into JSON and writes the JSON to the response. |
| Redirects the user to the given URL. |
| Redirects the user to a URL specified via Routing parameters. |
| Calls into a View engine to render a View to the response. |
| This is similar to |
| 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. |
| Derives from |
| Derives from |
| Derives from |
| Used to execute JavaScript code immediately on the client sent from the server. |
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.
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.
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.
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 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.
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.
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
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
.
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
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.
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
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 |
Void | The action invoker treats the action method as if it returned null, and thus an |
Object (anything other than | The action invoker calls |
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.
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.
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.
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.
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
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.
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.
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 |
Request.QueryString collection | These are name/value pairs appended to the URL. |
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.
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.
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.
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.
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.
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.
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.
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).
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 Sample
Async()
and Sample
Completed()
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.
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
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()
.
Remember to drop the Async suffix when passing the action name parameter to Html.Action()
or Html.RenderAction()
.
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:
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
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
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
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.
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:
The Product passed to the action will already be populated by the default ModelBinder, using the values from the Request, if possible.
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.
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 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.
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
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>
.
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.