Wednesday, April 22, 2015

MVC Controllers and Actions:Producing Output

from: Pro ASP.NET MVC5, Chapter 17

 

Producing Output

After a controller has finished processing a request, it usually needs to generate a response. When I created the bare-metal controller by implementing the IController interface directly, I needed to take responsibility for every aspect of processing a request, including generating the response to the client. If I want to send an HTML response, for example, then I must create and assemble the HTML data and send it to the client using the Response.Write method. Similarly, if I want to redirect the user's browser to another URL, I need to call the Response.Redirect method and pass the URL I am interested in directly. Both of these approaches are shown in Listing 17-7, which shows enhancements to the BasicController class.
Listing 17-7: Generating Results in the BasicController.cs File

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

namespace ControllersAndActions.Controllers {

    public class BasicController : IController {

        public void Execute(RequestContext requestContext) {

            string controller = (string)requestContext.RouteData.Values["controller"];
            string action = (string)requestContext.RouteData.Values["action"];

            if (action.ToLower() == "redirect") {
                requestContext.HttpContext.Response.Redirect("/Derived/Index");
                } else {
                    requestContext.HttpContext.Response.Write(
                        string.Format("Controller: {0}, Action: {1}",
                        controller, action));
                }
           }
     }
}

You can use the same approach when you have derived your controller from the Controller class. The HttpResponseBase class that is returned when you read the requestContext.HttpContext.Responseproperty in your Execute method is available through the Controller.Response property, as shown in Listing 17-8, which shows enhancements to the DerivedController class.
Listing 17-8: Using the Response Property to Generate Output in the DerivedController.cs File

using System.Web.Mvc;

namespace ControllersAndActions.Controllers {

    public class DerivedController : Controller {

        public ActionResult Index() {
            ViewBag.Message = "Hello from the DerivedController Index method";
            return View("MyView");
        }

        public void ProduceOutput() {
            if (Server.MachineName == "TINY") {
                Response.Redirect("/Basic/Index");
            } else {
                Response.Write("Controller: Derived, Action: ProduceOutput");
            }
        }
    }
}

The ProduceOutput method uses the value of the Server.MachineName property to decide what response to send to the client. (TINY is the name of one of my development machines.) This approach works, but it has a few problems:
  • The controller classes must contain details of HTML or URL structure, which makes the classes harder to read and maintain.
  • It is hard to unit test a controller that generates its response directly to the output. You need to create mock implementations of the Response object, and then be able to process the output you receive from the controller in order to determine what the output represents. This can mean parsing HTML for keywords, for example, which is a drawn-out and painful process.
  • Handling the fine detail of every response this way is tedious and error-prone. Some programmers will like the absolute control that building a raw controller gives, but normal people get frustrated pretty quickly.

Fortunately, the MVC Framework has a nice feature that addresses all of these issues, called action results. The following sections introduce the action result concept and show you the different ways that it can be used to generate responses from controllers.

Understanding Action Results

The MVC Framework uses action results to separate stating intentions from executing intentions. The concept is simple once you have mastered it, but it takes a while to get your head around the approach at first because there is a little bit of indirection going on.
Instead of working directly with the Response object, action methods return an object derived from the ActionResult class that describes what the response from controller will be, such as rendering a view or redirecting to another URL or action method. But—and this is where the indirection comes in—you don't generate the response directly. Instead, you create an ActionResult object that the MVC Framework processes to produce the result for you, after the action method has been invoked.

Note 
The system of action results is an example of the command pattern. This pattern describes scenarios where you store and pass around objects that describe operations to be performed. Seehttp://en.wikipedia.org/wiki/Command_pattern for more details.
When the MVC Framework receives an ActionResult object from an action method, it calls the ExecuteResult method defined by that object. The action result implementation then deals with the Response object for you, generating the output that corresponds to your intention. To demonstrate how this works, I created an Infrastructure folder and added a new class file called CustomRedirectResult.cs to it, which I then used to define the custom ActionResult implementation shown in Listing 17-9.
Listing 17-9: The Contents of the CustomRedirectResult.cs File

using System.Web.Mvc;

namespace ControllersAndActions.Infrastructure {
    public class CustomRedirectResult : ActionResult {

        public string Url { get; set; }

        public override void ExecuteResult(ControllerContext context) {
            string fullUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
            context.HttpContext.Response.Redirect(fullUrl);
         }
     }
}

