Wednesday, April 22, 2015

MVC Controllers and Actions: Receiving Request Data

from: Pro ASP.NET MVC5, Chapter 17

 

Receiving Request Data

Controllers frequently need to access data from the incoming request, such as query string values, form values, and parameters parsed from the URL by the routing system. There are three main ways to access that data:
  • Extract it from a set of context objects.
  • Have the data passed as parameters to your action method.
  • Explicitly invoke the framework's model binding feature.

Here, I look at the approaches for getting input for your action methods, focusing on using context objects and action method parameters. In Chapter 24, I cover model binding in depth.

Getting Data from Context Objects

When you create a controller by deriving from the Controller base class, you get access to a set of convenience properties to access information about the request. These properties include Request, Response, RouteData, HttpContext, and Server. Each provides information about a different aspect of the request. I refer to these as convenience properties, because they each retrieve different types of data from the request's ControllerContext instance (which can be accessed through the Controller.ControllerContext property). I have described some of the most commonly used context objects and properties in Table 17-4.


Table 17-4: Commonly Used Context Objects and Properties 
 Open table as spreadsheet
Property
Type
Description
Request.QueryString
NameValueCollection
GET variables sent with this request
Request.Form
NameValueCollection
POST variables sent with this request
Request.Cookies
HttpCookieCollection
Cookies sent by the browser with this request
Request.HttpMethod
string
The HTTP method (verb, such as GET or POST) used for this request
Request.Headers
NameValueCollection
The full set of HTTP headers sent with this request
Request.Url
Uri
The URL requested
Request.UserHostAddress
string
The IP address of the user making this request
RouteData.Route
RouteBase
The chosen RouteTable.Routes entry for this request
RouteData.Values
RouteValueDictionary
Active route parameters (either extracted from the URL or default values)
HttpContext.Application
HttpApplicationStateBase
Application state store
HttpContext.Cache
Cache
Application cache store
HttpContext.Items
IDictionary
State store for the current request
HttpContext.Session
HttpSessionStateBase
State store for the visitor's session
User
IPrincipal
Authentication information about the logged-in user
TempData
TempDataDictionary
Temporary data items stored for the current user

The individual properties that I refer to here—Request, HttpContext, and so on—provide context objects. I am not going to go into them in detail in this book (because they are part of the ASP.NET platform), but they provide access to some useful information and features and are worth exploring. An action method can use any of these context objects to get information about the request, as Listing 17-5 demonstrates in the form of a hypothetical action method.

Listing 17-5: An Action Method Using Context Objects to Get Information About a Request

…
public ActionResult RenameProduct() {
     // Access various properties from context objects
     string userName = User.Identity.Name;
     string serverName = Server.MachineName;
     string clientIP = Request.UserHostAddress;
     DateTime dateStamp = HttpContext.Timestamp;
     AuditRequest(userName, serverName, clientIP, dateStamp, "Renaming product");

     // Retrieve posted data from Request.Form
     string oldProductName = Request.Form["OldName"];
     string newProductName = Request.Form["NewName"];
     bool result = AttemptProductRename(oldProductName, newProductName);

     ViewData["RenameResult"] = result;
     return View("ProductRenamed");
}


You can explore the vast range of available request context information using IntelliSense (in an action method, type this. and browse the pop-up), and the Microsoft Developer Network (look upSystem.Web.Mvc.Controller and its base classes, or System.Web.Mvc.ControllerContext).

Using Action Method Parameters

As you've seen in previous chapters, action methods can take parameters. This is a neater way to receive incoming data than extracting it manually from context objects, and it makes your action methods easier to read. For example, suppose I have an action method that uses context objects like this:
…
public ActionResult ShowWeatherForecast() {
    string city = (string)RouteData.Values["city"];
    DateTime forDate = DateTime.Parse(Request.Form["forDate"]);
    //  implement weather forecast here 
    return View();
}

I can rewrite it to use parameters, like this:
…
public ActionResult ShowWeatherForecast(string city, DateTime forDate) {
    //  implement weather forecast here 
    return View();
}

Not only is this easier to read, but it also helps with unit testing. I can test the action method without needing to mock the convenience properties of the controller class.


Tip 
It is worth noting that action methods aren't allowed to have out or ref parameters. It wouldn't make any sense if they did and the MVC Framework will simply throw an exception if it sees such a parameter.
The MVC Framework will provide values for action method parameters by checking the context objects and properties automatically, including Request.QueryString, Request.Form, and RouteData.Values. The names of the parameters are treated case-insensitively, so that an action method parameter called city can be populated by a value from Request.Form["City"], for example.

Understanding How Parameters Objects Are Instantiated

The base Controller class obtains values for action method parameters using MVC Framework components called value providers and model binders. Value providers represent the set of data items available to your controller. There are built-in value providers that fetch items from Request.Form, Request.QueryString, Request.Files, and RouteData.Values. The values are then passed to model binders that try to map them to the types that your action methods require as parameters.
The default model binders can create and populate objects of any .NET type, including collections and project-specific custom types. You saw an example of this in Chapter 11 when form posts from administrators were presented to an action method as a single Product object, even though the individual values were dispersed among the elements of the HTML form. I cover value providers and model binders in depth in Chapter 24.

Understanding Optional and Compulsory Parameters

If the MVC Framework cannot find a value for a reference type parameter (such as a string or object), the action method will still be called, but using a null value for that parameter. If a value cannot be found for a value type parameter (such as int or double), then an exception will be thrown, and the action method will not be called. Here is another way to think about it:
  • Value-type parameters are compulsory. To make them optional, either specify a default value (see the next section) or change the parameter type to a nullable type (such as int? or DateTime?), so the MVC Framework can pass null if no value is available.
  • Reference-type parameters are optional. To make them compulsory (to ensure that a non null value is passed), add some code to the top of the action method to reject null values. For example, if the value equals null, throw an ArgumentNullException.

Specifying Default Parameter Values

If you want to process requests that do not contain values for action method parameters, but you would rather not check for null values in your code or have exceptions thrown, you can use the C# optional parameter feature instead. Listing 17-6 provides a demonstration.
Listing 17-6: Using the C# Optional Parameter Feature in an Action Method

…
public ActionResult Search(string query = "all", int page = 1) {
     // process request
     return View();
}



You mark parameters as optional by assigning values when you define them. In the listing, I have provided default values for the query and page parameters. The MVC Framework will try to obtain values from the request for these parameters, but if there are no values available, the defaults I have specified will be used instead.
For the string parameter, query, this means that I do not need to check for null values. If the request I am processing didn't specify a query, then the action method will be called with the string all. For the intparameter, I do not need to worry about requests resulting in errors when there is no page value: the method will be called with the default value of 1. Optional parameters can be used for literal types, which are types that you can define without using the new keyword, including string, int, and double.

Caution 
If a request does contain a value for a parameter but it cannot be converted to the correct type (for example, if the user gives a nonnumeric string for an int parameter), then the framework will pass the default value for that parameter type (for example, 0 for an int parameter), and will register the attempted value as a validation error in a special context object called ModelState. Unless you check for validation errors in ModelState, you can get into odd situations where the user has entered bad data into a form, but the request is processed as though the user had not entered any data or had entered the default value. See Chapter 25 for details of validation and ModelState, which can be used to avoid such problems.

No comments: