Chapter 3 – Understanding Controllers

This is a rough draft of a chapter from the book ASP.NET MVC Framework Unleashed by Stephen Walther. Comments are welcome and appreciated. When the book is published, the text from this blog entry will be removed and only the code listings will remain.

Order this Book from Amazon

ASP.NET MVC controllers are responsible for controlling the flow of application execution. When you make a browser request against an ASP.NET MVC application, a controller is responsible for returning a response to that request.

Controllers expose one or more actions. A controller action can return different types of action results to a browser. For example, a controller action might return a view, a controller action might return a file, or a controller action might redirect you to another controller action.

In this chapter, you learn how to create controllers and controller actions. You learn how to return different types of controller action results. You also learn how to use attributes to control when a particular controller action gets invoked. We complete this chapter by discussing how you can write unit tests for your controllers and actions.

Creating a Controller

The easiest way to create a controller is to right-click the Controllers folder in the Visual Studio Solution Explorer window and select the menu option Add, Controller. Selecting this menu option displays the Add Controller dialog (see Figure 1). If you enter the name ProductController then you will get the code in Listing 1.

*** Begin Warning ***

A controller name must end with the suffix Controller. If you forget to include the Controller suffix then you won’t be able to invoke the controller.

*** End Warning ***

Figure 1 – The Add Controller dialog

clip_image002

Listing 1 – ControllersProductController.cs [C#]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        //
        // GET: /Product/

        public ActionResult Index()
        {
            return View();
        }

    }
}

 

Listing 1 – ControllersProductController.vb [VB]

Public Class ProductController
    Inherits System.Web.Mvc.Controller

    '
    ' GET: /Product/

    Function Index() As ActionResult
        Return View()
    End Function

End Class