I based this class on the way that the System.Web.Mvc.RedirectResult class works. One of the benefits of the MVC Framework being open source is that you can see how things work behind the scenes. TheCustomRedirectResult class is a lot simpler than the MVC equivalent, but is enough for my purposes in this chapter.
When I create an instance of the RedirectResult class, I pass in the URL I want to redirect the user to. The ExecuteResult method, which will be executed by the MVC Framework when the action method has finished, gets the Response object for the query through the ControllerContext object that the framework provides, and calls the Redirect method, which is exactly what I was doing in the bare-bones IControllerimplementation in Listing 17-7. You can see how I have used the CustomRedirectResult class in the Derived controller in Listing 17-10.

Listing 17-10: Using the CustomRedirectResult Class in the DerivedController.cs File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ControllersAndActions.Infrastructure;

namespace ControllersAndActions.Controllers {
    public class DerivedController : Controller {
        public ActionResult Index() {
            ViewBag.Message = "Hello from the DerivedController Index method";
            return View("MyView");
        }

        public ActionResult ProduceOutput() {
            if (Server.MachineName == "TINY") {
                return new CustomRedirectResult { Url = "/Basic/Index" };
            } else {
                Response.Write("Controller: Derived, Action: ProduceOutput");
                return null;
            }
        }
    }
}

Notice that I have had to change the result of the action method to return an ActionResult. I return null if I do not want the MVC Framework to do anything after the action method has been executed, which is what I have done when I do not return a CustomRedirectResult instance.

Now that you have seen how a custom redirection action result works, I can switch to the equivalent one provided by the MVC Framework, which has more features and has been thoroughly tested by Microsoft. Listing 17-11 shows the change to the Derived controller.
Listing 17-11: Using the Built-in RedirectResult Object in the DerivedController.cs File

…
public ActionResult ProduceOutput() {
    return new RedirectResult("/Basic/Index");
}


I have removed the conditional statement from the action method, which means that if you start the application and navigate to the /Derived/ProduceOutput method, your browser will be redirected to the/Basic/Index URL.
To make action method code simpler, the Controller class includes convenience methods for generating different kinds of ActionResult objects. So, as an example, I can achieve the effect in Listing 17-11 by returning the result of the Redirect method, as shown in Listing 17-12.
Listing 17-12: Using a Controller Convenience Method in the DerivedController.cs File

…
public ActionResult ProduceOutput() {
    return Redirect("/Basic/Index");
}


There is nothing in the action result system that is especially complex, but it helps you create with simpler, cleaner and more consistent code, which is easier to read and easier to unit test.
In the case of a redirection, for example, you can simply check that the action method returns an instance of RedirectResult and that the Url property contains the target you expect.
The MVC Framework contains a number of built-in action result types, which are shown in Table 17-5. All of these types are derived from ActionResult, and many of them have convenient helper methods in theController class. In the following sections, I will show you how to use the most important of these result types.


Table 17-5: Built-in ActionResult Types 
 Open table as spreadsheet
Type
Description
Helper Methods
ViewResult
Renders the specified or default view template
View
PartialViewResult
Renders the specified or default partial view template
PartialView
RedirectToRouteResult
Issues an HTTP 301 or 302 redirection to an action method or specific route entry, generating a URL according to your routing configuration
RedirectToAction 
RedirectToActionPermanent
RedirectToRoute 
RedirectToRoutePermanent
RedirectResult
Issues an HTTP 301 or 302 redirection to a specific URL
Redirect 
RedirectPermanent
ContentResult
Returns raw textual data to the browser, optionally setting a content-type header
Content
FileResult
Transmits binary data (such as a file from disk or a byte array in memory) directly to the browser
File
JsonResult
Serializes a .NET object in JSON format and sends it as the response. This kind of response is more typically generated using the Web API feature, which I describe in Chapter 27, but you can see this action type used in Chapter 23.
Json
JavaScriptResult
Sends a snippet of JavaScript source code that should be executed by the browser
JavaScript
HttpUnauthorizedResult
Sets the response HTTP status code to 401 (meaning "not authorized"), which causes the active authentication mechanism (forms authentication or Windows authentication) to ask the visitor to log in
None
HttpNotFoundResult
Returns a HTTP 404—Not found error
HttpNotFound
HttpStatusCodeResult
Returns a specified HTTP code
None
EmptyResult
Does nothing
None

Returning HTML by Rendering a View

The most common kind of response from an action method is to generate HTML and send it to the browser. To demonstrate how to render views, I added a controller called Example to the project. You can see the contents of the ExampleController.cs class file in Listing 17-13.
Listing 17-13: The Contents of the ExampleController.cs File

using System.Web.Mvc;

