Chapter 5 - Controllers --- Professional ASP.NET MVC 1.0

Overview

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 with one of the core elements of the three-sided pattern that is MVC.

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, and 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 provides 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);}

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ðNewðASP.NET MVC Web Application) and then a new a class in the "Controllers" folder (note: this should be a normal class file, not a new Controller) named SimpleController.

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 groundbreaking, 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>");    }}

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 this author's machine it's http://localhost:61353/simple. Figure 5-1 shows the result.

Image from book
Figure 5-1

Apart from the large font, 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; }}

Ignoring the IsReusable property for a moment, IController and IHttpHandler are pretty much equivalent in terms of responsibility. The IController.Execute method and the 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 Form 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.

Important 

Product Team Aside

The ASP.NET MVC product team debated removing the IController interface completely. Developers who wanted to implement that interface could implement their own implementation of MvcHandler instead, which decidedly handles a lot of the core execution mechanics based on the request coming in from Routing.

We decided to leave it in, however, because other features of the ASP.NET MVC framework (IControllerFactory and ControllerBuilder) can work with the interface directly — which provides added value to developers.

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 web site.

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.

Important 

Product Team Aside

Upon reading that every public method of your controller class is publicly callable from the Web, you might have a gut reaction concerning the security of such an approach. The product team had a lot of internal and external debate around this.

Originally, each action method required that an attribute, ControllerActionAttribute, be applied to each callable method. However, many felt this violated the DRY principle (don't repeat yourself). It turns out that the concern over these methods being web callable has to do with a disagreement of what it means to "opt in."

As far as the product team is concerned, there are multiple levels of opting in before a method is truly web callable. The first level that you need to have opted into is an ASP. NET MVC project. If you add a public Controller class to a standard ASP.NET Web Application project, that class is not going to suddenly be web callable (although adding such a class to an ASP.NET MVC project is likely to make it callable). You would still need to define a route with a route handler (such as the MvcRouteHandler) that corresponds to that class.

The general consensus here is that by inheriting from Controller, you've opted in to this behavior. You can't do that by accident. And even if you did, you would still have to define routes that correspond to that class.

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ðController, then name it Simple2Controller. Next, add the following code:

using System;using System.Web;using System.Web.Mvc;public class Simple2Controller : Controller{    public void Hello()    {        Response.Write("<h1>Hello World Again!</h1>");    }}

Press Ctrl+F5 (or DebugðRun) and navigate to /simple2/hello in the browser. You should see Figure 5-2.

Image from book
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

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));    }}

This method is callable via the URL:

/simple2/goodbye?name=World

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 (discuss in Chapter 4). For example, the following URL is more aesthetically pleasing to many developers and Internet users:

/simple2/goodbye/world
Important 

Product Team Aside

Many developers would also consider the second approach to be more search engine—friendly, but this isn't necessarily the case. Modern search engines do read the query string and in this example, the URL with the query string actually provides more information.

Usually, when we're talking about optimizing for search engines (Search Engine Optimization, or SEO) issues surrounding URLs, we're talking about URLs that pass in opaque identifiers in the query string such as:

/products/view.aspx?id=45434

which tells us nothing compared to:

/products/view/shoes

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ðNew) is already set up for you and contains a pretty common URL pattern: {controller}/{action}/{id}.

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));    }}

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
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));}

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

/simple2/distance?x2=1&y2=2&x1=0&y1=0

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" });

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

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

 
 

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:

public abstract class ActionResult{   public abstract void ExecuteResult(ControllerContext context);}

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 };}

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);}

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 the following table. Each type is type is discussed in more detail in the sections that follow.

b24-bluearrow.gif Open table as spreadsheet

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

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 write a binary response to the stream. Useful for returning files to the user.

FilePathResult

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

FileContentResult

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

FileStreamResult

Derives from FileResult and returns writes a stream to the response.

JavaScriptResult

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

EmptyResult

As the name implies, this result 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

This result 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

This result is very similar to the ContentResult except that it is used to write binary content (for example, 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

This result 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.

Like the 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);}}
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);}

This would be called by the following code:

<%= Ajax.ActionLink("click", "DoSomething", new AjaxOptions()) %><div id=" some-div"></div>

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

RedirectResult

This result 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 an 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

This result 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

This 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();}

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.

The following table lists the existing methods and which type they return.

b24-bluearrow.gif Open table as spreadsheet

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 execute 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);}

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 action method and uses that internally as the ActionResult.

One thing to keep in mind is that 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));}

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

The following table highlights the various implicit conversions you can expect when writing action methods that do not have a return type of ActionResult.

b24-bluearrow.gif Open table as spreadsheet

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.

Important 

Product Team Aside