Notice that a controller is just a class (a Visual Basic or C# class) that inherits from the base System.Web.Mvc.Controller class.

Any public method exposed by a controller is exposed as a controller action. The controller class in Listing 1 exposes one action named Index(). The Index() action is the default action that is invoked on a controller when no explicit action is specified.

*** Begin Warning ***

By default, any public method contained in a controller class can be invoked by anyone located anywhere on the Internet. Be careful about the methods that you publically expose from a controller. If you want to prevent a public controller method from being invoked, you can decorate the method with the NonAction attribute.

*** End Warning ***

Notice that the Index() action returns an ActionResult. A controller action always returns an ActionResult (even if it doesn’t appear to be returning an ActionResult). The ActionResult determines the response returned to the browser. The Index() controller returns a view as its ActionResult.

A controller typically exposes multiple actions. You add actions to a controller by adding new methods to the controller. For example, the modified Product controller in Listing 2 exposes three actions named Index(), Help(), and Details().

Listing 2 – ControllersProductController.cs with additional methods [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class ProductController : Controller
    {
        //
        // GET: /Product/

        public ActionResult Index()
        {
            return View();
        }

        //
        // GET: /Product/Help

        public ActionResult Help()
        {
            return View();
        }

        //
        // GET: /Details/1

        public ActionResult Details(int Id)
        {
            return View();
        }

    }
}

Listing 2 – ControllersProductController.vb with additional methods [VB]

Public Class ProductController
    Inherits System.Web.Mvc.Controller

    '
    ' GET: /Product/

    Function Index() As ActionResult
        Return View()
    End Function

    '
    ' GET: /Product/Help

    Function Help() As ActionResult
        Return View()
    End Function

    '
    ' GET: /Details/1

    Function Details(ByVal id As Integer) As ActionResult
        Return View()
    End Function

End Class

Here’s what you would type into a browser address bar to invoke the different actions:

· /Product/Index — Invokes the ProductController Index() action.

· /Product — Invokes the ProductController Index() action.

· /Product/Help — Invokes the ProductController Help() action.

· /Product/Details/34 — Invokes the ProductController Details() action with the value 34 for the Id parameter.

You invoke a controller action by following a particular pattern that looks like this:

{controller}/{action}/{id}

Notice that when you invoke a controller, you don’t include the Controller suffix in the URL. For example, you invoke the Product controller with the URL /Product/Index and not the URL /ProductController/Index.

The default controller action is the Index() action. Therefore, the URL /Product/Index and the URL /Product both invoke the product controller Index() action.

When you invoke a controller, you can supply an optional Id parameter. For example, the Details() action accepts an Id parameter. The URL /Product/Details/2 invokes the Details() action and passes the value 2 for the Id parameter. The name of the parameter is important. You must name the parameter Id.

*** Begin Note ***

The default pattern for invoking controller actions is defined by the default route in the Global.asax file. If you want to modify the URL pattern for invoking actions then you can modify this default route. To learn more about creating custom routes, see Chapter 9, Understanding Routing.

*** End Note

Returning Action Results

A controller action always returns an ActionResult. The ASP.NET MVC framework includes the following types of ActionResults:

· ViewResult – Represents an ASP.NET MVC view.

· PartialViewResult – Represents a fragment of an ASP.NET MVC view.

· RedirectResult – Represents a redirection to another controller action or URL.

· ContentResult – Represents raw content sent to the browser.

· JsonResult – Represents a JavaScript Object Notation result (Useful in Ajax scenarios).

· FileResult – Represents a file to be downloaded.

· EmptyResult – Represents no result returned by an action.

· HttpUnauthorizedResult – Represents an HTTP Unauthorized status code.

· JavaScriptResult – Represents a JavaScript file.

· RedirectToRouteResult – Represents a redirection to another controller action or URL using route values.

Typically, you don’t directly return an ActionResult from a controller action. Instead, you call a controller method that returns an ActionResult. For example, if you want to return a ViewResult then you call the controller View() method.

Here’s a list of controller methods that return ActionResults:

· View() – Returns a ViewResult.

· PartialView() – Returns a PartialViewResult.

· RedirectToAction() – Returns a RedirectToRouteResult .

· Redirect() – Returns a RedirectResult.

· Content() – Returns a ContentResult.

· Json() – Returns a JsonResult.

· File() – Returns a FileResult.

· JavaScript() – Returns a JavaScriptResult.

· RedirectToRoute() – Returns a RedirectToRouteResult.

In the following sections, we’ll examine several of these ActionResults in more detail.

*** Begin Note ***

We examine partial view results (AKA view user controls or partials) in Chapter 10, Understanding View Master Pages and View User Controls.

*** End Note ***

Returning a View Result

The most common ActionResult returned by a controller action is a ViewResult. A ViewResult represents an ASP.NET MVC view. You return a ViewResult when you want to return HTML to the browser.

The Details() action exposed by the Customer controller in Listing 3 returns a ViewResult.

Listing 3 – ControllersCustomerController.cs [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class CustomerController : Controller
    {

        public ActionResult Details()
        {
            return View();
        }

    }
}

Listing 3 – ControllersCustomerController.vb [VB]

Public Class CustomerController
    Inherits System.Web.Mvc.Controller

    Function Details() As ActionResult
        Return View()
    End Function

End Class

The Details() method calls the View() method to return a ViewResult. There are two ways that you can specify a view when calling the View() method: you can specify a view implicitly or explicitly.

In Listing 3, the name of the view is specified implicitly. The ASP.NET MVC framework determines the name of the view from the name of the action. In this case, the action returns a view at the following location:

ViewsCustomerDetails.aspx

The ASP.NET MVC framework follows this pattern to determine the location of a view:

Views{controller}{action}.aspx

If you prefer, you can specify the name of a view explicitly. In Listing 4, the View() method includes an explicit view name.

Listing 4 – ControllersCustomerController.cs with explicit view [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class CustomerController : Controller
    {

        public ActionResult Details()
        {
            return View("Details");
        }

    }
}

Listing 4 – ControllersCustomerController.vb with explicit view [VB]

Public Class CustomerController
    Inherits System.Web.Mvc.Controller

    Function Details() As ActionResult
        Return View("Details")
    End Function

End Class

The View() method in Listing 4 returns the very same view. However, it is explicit about the view name. Notice that you don’t include the .aspx extension when providing the name of the view.

*** Begin Tip ***

If you plan to build unit tests for your ASP.NET MVC application then it is a good idea to be explicit about your view names. Otherwise, you cannot test to see if the view with the right view name has been returned from a controller action.

*** End Tip ***

A view name can contain a relative or absolute path. If you specify a relative path then the location of the view is calculated relative to its normal location. For example, calling View(“Subfolder/Details”) from the Details() action would return a view from this location:

ViewsDetailsSubfolderDetails.aspx

You also can provide an absolute path to a view. If you call View(“~/Details.aspx”) from the Details() action then a view from the following location is returned:

Details.aspx

Notice that when you provide an absolute path, you provide the .aspx extension.

There are multiple overloads of the View() method that accept different parameters. Here is a list of all of the possible parameters that you can pass to the View() method:

· viewName – The name of the view (or path to the view).

· masterName – The name of a view master page.

· model – The model class passed to the view.

We discuss view master pages in Chapter 10, Understanding View Master Pages and View User Controls. We discuss passing models to views in the next chapter, Understanding Views.

Returning a Redirect Result

Often, you’ll need to redirect from one controller action to a second controller action. You can use the RedirectToAction() method to return a RedirectResult that redirects a user from one controller action to another.

For example, the Widget controller in Listing 5 contains a Details() action. If the Details() action is invoked without a value for the id parameter, then the user is redirected to the Index() action.

Listing 5 – ControllersWidgetController.cs [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class WidgetController : Controller
    {
        //
        // GET: /Widget/

        public ActionResult Index()
        {
            return View();
        }

        //
        // POST: /Widget/Create

        public ActionResult Details(int? id)
        {
            if (!id.HasValue)
                return RedirectToAction("Index");

            return View();
        }

    }
}

Listing 5 – ControllersWidgetController.cs [VB]

Public Class WidgetController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
        Return View()
    End Function

    Function Details(ByVal id As Integer?) As ActionResult
        If Not id.HasValue Then
            Return RedirectToAction("Index")
        End If

        Return View()
    End Function

End Class

*** Begin Note ***

The id parameter in Listing 5 is a nullable type. A nullable integer can have any value of an integer or the value null. You create a nullable type by placing a question mark ? after the type keyword.

*** End Note ***

There are multiple overloads of the RedirectToAction() method. Here’s a list of all of the possible parameters that you can use with the RedirectToAction() method:

· actionName – The name of a controller action.

· controllerName – The name of a controller.

· routeValues – The route values passed to the action.

You can use the controllerName parameter to redirect from an action in one controller to another controller. When you specify the controllerName, you do not include the Controller suffix. For example, use Product and not ProductController like this:

[C#]

return RedirectToAction(“Index”, “Product”);

[VB]

Return RedirectToAction(“Index”, “Product”)

Providing a value for routeValues is particularly important when you need to pass an id to an action. For example, imagine that you want to redirect to the Details() action from another action and pass a value for the id parameter. In that case, you can call the RedirectToAction() method like this:

[C#]

return RedirectToAction(“Details”, new {id=53});

[VB]

Return RedirectToAction(“Details”, New With {.id=53})

This call to the RedirectToAction() method passes the value 53 as the id parameter to the Index() action.

*** Begin Note ***

The RedirectToAction() method returns a 302 Found HTTP status code to the browser to perform the redirect to the new action. One advantage of performing a browser redirect is that it updates the browser address bar with the new URL.

*** End Note ***

Returning a Content Result

The Say() action exposed by the Hello controller in Listing 6 does not return an ActionResult. Instead, the action returns a string. If you invoke this action then the string is rendered to your browser (see Figure 2).

Figure 2 – Results of invoking Say() action

clip_image004

Listing 6 – ControllersHelloController.cs [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HelloController : Controller
    {

        public string Say()
        {
            return "Hello";
        }

    }
}

Listing 6 – ControllersHelloController.vb [VB]

Public Class HelloController
    Inherits System.Web.Mvc.Controller

    Function Say() As String
        Return "Hello!"
    End Function

End Class

An action method can also return DateTime values, integer values, or any type of values from the .NET framework.

Behind the scenes, the ASP.NET MVC framework converts any value that is not an ActionResult into an ActionResult. In particular, the ASP.NET MVC framework converts any value that is not an ActionResult into a ContentResult. The ASP.NET MVC framework calls the ToString() method on the value and wraps the resulting value in a ContentResult.

If you prefer, you can explicitly return a ContentResult like this:

[C#]

public ActionResult Say()
{
  return Content("Hello!");
}

[VB]

Function Say() As ActionResult
   Return Content("Hello!")
End Function

 

There are multiple overloads of the Content() method. Here is a list of all of the possible parameters that you can pass to this method:

· string – The string to render to the browser.

· contentType – The MIME type of the content (defaults to text/html).

· contentEncoding – The text encoding of the content (for example, Unicode or ASCII).

Returning a JSON Result

JavaScript Object Notation (JSON) was invented by Douglas Crockford as a lightweight alternative to XML appropriate for sending data across the Internet in AJAX applications. For example, you can convert a set of database records into a JSON representation and pass the data from the server to the browser.

*** Begin Note ***

You can learn more about JSON by visiting JSON.org.

*** End Note ***

You return JSON from an action by calling the Json() method. For example, the controller in Listing 7 returns a collection of quotations.

Listing 7 – ControllersQuotationController.cs [C#]

using System.Collections.Generic;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class QuotationController : Controller
    {

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult List()
        {
            var quotes = new List<string>
            {
                "Look before you leap",
                "The early bird gets the worm",
                "All hat, no cattle"
            };

            return Json(quotes);
        }

    }
}

Listing 7 – ControllersQuotationController.vb [VB]

Public Class QuotationController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
        Return View()
    End Function

    Function List() As ActionResult
        Dim quotes As New List(Of String)()
        quotes.Add("Look before you leap")
        quotes.Add("The early bird gets the worm")
        quotes.Add("All hat, no cattle")

        Return Json(quotes)
    End Function

End Class

*** Begin Note ***

Behind the scenes, the Json() method uses a class in the .NET framework called the JavaScriptSerializer class to serialize an object into a JSON representation. You can control how this class serializes objects by registering custom converters.

*** End Note ***

When the List() action is invoked, the action returns the following JSON representation of the collection of quotations:

[“Look before you leap”, “The early bird gets the worm”, “All hat, no cattle”]

You can invoke the Index() method from a view by performing an Ajax call against the server. The view in Listing 8 grabs the collection of quotations and randomly displays one of them.

Figure 3 – Using JSON to retrieve quotations.

clip_image006

Listing 8 – ViewsQuotationIndex.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

  <script src="../../Scripts/jquery-1.2.6.js" type="text/javascript"></script>

  <script type="text/javascript">

      $(getQuote);

      function getQuote() {
          $.getJSON("Quotation/List", showQuote);
      }

      function showQuote(data) {
          var index = Math.floor(Math.random() * 3);
          $("#quote").text(data[index]);
      }

  </script>

  <p id="quote"></p>

  <button onclick="getQuote()">Get Quote</button>

</asp:Content>

*** Begin Note ***

The view in Listing 8 uses jQuery to retrieve the JSON result from the server. We discuss jQuery in detail in Chapter 17, Using jQuery.

*** End Note ***

The Json() method has several overloads and supports the following parameters:

· data – The content to serialize.

· contentType – The MIME type of the content (defaults to application/json).

· contentEncoding — The text encoding of the content (for example, Unicode or ASCII).

Returning a File Result

You can return a file from an action. For example, you can return an image file, a Microsoft Word file, or a Microsoft Excel file.

For example, the controller in Listing 9 exposes two actions named Index() and Download(). The Index() action displays a view with a link to the Download() action. When you click the link, you are prompted with a dialog to view or save the file (see Figure 4).

Figure 4 – Downloading a file

clip_image008

Listing 9 – ControllersContentManagerController.cs [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class ContentManagerController : Controller
    {

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Download()
        {

            return File("~/Content/CompanyPlans.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "CompanyPlans.docx");
        }

    }
}

Listing 9 – ControllersContentManagerController.vb [VB]

Public Class ContentManagerController
    Inherits System.Web.Mvc.Controller

    Function Index()
        Return View()
    End Function

    Function Download() As ActionResult
        Return File("~/Content/CompanyPlans.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "CompanyPlans.docx")
    End Function

End Class

The Download() action returns a Microsoft Word document named CompanyPlans.docx. Notice that the File() method requires three parameters: the path to the file, the content type of the file, and the name of the file. The proper MIME type for a Microsoft Word DOCX file is:

application/vnd.openxmlformats-officedocument.wordprocessingml.document

The File() method has multiple overloads and accepts the following parameters:

· filename – The path to the file to download.

· contentType – The MIME type of the file to download.

· fileDownloadName – The name of the file as it will appear in the browser dialog.

· fileContents – Instead of providing the path to the file to download, you can provide the actual file contents as a Byte array.

· fileStream – Instead of providing the path to the file to download, you can provide the actual file contents as a file stream.

*** Begin Note ***

The File() method uses the HTTP Content-Disposition header to set the file download name.

*** End Note ***

Controlling How Actions are Invoked

The default algorithm for how the ASP.NET MVC framework invokes actions is pretty simple. If you type /Product/Details, for example, then the Details() method of the ProductController class is executed.

However, things can quickly become more complicated. What happens when you have multiple methods with the same name? How do you invoke an action when posting form data but not otherwise? How do you invoke a particular action when an Ajax request is made?

In this section, you learn how to use the AcceptVerbs, ActionName, and ActionMethodSelector attributes to specify when a particular action gets invoked.

Using AcceptVerbs

The AcceptVerbs attribute enables you to prevent an action from being invoked unless a particular HTTP operation is performed. For example, you can use the AcceptVerbs attribute to prevent an action from being invoked unless an HTTP POST operation is performed.

The Employee controller in Listing 10 exposes two actions named Create(). The first Create() action is used to display an HTML form for creating a new employee. The second Create() action inserts the new employee into the database.

Both Create() methods are decorated with the AcceptVerbs attribute. The first Create() action can only be invoked by an HTTP GET operation and the second Create() action can only be invoked by an HTTP POST operation.

Listing 10 – ControllersEmployeeController.cs [C#]

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class EmployeeController : Controller
    {
        private EmployeeRepository _repository = new EmployeeRepository();

        // GET: /Employee/
        public ActionResult Index()
        {
            return View();
        }

        // GET: /Employee/Create
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Create()
        {
            return View();
        }

        // POST: /Employee/Create
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(Employee employeeToCreate)
        {
            try
            {
                _repository.InsertEmployee(employeeToCreate);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        // DELETE: /Employee/Delete/1
        [AcceptVerbs(HttpVerbs.Delete)]
        public ActionResult Delete(int id)
        {
            _repository.DeleteEmployee(id);
            return Json(true);
        }

    }
}

Listing 10 – ControllersEmployeeController.vb [VB]

Public Class EmployeeController
    Inherits System.Web.Mvc.Controller

    Private _repository As New EmployeeRepository()

    ' GET: /Employee/Create
    Function Index() As ActionResult
        Return View()
    End Function

    ' GET: /Employee/Create
    <AcceptVerbs(HttpVerbs.Get)> _
    Function Create() As ActionResult
        Return View()
    End Function

    ' POST: /Employee/Create
    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(ByVal employeeToCreate As Employee) As ActionResult
        Try
            _repository.InsertEmployee(employeeToCreate)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    ' DELETE: /Employee/Create
    <AcceptVerbs(HttpVerbs.Delete)> _
    Function Delete(ByVal id As Integer) As ActionResult
        _repository.DeleteEmployee(id)
        Return Json(True)
    End Function

End Class

Most people are familiar with HTTP GET and HTTP POST operations. You perform an HTTP GET operation whenever you request a page from a website by typing the address of the page in your web browser. You perform an HTTP POST operation when you submit an HTML form that has a method=”post” attribute.

Most people don’t realize that the HTTP protocol supports a number of additional types of HTTP operations:

· OPTIONS – Returns information about the communication options available.

· GET – Returns whatever information is identified by the request.

· HEAD – Performs the same operation as GET without returning the message body.

· POST – Posts new information or updates existing information.

· PUT – Posts new information or updates existing information.

· DELETE – Deletes information.

· TRACE – Performs a message loop back.

· CONNECT – Used for SSL tunneling.

*** Begin Note **

The HTTP operations are defined as part of the HTTP 1.1 standard which you can read about at http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html.

*** End Note ***

You can perform these additional HTTP operations when performing Ajax requests. The controller in Listing 10 includes a Delete() action that can be invoked only with an HTTP DELETE operation. The view in Listing 11 includes a delete link that uses Ajax to perform an HTTP DELETE operation.

Listing 11 – ViewsEmployeeDelete.aspx [C#]

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    <script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

    <h2>Index</h2>

    <%= Ajax.ActionLink
        (
            "Delete",   // link text
            "Delete",   // action name
            new {id=39}, // route values
            new AjaxOptions {HttpMethod="DELETE", Confirm="Delete Employee?"}
        ) %>

</asp:Content>

Listing 11 – ViewsEmployeeDelete.aspx [VB]

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    <script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

    <h2>Index</h2>

    <%=Ajax.ActionLink( _
        "Delete", _
        "Delete", _
        New With {.id = 39}, _
        New AjaxOptions With {.HttpMethod = "DELETE", .Confirm = "Delete Employee?"} _
    )%>

    <%=DateTime.Now%>

</asp:Content>

In Listing 11, the Ajax.ActionLink() helper renders a link that performs an HTTP DELETE operation. The link deletes the employee with Id 39. You can verify that the link performs an HTTP DELETE operation in Firebug (see Figure 5).

Figure 5 – Performing an HTTP DELETE operation

clip_image010

*** Begin Note ***

Firebug is an essential tool for debugging Ajax applications. Firebug is a Mozilla Firefox extension that you can download from http://getFirebug.com.

*** End Note ***

Using ActionName

The ActionName attribute enables you to expose an action with a different name than its method name. There are two situations in which the ActionName attribute is useful.

First, when a controller has overloaded methods, you can use the ActionName attribute to distinguish the two methods. In other words, you can use the ActionName attribute to expose two methods with the same name as actions with different names.

For example, imagine that you have created a Product controller that has two overloaded methods named Details(). The first Details() method accepts an id parameter and the second Details() method does not. In that case, you can use the ActionName attribute to distinguish the two Details() methods by exposing the two Details() methods with different action names.

Second, using the ActionName attribute is useful when a controller has methods with different names and you want to expose these methods as actions with the same name. For example, the controller in Listing 12 exposes two actions named Edit() that accept the same parameter.

Listing 12 – ControllersMerchandiseController.cs [C#]

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
    public class MerchandiseController : Controller
    {
        private MerchandiseRepository _repository = new MerchandiseRepository();

        // GET: /Merchandise/Edit
        [ActionName("Edit")]
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Edit_GET(Merchandise merchandiseToEdit)
        {
            return View(merchandiseToEdit);
        }

        // POST: /Merchandise/Edit
        [ActionName("Edit")]
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit_POST(Merchandise merchandiseToEdit)
        {
            try
            {
                _repository.Edit(merchandiseToEdit);
                return RedirectToAction("Edit");
            }
            catch
            {
                return View();
            }
        }
    }
}

Listing 12 – ControllersMerchandiseController.vb [VB]

Public Class MerchandiseController
    Inherits System.Web.Mvc.Controller

    Private _repository As New MerchandiseRepository()

    ' GET: /Merchandise/Edit
    <ActionName("Edit")> _
    <AcceptVerbs(HttpVerbs.Get)> _
    Function Edit_GET(ByVal merchandiseToEdit As Merchandise) As ActionResult
        Return View(merchandiseToEdit)
    End Function

    ' POST: /Merchandise/Edit
    <ActionName("Edit")> _
    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit_POST(ByVal merchandiseToEdit As Merchandise) As ActionResult
        Try
            _repository.Edit(merchandiseToEdit)
            Return RedirectToAction("Edit")
        Catch
            Return View()
        End Try
    End Function

End Class

You can’t have two methods with the same name and the same parameters in the same class. However, you can have two actions that have the same name and the same parameters.

The two Edit() actions in Listing 12 are distinguished by the AcceptVerbs attribute. The first Edit() action can be invoked only by an HTTP GET operation and the second Edit() action can be invoked only by an HTTP POST operation. The ActionName attribute enables you to expose these two actions with the same name.

Using ActionMethodSelector

You can build your own attributes that you can apply to controller actions to control when the controller actions are invoked. You build your own attributes by deriving a new attribute from the abstract ActionMethodSelectorAttribute class.

This is an extremely simple class. It has a single method that you must implement named IsValidForRequest(). If this method returns false, then the action method won’t be invoked.

You can use any criteria that you want when implementing the IsValidForRequest() method including the time of day, a random number generator, or the current temperature outside. The AjaxMethod attribute in Listing 13 is a more practical sample of how you can use the ActionMethod attribute. This attribute prevents a method from being called in cases in which the request is not an Ajax request.

Listing 13 – SelectorsAjaxMethodAttribute.cs [C#]

using System.Reflection;
using System.Web.Mvc;

namespace MvcApplication1.Selectors
{
    public class AjaxMethod : ActionMethodSelectorAttribute
    {
        public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
        {
            return controllerContext.HttpContext.Request.IsAjaxRequest();
        }
    }
}

Listing 13 – SelectorsAjaxMethodAttribute.vb [VB]

Imports System.Reflection

Public Class AjaxMethodAttribute
    Inherits ActionMethodSelectorAttribute

    Public Overrides Function IsValidForRequest(ByVal controllerContext As ControllerContext, ByVal methodInfo As MethodInfo) As Boolean
        Return controllerContext.HttpContext.Request.IsAjaxRequest
    End Function

End Class

The selector in Listing 13 simply returns the value of the IsAjaxRequest() method as its selection criterion.

The controller in Listing 14 illustrates how you can use the AjaxMethod attribute.

Listing 14 – ControllersNewsController.cs [C#]

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using MvcApplication1.Selectors;

namespace MvcApplication1.Controllers
{
    public class NewsController : Controller
    {
        private readonly List<string> _news = new List<string>();
        private Random _rnd = new Random();

        public NewsController()
        {
            _news.Add("Moon explodes!");
            _news.Add("Stock market up 200 percent!");
            _news.Add("Talking robot created!");
        }

        public ActionResult Index()
        {
            var selectedIndex = _rnd.Next(_news.Count);
            ViewData.Model = _news[selectedIndex];
            return View();
        }

        [AjaxMethod]
        [ActionName("Index")]
        public string Index_AJAX()
        {
            var selectedIndex = _rnd.Next(_news.Count);
            return _news[selectedIndex];
        }

    }
}

Listing 14 – ControllersNewsController.vb [VB]

Public Class NewsController
    Inherits System.Web.Mvc.Controller

    Private ReadOnly _news As New List(Of String)
    Private _rnd As New Random()

    Sub New()
        _news.Add("Moon explodes!")
        _news.Add("Stock market up 200 percent!")
        _news.Add("Talking robot created!")
    End Sub

    Function Index() As ActionResult
        Dim selectedIndex = _rnd.Next(_news.Count)
        ViewData.Model = _news(selectedIndex)
        Return View()
    End Function

    <AjaxMethod()> _
    <ActionName("Index")> _
    Function Index_AJAX() As String
        Dim selectedIndex = _rnd.Next(_news.Count)
        Return _news(selectedIndex)
    End Function

End Class

The controller in Listing 14 exposes two actions named Index(). The first Index() action is intended to be invoked by a normal browser request. The second action is intended to be invoked by an Ajax request.

The AjaxMethod attribute is applied to the second Index() action. If this action were not decorated with the AjaxMethod attribute then you would get an Ambiguous Match Exception because the ASP.NET MVC framework would not be able to decide which of the two actions to execute (see Figure 6).

Figure 6 – An Ambiguous Match Exception

clip_image012

The view in Listing 15 uses the Ajax.ActionLink() helper method to render a Get News link for displaying the news. If you are using an uplevel browser – a browser that supports basic JavaScript – then clicking the link performs an Ajax request against the server. The Index() method decorated with the AjaxMethod attribute is invoked and the page is updated without performing a postback.

If, on the other hand, you are using a downlevel browser – a browser that does not support basic JavaScript – then clicking the Get News link performs a normal postback. The page still gets updated with a news item, but the user must undergo the awful experience of a postback (see Figure 7).

Figure 7 – Displaying the news

clip_image014

Listing 15 – ViewsNewsIndex.aspx [C#]

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    <script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

    <%=Ajax.ActionLink("Get News", "Index", new AjaxOptions {UpdateTargetId = "news"})%>

    <span id="news"></span>

</asp:Content>

Listing 15 – ViewsNewsIndex.aspx [VB]

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    <script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

    <%=Ajax.ActionLink("Get News", "Index", New AjaxOptions With {.UpdateTargetId = "news"})%>

    <span id="news"></span>

</asp:Content>

Handling Unknown Actions

A controller has a special method named HandleUnknownAction(). This method is called automatically when a controller cannot find an action that matches a browser request. For example, if you request the URL /Product/DoSomethingCrazy and the Product controller does not have an action named DoSomethingCrazy() then the Product controller HandleUnknownAction() method is invoked.

By default, this method throws a 404 Resource Not Found HTTP exception. However, you can override this method and do anything you want. For example, the controller in Listing 16 displays a custom error message.

Listing 16 – ControllersCatalogController.cs [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class CatalogController : Controller
    {

        public ActionResult Create()
        {
            return View();
        }

        public ActionResult Delete(int id)
        {
            return View();
        }

        protected override void HandleUnknownAction(string actionName)
        {
            ViewData["actionName"] = actionName;
            View("Unknown").ExecuteResult(this.ControllerContext);
        }

    }
}

Listing 16 – ControllersCatalogController.vb [VB]

Public Class CatalogController
    Inherits System.Web.Mvc.Controller

    Function Create() As ActionResult
        Return View()
    End Function

    Function Delete() As ActionResult
        Return View()
    End Function

    Protected Overrides Sub HandleUnknownAction(ByVal actionName As String)
        ViewData("actionName") = actionName
        View("Unknown").ExecuteResult(Me.ControllerContext)
    End Sub

End Class

If you request the URL /Catalog/Create or /Catalog/Delete then the Catalog controller will return the Create or Delete view. If you request a URL that contains an unknown action such as /Catalog/Wow or /Catalog/Eeeks then the HandleUnknownAction() method executes.

In Listing 16, the HandleUnknownAction() method adds the name of the action to view data and then renders a view named Unknown (see Figure 8).

Figure 8 – Displaying the Unknown view

clip_image016

Testing Controllers and Actions

The ASP.NET MVC team worked hard to make sure that controller actions were extremely easy to test. If you want to test a controller action then you simply need to instantiate the controller and call the action method.

For example, the controller in Listing 17 exposes two actions named Index() and Details(). If you invoke the Details() action without passing a value for the id parameter then you should be redirected to the Index() action.

Listing 17 – ControllersPersonController.cs [C#]

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class PersonController : Controller
    {
        public ActionResult Index()
        {
            return View("Index");
        }

        public ActionResult Details(int? id)
        {
            if (!id.HasValue)
                return RedirectToAction("Index");
            return View("Details");
        }

    }
}

Listing 17 – ControllersPersonController.vb [VB]

Public Class PersonController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
        Return View("Index")
    End Function

    Function Details(ByVal id As Integer?) As ActionResult
        If Not id.HasValue Then
            Return RedirectToAction("Index")
        End If

        Return View("Details")
    End Function

End Class

*** Begin Warning ***

When returning a view, you must be explicit about the view name or you won’t be able to verify the name of the view in a unit test. For example, in Listing 17, the Index() method returns View(“Index”) and not View().

*** End Warning ***

The unit tests in Listing 18 illustrate how you can test the actions exposed by the Person controller. The first unit test, named DetailsWithId(), verifies that calling the Details() method with a value for the id parameter returns the Details view.

The second unit test, named DetailsWithoutId(), verifies that calling the Details() method with no value for the id parameter causes a RedirectToRouteResult to be returned.

Listing 18 – ControllersPersonControllerTest.cs [C#]

using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcApplication1.Controllers;

namespace MvcApplication1.Tests.Controllers
{
    [TestClass]
    public class PersonControllerTest
    {
        [TestMethod]
        public void DetailsWithId()
        {
            // Arrange
            var controller = new PersonController();

            // Act
            var result = (ViewResult)controller.Details(33);

            // Assert
            Assert.AreEqual("Details", result.ViewName);
        }

        [TestMethod]
        public void DetailsWithoutId()
        {
            // Arrange
            var controller = new PersonController();

            // Act
            var result = (RedirectToRouteResult)controller.Details(null);

            // Assert
            Assert.AreEqual("Index", result.RouteValues["action"]);
        }
    }
}

Listing 18 – ControllersPersonControllerTest.vb [VB]

Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.Web.Mvc

<TestClass()> Public Class PersonControllerTest

    <TestMethod()> _
    Public Sub DetailsWithId()
        ' Arrange
        Dim controller As New PersonController()

        ' Act
        Dim result As ViewResult = controller.Details(33)

        ' Assert
        Assert.AreEqual("Details", result.ViewName)
    End Sub

    <TestMethod()> _
    Public Sub DetailsWithoutId()
        ' Arrange
        Dim controller As New PersonController()

        ' Act
        Dim result As RedirectToRouteResult = controller.Details(Nothing)

        ' Assert
        Assert.AreEqual("Index", result.RouteValues("action"))
    End Sub

End Class

*** Begin Note ***

To learn more about creating and running unit tests, see Appendix B of this book.

*** End Note ***

Summary

This chapter was devoted to the topic of ASP.NET MVC controllers. The goal of this chapter was to provide an in-depth explanation of how you can create controllers and controller actions.

In the first part of this chapter, you were provided with an overview of the different types of ActionResults that can be returned from a controller action. You learned how to returns views, redirect users to other actions, return JSON, and return downloadable files.

Next, we examined the different attributes that you can apply to a controller action to control when the controller action is invoked. You learned how to use the AcceptVerbs and ActionName attributes. You also learned how to create a custom ActionSelect attribute that enables you to execute an action only within the context of an Ajax request.

Finally, you learned how to build unit tests for your controllers. You learned how to test whether a controller returns different ActionResults such as a ViewResult or a RedirectToRouteResult.

Discussion

  1. Vincent B. says:

    Hi,

    I’ve already read the 3 first book’s parts of you have bloged, everything is very good, I think about buying this book.

    Thank !

  2. Bhakthan says:

    Great article Stephen! I’m sure your book is going to be a great success.

  3. Hello Stephen,

    “A controller name must end with the suffix Controller. If you forget to include the Controller suffix then you won’t be able to invoke the controller.”

    If I forget, “Controller” suffix should be automatically added and this support must be given. What do you think about that?

    Thanks

  4. razvantim says:

    Great articles Stephen, I look forward on buying the book. Your explanations about MVC are so easy to assimilate. Keep up the good work 🙂

  5. Roi says:

    Great stuff, can’t wait for the book already!
    Keep these articles flowing…

  6. Hello Stephen…

    Here are some recommendations I think you can evaluate. Excuse my English.

    1. In the second Warning you can explain how “decorate the method with the NonAction attribute” or reference where in the book you explain it.

    2. In the Listing 2, in the 3 method I think the get is:
    // GET: /Product/Details/1

    3. Read it: “The most common ActionResult returned by a controller action is a ViewResult. A ViewResult represents an ASP.NET MVC view. You return a ViewResult when you want to return HTML to the browser.”
    Is it no better some like this: “The most common ActionResult returned by a controller action is a ViewResult. It represents an ASP.NET MVC view and you return it when you want to return HTML to the browser.”

    4. In the Listing 3 you put as example the Details view. But in the listing 2 it was too. I know it is another controller, but it can confuse. Is it not better put another method in this listing?
    Also in the next paragraphs you said that I can explicit call the Details View. People can ask: Is there no problem if I have another Details view (the Products one). So maybe you can explain that there is no problem if you have views or master views with the same name. MVC knows which one to work with.

    5. Also you mention that we can call this: View(“~/Details.aspx”). Why you can do it if this file is not in the Views folder. You can explain why you can do it and in which cases is recommended do it.

    6. In the note after Listing 5 you mention the nullable types. The example is for int types. It’ll be good to mention which types can be nullable (string?, float?, All?). Can I use any type as a nullable parameter?

    7. After Listing 9, you explain the file method. The example of the DOCX mention the proper MIME type. Where did you get MIME type? Google it? What if I need return a PDF or a ZIP or a DOC. Where I get these MIME types? Google?

    8. Wowww… In the listing 10, how the action Create can receive an Employee as a parameter? How I convert some form values in the view in the Employee object? Maybe a simple example is better here (parameters: string name, string email)… Or maybe you can explain the transformation between forma values in an object.

    9. In the Listing 16 you have this line: View(“Unknown”).ExecuteResult(this.ControllerContext);
    You don’t explain the part: ExecuteResult(…). Is it not necessary?

    Well… The book is getting great. Thanks!!!

  7. Simon Martin says:

    I like the way this book is shaping up!

    My comment is that I’d like the code examples to build on each other, as it stands I find I have to keep scrolling back up the page to remember why or how something is done, though I realise this could quickly become a loooong post that way.

  8. Dick Klaver says:

    Hi Stephen!

    I’m also looking forward to your book. I like the style so far.

    I think I found a typo between listing 4 and 5.


    A view name can contain a relative or absolute path. If you specify a relative path then the location of the view is calculated relative to its normal location.

    For example, calling View(“Subfolder/Details”) from the Details()
    action would return a view from this location:

    ViewsDetailsSubfolderDetails.aspx

    shouldn’t this be

    ViewsCustomerSubfolderDetails.aspx

    kind regards,
    Dick.

  9. @Engr, @Dick — Thanks for your detailed feedback — these are good points.

  10. Craig Carns says:

    These sentences took me a few reads before I got it. “You can’t have two methods with the same name and the same parameters in the same class. However, you can have two actions that have the same name and the same parameters.” Can you emphasize the words “methods” and “actions” in those two sentences so that it is very visually apparent.

  11. David says:

    Excellent chapter — it answered a couple questions that had been nagging me before proceeding on more complex projects with MVC.

    Regarding the contentType in the various MIME return types; I have to agree with the point @Engr raised, but would really like to take it one step further.

    Why not provide the methods with an override that would have contentType as an enumerated value?

    Looking forward to the next chapter.

  12. Jacob says:

    Stephen,

    I finally decided to get into ASP’s MVC framework a couple of months ago. I’ve been waiting for it to mature enough so that what I learned wasn’t made obsolete by the next beta iteration.

    After watching your videos on ASP.net I decided to find a book or two to dive deeper in. Not surprisingly, there was nothing available. I pre-ordered ASP.NET MVC in Action from Manning and got the beta (or MEAP as they call it) PDF to read.

    Out of all the hundreds of computer-related books I have read, that book is shaping up to be the worst ever.

    After you had posted your beta chapters online, I knew I had made a big mistake by pre-ordering the Manning book instead of yours. I really love your style of conveyance and cannot wait to read your book.

    Keep up the good work and thank you for what you provide to the community.

    **P.S. Your contact form is throwing up errors **

  13. Kode says:

    Hi Stephen
    Thank you for sharing the material.
    There is a type in one of the sentences after Listing 1.
    The following sentence
    The Index() controller returns a view as its ActionResult should be
    The Index() action returns a view as its ActionResult should be

  14. Brian says:

    Hello Stephen,

    Another very nice chapter. I was working through some of the sample code for this chapter and I’m not sure if you’re expecting the code to work as is. But if you are, then under the section entitled Returning a JSON Result, the code needs a slight tweek.

    Evidently IE7 caches the $.getJSON() request (I haven’t checked it out on IE8). So after the first call, subsequent requests never get back to the server. This could be what you intended but if it isn’t, then you can add:

    $.ajaxSetup({cache: false });

    and requests will get back to the server after the initial call.

    Again, thanks for putting these chapters up on your blog. Great stuff.

  15. @Brian — Good point. I’ll update the code sample. Thanks!

  16. Orion says:

    “This call to the RedirectToAction() method passes the value 53 as the id parameter to the Index() action.”

    Shouldnt it be to Detail() action?

  17. Great Post! Really very useful information given about controllers. Thanks.

  18. Ya really its very useful. Controllers are always important in .net. Thanks Stephen.

  19. fwq ew this is given in attachment. I understand that very well. Thanks.

  20. stuff to pack and I don’t think I am going to make it… Now I am trying to figure out which things I need most.Business management school | online Bachelor degrees

  21. keit says:

    fwq ew this is given in attachment. I understand that very well. Thanks. PhD economics | law degree

  22. Very useful materials for college and university students who study HTML.

  23. Narconon says:

    Very cool thank you for your post!! 😀

  24. f44 Great Post! Very good introduction is given. Very useful also. Thanks Stephen

  25. scott says:

    Official Ed Hardy Store for all Clothing and Gear by Christian Audigier. The lifestyle brand is inspired by vintage tattoo art incorporating apparel Ed Hardy Clothing | Ed Hardy Hoodies | ED Hardy Long Sleeve | Ed Hardy Handbags | Ed Hardy