namespace ControllersAndActions.Controllers {

    public class ExampleController : Controller {

        public ViewResult Index() {
            return View("Homepage");
        }
    }
}

When using the action result system, you specify the view that you want the MVC Framework to render using an instance of the ViewResult class. The simplest way to do this is to call the controller's View method, passing the name of the view as an argument. In the listing, I called the View method with an argument of Homepage, which specifies that I want the HomePage.cshtml view to be used.


Note 
Notice that that the return type for the action method in the listing is ViewResult. The method would compile and work just as well if I had specified the more general ActionResult type. In fact, some MVC programmers will define the result of every action method as ActionResult, even when they know it will always return a more specific type.
When the MVC Framework calls the ExecuteResult method of the ViewResult object, a search will begin for the view that you have specified. If you are using areas in your project, then the framework will look in the following locations:
  • /Areas/  /Views / / .aspx
  • /Areas/  /Views/  / .ascx
  • /Areas/  /Views/Shared/ .aspx
  • /Areas/  /Views/Shared/ .ascx
  • /Areas/  /Views/  / .cshtml
  • /Areas/  /Views/  / .vbhtml
  • /Areas/  /Views/Shared/.cshtml
  • /Areas/  /Views/Shared/ .vbhtml
You can see from the list that the framework looks for views that have been created for the legacy ASPX view engine (the .aspx and .ascx file extensions), even though the MVC Framework uses Razor. This is to preserve compatibility with early versions of the MVC Framework that used the rendering features from ASP.NET Web Forms.
The framework also looks for C# and Visual Basic .NET Razor templates. (The .cshtml files are the C# ones and .vbhtml files are Visual Basic. The Razor syntax is the same in these files, but the code fragments are, as the names suggest, in different languages.) The MVC Framework checks to see if each of these files exists in turn. As soon as it locates a match, it uses that view to render the result of the action method.
If you are not using areas, or you are using areas but none of the files in the preceding list have been found, then the framework continues its search, using the following locations:
  • /Views/  / .aspx
  • /Views//.ascx
  • /Views/Shared/ .aspx
  • /Views/Shared/ .ascx
  • /Views/  / .cshtml
  • /Views/  / .vbhtml
  • /Views/Shared/ .cshtml
  • /Views/Shared/ .vbhtml
Once again, as soon as the MVC Framework tests a location and finds a file, then the search stops, and the view that has been found is used to render the response to the client.
I am not using areas in the example application, so the first place that the framework will look will be /Views/Example/Index.aspx. Notice that the Controller part of the class name is omitted, so that creating aViewResult in ExampleController leads to a search for a directory called Example.

The sequence of directories that the MVC Framework searches for a view is another example of convention over configuration. You do not need to register your view files with the framework. You just put them in one of a set of known locations, and the framework will find them. I can take the convention a step further by omitting the name of the view I want rendered when calling the View method, as shown in Listing 17-14.
Listing 17-14: Creating a ViewResult Without Specifying a View in the ExampleController.cs File

using System.Web.Mvc;
using System;

namespace ControllersAndActions.Controllers {

    public class ExampleController : Controller {

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

The MVC Framework assumes that I want to render a view that has the same name as the action method. This means that the call to the View method in Listing 17-14 starts a search for a view called Index.

Note 
The MVC Framework actually gets the name of the action method from the RouteData.Values["action"] value, which I explained as part of the routing system in Chapters 15 and 16. The action method name and the routing value will be the same if you are using the built-in routing classes, but this may not be the case if you have implemented custom routing classes which do not follow the MVC Framework conventions.
There are a number of overridden versions of the View method. They correspond to setting different properties on the ViewResult object that is created. For example, you can override the layout used by a view by explicitly naming an alternative, like this:
…
public ViewResult Index() {
    return View("Index", "_AlternateLayoutPage");
}
…

Passing Data from an Action Method to a View

The MVC Framework provides a number of different ways to pass data from an action method to a view, which I describe in the following sections. I touch on the topic of views, which I cover in depth in Chapter 20. In this chapter, I discuss only enough view functionality to demonstrate the controller features of interest.

Providing a View Model Object

You can send an object to the view by passing it as a parameter to the View method as shown in Listing 17-15.
Listing 17-15: Specifying a View Model Object in the ExampleController.cs File

using System;
using System.Web.Mvc;

namespace ControllersAndActions.Controllers {