The code to create a ContentResult instance is encapsulated in a virtual method CreateActionResult. For those who want to return a different implicit action result type, simply derive from ControllerActionInvoker and override that method.

One example might be to have return values from action methods automatically be wrapped by a JsonResult.

 
_.gif
 

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 ASP.NET MVC request processing chain: the thing that actually invokes the action you're calling — the ActionInvoker. 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:

  • Locates the action method to call.

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

  • Invokes the action method and all of its filters.

  • 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}

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

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, 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();}

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);}

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 implementation of this base attribute, the AcceptVerbsAttribute and the NonActionAttribute.

AcceptVerbsAttribute

This is a concrete implementation of ActionSelectorAttribute which uses the current HTTP request's HTTP method (aka 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.

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

[AcceptVerbs(HttpVerbs.Get)]public ActionResult Edit(string id){  return View();}[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(string id, FormCollection form){  //Save the item and redirect...}

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.

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

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

The following table describes the locations that the invoker looks for parameter values.

b24-bluearrow.gif Open table as spreadsheet

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.

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;}}
  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;    ViewData["product"] = product;    return View();  }}
  3. Now, create the Edit view. You'll have it render a form that displays the current values.

    <p><%= Html.Encode(ViewData["Message"]) %></p><% using (Html.BeginForm()) { %>    <p>        Name: <%= Html.TextBox("ProductName") %>        <%= Html.ValidationMessage("ProductName") %>    </p>    <p>        Unit Price: <%= Html.TextBox("UnitPrice")%>        <%= Html.ValidationMessage("UnitPrice") %>    </p>    <input type=" submit" /><% } %>

Notice with this example that you're leaning on convention again by naming your HTML inputs with the same name as the Model properties you're working with. There's not much more trickery here — it's just that simple! 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:

[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(Product product){  //...}

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:

[AcceptVerbs(HttpVerbs.Post)]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  {    ViewData["product"] = product;    return View();  }}

There are a number of 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 on 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."

Important 

Product Team Aside

PRG stands for Post-Redirect-Get, and it describes an interaction pattern for web sites when posting a form to the server.

The problem it solves is probably something you've run into many times. When submitting a form to the server, if the server renders HTML in response to that form post, pressing the Refresh button causes the browser to attempt to resubmit the form. At that point, a browser dialog with some gobbledygook about reposting the form appears to the end user. Technical people like you and me understand it, but the majority of non-technical people out there don't have a clue what it means. Worse, if they go through with the form submission, they may have taken the same action twice. This is a common occurrence with postbacks.

With PRG, you never render HTML in response to a post, instead you issue a redirect to the browser, which then issues a GET — hence Post-Redirect-Get. This avoids the aforementioned usability problems.

Using Validation with Model Binders

The default model binder supports the use of objects that implement IDataErrorInfo. This provides a way for your objects that are being passed into an action method to perform simple validation and supply simple error messages.

Let's retrofit the Product class you used in the previous section to implement IDataErrorInfo. This interface contains two properties, one is an indexer that accepts the column or property name; the other is a string containing the general error message if any errors occur. In the following implementation, the Product class stores errors within a private dictionary, using the property name as the key:

using System.ComponentModel;public class Product : IDataErrorInfo {  Dictionary<string, string> _errors = new Dictionary<string, string>();  public string ProductName {    get {      return _productName;    }    set {      if (!String.IsNullOrEmpty(value)) {        _productName = value;        return;      }      _errors.Add("ProductName", "The product name must not be empty.");    }  }  string _productName;  public double UnitPrice {    get {      return _unitPrice;    }    set {      if (value > 0.00) {        _unitPrice = value;        return;      }      _errors.Add("UnitPrice", value + "is not valid. The unit price must belarger than 0.00.");    }  }  double _unitPrice;  public string Error {    get {      if (_errors.Count == 0) {        return null;      }      return "There were some errors in creating this product.";    }  }  public string this[string columnName] {    get {      string error;      if (_errors.TryGetValue(columnName, out error)) {        return error;      }      return null;    }  }}

Now, if you type -1 for the unit price, you'll see a more specific error message in the form. This provides a very simple, but limited, means of validation with model binders. In future versions of ASP.NET MVC, there will be richer means to provide validation using metadata and so forth.

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 you could never imagine from places that only exist in the dark corners of Hades. 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 every user is secretly hosting a demon that wants to claim your soul for its master. Some specific ways you can do this are:

  • Use whitelists for bound input. You can declare explicitly which values that you want the ModelBinders 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 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);

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>

The text you end up with is:

I%27m%20a%20perfectly%20safe%20string

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);  }}
Note 

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>.

 
_.gif
 

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.

_.gif
posted on 2010-06-26 13:20 Yan-Feng 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/yan-feng/archive/2010/06/26/1765750.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值