    public class ExampleController : Controller {
        public ViewResult Index() {
            DateTime date = DateTime.Now;
            return View(date);
        }
    }
}

I passed a DateTime object as the view model and I can access the object in the view using the Razor Model keyword. To demonstrate the Model keyword, I added a view called Index.cshtml in the Views/Examplefolder, with the contents shown in Listing 17-16.
Listing 17-16: Accessing a View Model in the Index.cshtml File

@{
    ViewBag.Title = "Index";
}

Index

The day is: @(((DateTime)Model).DayOfWeek)

This is an untyped or weakly typed view. The view does not know anything about the view model object, and treats it as an instance of object. To get the value of the DayOfWeek property, I need to cast the object to an instance of DateTime. This works, but produces messy views. I can tidy this up by creating strongly typed views, in which the view includes details of the type of the view model object, as demonstrated in Listing 17-17.
Listing 17-17: Adding Strong Typing to the Index.cshtml File

@model DateTime
@{
    ViewBag.Title = "Index";
}

Index

The day is: @Model.DayOfWeek

I specified the view model type using the Razor model keyword. Notice that I use a lowercase m when specifying the model type and an uppercase M when reading the value. Not only does this help tidy up the view, but Visual Studio supports IntelliSense for strongly typed views, as shown in Figure 17-3.

Image from book 
Figure 17-3: IntelliSense support for strongly typed views

Passing Data with the View Bag

I introduced the View Bag feature in Chapter 2. This feature allows you to define properties on a dynamic object and access them in a view. The dynamic object is accessed through the Controller.ViewBag property, as demonstrated in Listing 17-18.
Listing 17-18: Using the View Bag Feature in the ExampleController.cs File

using System;
using System.Web.Mvc;

namespace ControllersAndActions.Controllers {

    public class ExampleController : Controller {

        public ViewResult Index() {
            ViewBag.Message = "Hello";
            ViewBag.Date = DateTime.Now;
            return View();
        }
    }
}

I have defined View Bag properties called Message and Date simply by assigning values to them. Before this point, no such properties existed, and I made no preparations to create them. To read the data back in the view, I simply get the same properties that I set in the action method, as Listing 17-19 shows.

Listing 17-19: Reading Data from the ViewBag in the Index.cshtml File

@{
    ViewBag.Title = "Index";
}

Index

The day is: @ViewBag.Date.DayOfWeek The message is: @ViewBag.Message

The ViewBag has an advantage over using a view model object in that it is easy to send multiple objects to the view. If I were restricted to using view models, then I would need to create a new type that had string andDateTime members in order to get the same effect.
When working with dynamic objects, you can enter any sequence of method and property calls in the view, like this:
…
The day is: @ViewBag.Date.DayOfWeek.Blah.Blah.Blah
…
Visual Studio cannot provide IntelliSense support for any dynamic objects, including the ViewBag, and errors such as this won't be revealed until the view is rendered.

Performing Redirections


A common result from an action method is not to produce any output directly, but to redirect the user's browser to another URL. Most of the time, this URL is another action method in the application that generates the output you want the users to see.
When you perform a redirect, you send one of two HTTP codes to the browser:
  • Send the HTTP code 302, which is a temporary redirection. This is the most frequently used type of redirection and when using the Post/Redirect/Get pattern, this is the code that you want to send.
  • Send the HTTP code 301, which indicates a permanent redirection. This should be used with caution, because it instructs the recipient of the HTTP code not to request the original URL ever again and to use the new URL that is included alongside the redirection code. If you are in doubt, use temporary redirections; that is, send code 302.
Redirecting to a Literal URL
The most basic way to redirect a browser is to call the Redirect method, which returns an instance of the RedirectResult class, as shown in Listing 17-20.
Listing 17-20: Redirecting to a Literal URL in the ExampleController.cs File

using System;
using System.Web.Mvc;

namespace ControllersAndActions.Controllers {

    public class ExampleController : Controller {

        public ViewResult Index() {

            ViewBag.Message = "Hello";
            ViewBag.Date = DateTime.Now;
            return View();
        }
        public RedirectResult Redirect() {
            return Redirect("/Example/Index");
        }
    }
}

The URL you want to redirect to is expressed as a string and passed as a parameter to the Redirect method. The Redirect method sends a temporary redirection. You can send a permanent redirection using theRedirectPermanent method, as shown in Listing 17-21.
Listing 17-21: Permanently Redirecting to a Literal URL in the ExampleController.cs File

…
public RedirectResult Redirect() {
    return RedirectPermanent("/Example/Index");
}



Tip 
If you prefer, you can use the overloaded version of the Redirect method, which takes a bool parameter that specifies whether or not a redirection is permanent.
Redirecting to a Routing System URL

If you are redirecting the user to a different part of your application, you need to make sure that the URL you send is valid within your URL schema. The problem with using literal URLs for redirection is that any change in your routing schema means that you need to go through your code and update the URLs. Fortunately, you can use the routing system to generate valid URLs with the RedirectToRoute method, which creates an instance of the RedirectToRouteResult, as shown in Listing 17-22.
Listing 17-22: Redirecting to a Routing System URL in the ExampleController.cs File

using System;
using System.Web.Mvc;

namespace ControllersAndActions.Controllers {

    public class ExampleController : Controller {

        public ViewResult Index() {

            ViewBag.Message = "Hello";
            ViewBag.Date = DateTime.Now;

            return View();
        }

        public RedirectToRouteResult Redirect() {
            return RedirectToRoute(new {
                controller = "Example",
                action = "Index",
                ID = "MyID"
            });
        }
    }
}

The RedirectToRoute method issues a temporary redirection. Use the RedirectToRoutePermanent method for permanent redirections. Both methods take an anonymous type whose properties are then passed to the routing system to generate a URL. For more details of this process, see the Chapters 15 and 16.

Tip 
Notice that the RedirectToRoute method returns a RedirectToRouteResult object and that I have updated the action method to return this type.

Redirecting to an Action Method
You can redirect to an action method more elegantly by using the RedirectToAction method (for temporary redirections) or the RedirectToActionPermanent (for permanent redirections). These are just wrappers around the RedirectToRoute method that lets you specify values for the action method and the controller without needing to create an anonymous type, as shown in Listing 17-23.
Listing 17-23: Redirecting Using the RedirectToAction Method in the ExampleController.cs File

…
public RedirectToRouteResult Redirect() {
    return RedirectToAction("Index");
}


If you just specify an action method, then it is assumed that you are referring to an action method in the current controller. If you want to redirect to another controller, you need to provide the name as a parameter, like this:
…
public RedirectToRouteResult Redirect() {
    return RedirectToAction("Index", "Basic");
}


There are other overloaded versions that you can use to provide additional values for the URL generation. These are expressed using an anonymous type, which does tend to undermine the purpose of the convenience method, but can still make your code easier to read.

Note 
The values that you provide for the action method and controller are not verified before they are passed to the routing system. You are responsible for making sure that the targets you specify actually exist.

Returning Errors and HTTP Codes

The last of the built-in ActionResult classes that I will look at can be used to send specific error messages and HTTP result codes to the client. Most applications do not require these features because the MVC Framework will automatically generate these kinds of results. However, they can be useful if you need to take more direct control over the responses sent to the client.
Sending a Specific HTTP Result Code
You can send a specific HTTP status code to the browser using the HttpStatusCodeResult class. There is no controller helper method for this, so you must instantiate the class directly, as shown in Listing 17-24.
Listing 17-24: Sending a Specific Status Code in the ExampleController.cs File

using System;
using System.Web.Mvc;

namespace ControllersAndActions.Controllers {

    public class ExampleController : Controller {

        public ViewResult Index() {

            ViewBag.Message = "Hello";
            ViewBag.Date = DateTime.Now;
            return View();
        }
        public RedirectToRouteResult Redirect() {
            return RedirectToAction("Index");
        }

        public HttpStatusCodeResult StatusCode() {
            return new HttpStatusCodeResult(404, "URL cannot be serviced");
        }
    }
}

The constructor parameters for HttpStatusCodeResult are the numeric status code and an optional descriptive message. In the listing, I returned code 404, which signifies that the requested resource does not exist.
Sending a 404 Result
I can achieve the same effect as Listing 17-24 using the more convenient HttpNotFoundResult class, which is derived from HttpStatusCodeResult and can be created using the controller HttpNotFound convenience method, as shown in Listing 17-25.
Listing 17-25: Generating a 404 Result in the ExampleController.cs File

…
public HttpStatusCodeResult StatusCode() {
    return HttpNotFound();
}


Sending a 401 Result
Another wrapper class for a specific HTTP status code is the HttpUnauthorizedResult, which returns the 401 code, used to indicate that a request is unauthorized. Listing 17-26 provides a demonstration.
Listing 17-26: Generating a 401 Result in the ExampleController.cs File

…
public HttpStatusCodeResult StatusCode() {
    return new HttpUnauthorizedResult();
}


There is no helper method in the Controller class to create instances of HttpUnauthorizedResult, so you must do so directly. The effect of returning an instance of this class is usually to redirect the user to the authentication page, as you saw in Chapter 12.


 

No comments: