Pro ASP.NET MVC Framework phần 6 - Pdf 20

public class PermanentRedirectToRouteResult : ActionResult
{
public RedirectToRouteResult Redirection { get; private set; }
public PermanentRedirectToRouteResult(RedirectToRouteResult redirection)
{
this.Redirection = redirection;
}
public override void ExecuteResult(ControllerContext context)
{
// After setting up a normal redirection, switch it to a 301
Redirection.ExecuteResult(context);
context.HttpContext.Response.StatusCode = 301;
}
}
}
Whenever you’ve imported this class’s namespace, you can simply add
.AsMovedPermanently() to the end of any redirection:
public ActionResult MyActionMethod()
{
return RedirectToAction("AnotherAction")
.AsMovedPermanently();
}
Search Engine Optimization
You’ve just considered URL design in terms of maximizing usability and compliance with
HTTP conventions. Let’s now consider specifically how URL design is likely to affect search
engine rankings.
Here are some techniques that can improve your chances of being ranked highly:
• Use relevant keywords in your URLs:
/products/dvd/simpsons will score more points
than
/products/293484.

ne cares about them) will never reveal the inner details of their ranking algorithms. URL
design is only part of it—link placement and getting inbound links from other popular sites is
more critical. Focus on making your URLs work well for humans, and those URLs will tend to
do well with search engines, too.
Summary
You’ve now had a close look at the routing system—how to use it, and how it works internally.
This means you can now implement almost any URL schema, producing human-friendly
and search engine–optimized URLs, without having to hard-code a URL anywhere in your
application.
In the next chapter, you’ll explore the heart of the MVC Framework itself, gaining
advanced knowledge of controllers and actions.
CHAPTER 8 ■ URLS AND ROUTING258
10078ch08.qxd 3/16/09 12:40 PM Page 258
Controllers and Actions
Each time a request comes in to your ASP.NET MVC application, it’s dealt with by a con-
troller. The controller is the boss: it can do anything it likes to service that request. It can issue
any set of commands to the underlying model tier or database, and it can choose to render
any view template back to the visitor. It’s a C# class into which you can add any logic needed
to handle the request.
In this chapter, you’ll learn in detail how this centerpiece of the MVC Framework oper-
ates, and what facilities it offers. We’ll start with a quick discussion of the relevant architectural
principles, and then look at your options for receiving input, producing output, and injecting
extra logic. Next, you’ll see how as an advanced user you can customize the mechanisms for
locating and instantiating controllers and invoking their action methods. Finally, you’ll see
how all of this design fits neatly with unit testing.
An Overview
Let’s recap exactly what role controllers play in MVC architecture. MVC is all about keeping
things simple and organized via separation of concerns. In particular, MVC aims to keep sepa-
rate three main areas of responsibility:
• Business or domain logic and data storage (model)

Comparisons with ASP.NET WebForms
There are some similarities between ASP.NET MVC’s controllers and the ASPX pages in tradi-
tional WebForms. For example, both are the point of interaction with the end user, and both
hold application logic. In other ways, they are conceptually quite different—for example,
You can’t separate a WebForms ASPX page from its code-behind class—the two only work
together, cooperating to implement both application logic and presentation logic (e.g.,
when data-binding), both being concerned with every single button and label. ASP.NET
MVC controllers, however, are cleanly separated from any particular UI (i.e., view)—they
are abstract representations of a set of user interactions, purely holding application logic.
This abstraction helps you to keep controller code simple, so your application logic stays
easier to understand and test in isolation.
WebForms ASPX pages (and their code-behind classes) have a one-to-one association
with a particular UI screen. In ASP.NET MVC, a controller isn’t tied to a particular view,
so it can deal with a request by returning any one of several different UIs—whatever is
required by your application logic.
Of course, the real test of the MVC Framework is how well it actually helps you to get your
job done and build great software. Let’s now explore the technical details, considering exactly
how controllers are implemented and what you can do with one.
All Controllers Implement IController
In ASP.NET MVC, controllers are .NET classes. The only requirement on them is that they must
implement the
IController interface. It’s not much to ask—here’s the full interface definition:
public interface IController
{
void Execute(RequestContext requestContext);
}
The “hello world” controller example is therefore
public class HelloWorldController : IController
{
public void Execute(RequestContext requestContext)

specifying results and executing
them
simplifies automated testing considerably.
Filters: You can encapsulate reusable behaviors (e.g., authentication or output caching) as
filters, and then tag each behavior onto one or more controllers or action methods by
putting an
[Attribute] in your source code.
This chapter covers all of these features in more detail. Of course, you’ve already seen and
worked with many controllers and action methods in earlier chapters, but to illustrate the pre-
ceding points, consider this:
[OutputCache(Duration=600, VaryByParam="*")]
public class DemoController : Controller
{
public ViewResult ShowGreeting()
{
ViewData["Greeting"] = "Hello, world!";
return View("MyView");
}
}
CHAPTER 9 ■ CONTROLLERS AND ACTIONS 261
10078ch09.qxd 3/26/09 12:11 PM Page 261
This simple controller class, DemoController, makes use of all three features mentioned
previously.
Since it’s derived from the standard
Controller base class, all its public methods are
action methods, so they can be invoked from the Web. The URL for each action method is
d
etermined by your routing configuration. With the default routing configuration, you
can invoke
ShowGreeting() by requesting /Demo/ShowGreeting.

ectly inv
oke the framework’s
model binding feature. We’ll now consider each of these techniques.
Getting Data from Context Objects
The most direct way to get hold of incoming data is to fetch it yourself. When your controllers are
derived from the framework’s
Controller base class, you can use its properties, including Request,
Response, RouteData, HttpContext, and Server, to access GET and POST values, HTTP headers,
cookie information, and basically everything else that the framework knows about the request.
1
1. All these pr
oper
ties ar
e merely shortcuts into the
ControllerContext pr
oper
ty
. F
or example
,
Request is
equiv
alent to
ControllerContext.HttpContext.Request.
CHAPTER 9 ■ CONTROLLERS AND ACTIONS262
10078ch09.qxd 3/26/09 12:11 PM Page 262
An action method can retrieve data from many sources—for example,
public ActionResult RenameProduct()
{
// Access various properties from context objects

Request.UserHostAddress string The IP address of the user making this request
RouteData.Route RouteBase The chosen RouteTable.Routes entry for this
r
equest
RouteData.Values RouteValueDictionary Active route parameters (either extracted
from the URL, or default values)
HttpContext.Application HttpApplicationStateBase Application state store
HttpContext.Cache
Cache
A
pplication cache stor
e
HttpContext.Items
IDictionary
S
tate stor
e for the curr
ent request
HttpContext.Session HttpSessionStateBase State store for the visitor’s session
User IPrincipal Authentication information about the logged-
in user
TempData
TempDataDictionary
D
ata items stor
ed while pr
ocessing the pr
evi
-
ous HTTP request in this session (more about

// implement weather forecast here
}
To supply values for your parameters, the MVC Framework scans several context
objects, including
Request.QueryString, Request.Form, and RouteData.Values, to find
matching key/value pairs. The keys are treated case-insensitively, so the parameter
city can
be populated from
Request.Form["City"]. (To recap, RouteData.Values is the set of curly
brace parameters extracted by the routing system from the incoming URL, plus any default
route parameters.)
Parameters Objects Are Instantiated Using a Model Binder
Behind the scenes, there’s a framework component called ControllerActionInvoker that actu-
ally invokes your action method and passes parameters to it. It obtains these parameter values
by using another framework feature called
model binding.
As you’ll discover, model binding is capable of supplying objects of any .NET type, includ-
ing collections and your own custom types. For example, this means that you can receive an
uploaded file simply b
y having your action method take a parameter of type
HttpPostedFileBase.
You saw an example of this at the end of Chapter 6, when letting administrators upload prod-
uct images to SportsStore.
To learn how model binding works, including how different context objects are priori-
tized, how incoming string values can be parsed to arbitrary .NET object types, and how it
CHAPTER 9 ■ CONTROLLERS AND ACTIONS264
2. This is not exactly the same as the definition of a pur
e function
in the theor
y of functional pr

I’m not talking about UI validation here: if your intention is to provide the end user with
feedback about certain form fields being required, see the “Validation” section in Chapter 11.
Parameters You Can’t Bind To
For completeness, it’s worth noting that action methods aren’t allowed to have out or ref
parameters. It wouldn’t make any sense if they did. ASP.NET MVC will simply throw an excep-
tion if it sees such a parameter.
Invoking Model Binding Manually in an Action Method
In data entry scenarios, it’s fairly common to set up a <form> that includes separate fields for
each property on a model object. When you receive the submitted form data, you might copy
each incoming value into the relevant object property—for example,
public ActionResult SubmitEditedProduct()
{
Product product = LoadProductByID(int.Parse(Request.Form["ProductID"]));
product.Name = Request.Form["Name"];
product.Description = Request.Form["Description"];
product.Price = double.Parse(Request.Form["Price"]);
CommitChanges(product);
return RedirectToAction("List");
}
CHAPTER 9 ■ CONTROLLERS AND ACTIONS 265
3. In C#, classes ar
e reference types (held on the heap), and structs are value types (held on the stack). The
most commonly used value types include
int, bool, and DateTime (but note that string is a reference
type). Reference types can be
null (the object handle is put into a state that means “no object”), but
value types can’t be (there is no handle; there’s just a block of memory used to hold the object’s value).
10078ch09.qxd 3/26/09 12:11 PM Page 265
Most of that code is boring and predictable. Fortunately, just as you can use model bind-
i

ctionR
esult Concept
If you create a bare-metal IController class (i.e., you implement IController directly, not
deriving from
System.Web.Mvc.Controller), then you can generate a response any way you like
by working directly with
controllerContext.HttpContext.Response. For example, you can
transmit HTML or issue HTTP redirections:
CHAPTER 9 ■ CONTROLLERS AND ACTIONS266
10078ch09.qxd 3/26/09 12:11 PM Page 266
public class BareMetalController : IController
{
public void Execute(RequestContext requestContext)
{
requestContext.HttpContext.Response.Write("I <b>love</b> HTML!");
// or
requestContext.HttpContext.Response.Redirect("/Some/Other/Url");
}
}
It’s simple, and it works. You could do the exact same thing with controllers derived from
the
Controller base class, too, by working directly with the Response property:
public class SimpleController : Controller
{
public void MyActionMethod()
{
Response.Write("I'll never stop using the <blink>blink</blink> tag");
// or
Response.Redirect("/Some/Other/Url");
// or


s the only
method on the
ActionResult base class. When your application is running for real, the
fr
amewor
k calls this method and actually perfor
ms the designated response b
y working
directly with
Response.
CHAPTER 9 ■ CONTROLLERS AND ACTIONS 267
4. Well, of course you can’t actually display HTML, issue an HTTP redirection, and transmit a binary file
all in the same HT
TP response
.
You can only do one thing per response, which is another reason why
it’s semantically clearer to return an
ActionResult than to do a series of things directly to Response.
10078ch09.qxd 3/26/09 12:11 PM Page 267
Table 9-2. ASP.NET MVC’s Built-In ActionResult Types
Testability is the main benefit of using action results, but the secondary benefit is tidiness
a
nd ease of use. Not only is there a concise API for generating typical
A
ctionResult
o
bjects
(e.g., to render a view), but you can create custom
ActionResult subclasses if you want to

Issues an HTTP 302 redirection to an
arbitrary URL.
return
Redirect("");
ContentResult
Returns raw textual data to the
browser, optionally setting a
content-type header.
return Content(rssString,
"application/rss+xml");
FileResult
Transmits binary data (such as a file
fr
om disk, or a b
yte array in memory)
directly to the browser.
return File(@"c:\report.pdf",
"application/pdf");
JsonResult
Serializes a .NET object in JSON
format and sends it as the response.
return Json(someObject);
JavaScriptResult
Sends a snippet of JavaScript source
code that should be executed b
y the
browser. This is only intended for use
in Ajax scenarios (described in
Chapter 12).
return

{
return View("Homepage");
// Or, equivalently: return new ViewResult { ViewName = "Homepage" };
}
}
■Note This action method specifically declares that it returns an instance of ViewResult. It would work
just the same if instead the method return type was
ActionResult (the base class for all action results).
In fact, some ASP.NET MVC programmers declare
all their action methods as returning a nonspecific
ActionResult, even if they know for sure that it will always return one particular subclass. However, it’s a
well-established principle in object-oriented programming that methods should return the most specific type
they can (as well as accepting the most general parameter types they can). Following this principle maxi-
mizes convenience and flexibility for code that calls your method, such as your unit tests.
The call to View() generates a ViewResult object. When executing that result, the MVC
Framework’s built-in view engine,
WebFormViewEngine, will by default look in the following
places (in this order) to find the view template:
1. /Views/ControllerName/ViewName.aspx
2. /Views/ControllerName/ViewName.ascx
3. /Views/Shared/ViewName.aspx
4. /Views/Shared/ViewName.ascx
■Note For more details about how this naming convention is implemented, and how you can customize it,
see the “Implementing a Custom View Engine” section in Chapter 10.
So, in this example, the first place it looks is /Views/Admin/Homepage.aspx (notice that
the
Controller suffix
on the controller class name is removed—that’s the controller naming
CHAPTER 9 ■ CONTROLLERS AND ACTIONS 269
10078ch09.qxd 3/26/09 12:11 PM Page 269

return View("~/path/to/some/view.aspx");
}
}
Note that full paths must start with / or ~/, and must include a file name extension
(usually
.aspx). U
nless you’ve registered a custom view engine, the file you reference must be
an ASPX view page.
Passing a ViewData Dictionary and a Model Object
As you know, controllers and views are totally different, independent things. Unlike in tradi-
tional ASP
.NET WebForms, where the code-behind logic is deeply intertwined with the ASPX
markup
, the MV
C F
r
amework enforces a strict separation between application logic and
presentation logic. Controllers supply data to their views, but views do not directly talk back
to contr
ollers. This separation of concerns is a key factor in MVC’s tidiness, simplicity, and
testability
.
The mechanism for controller-to-view data transfer is ViewData. The Controller base class
has a pr
oper
ty called
ViewData, of type ViewDataDictionary.
Y
ou’ve seen
ViewDataDictionary

in the view template, like this:
<%= ViewData["message"] %>, world!
The person's name is <%= ((Person)ViewData["person"]).Name %>
The person's age is <%= ((Person)ViewData["person"]).Age %>
Dictionary semantics are very flexible and convenient because you can send any collec-
tion of objects and pick them out by name. You don’t have to declare them in advance; it’s the
same sort of convenience that you get with loosely typed programming languages.
The drawback to using
ViewData as a loosely typed dictionary is that when you’re writing
the view template, you don’t get any IntelliSense to help you pick values from the collection.
You have to know what keys to expect (in this example,
person and message), and unless
you’re simply rendering a primitive type such as a
string, you have to perform explicit man-
ual typecasts. Of course, neither Visual Studio nor the compiler can help you here; there’s no
formal specification of what items should be in the dictionary (it isn’t even determined until
runtime).
Sending a Strongly Typed Object in ViewData.Model
ViewDataDictionary has a special pr
oper
ty called
Model.
Y
ou can assign any .NET object to that
property by writing
ViewData.Model = myObject; in your action method, or as a shortcut you
can pass
myObject as a par
ameter to
View()—for example

ammer
, y
ou no doubt appreciate the benefits of strong typing. The draw-
back, though, is that you’re limited to sending only
one object in ViewData.Model, which is
awkwar
d if you want to display a few status messages or other v
alues at the same time as
y
our
Person object.
T
o send multiple strongly typed objects
, y
ou
’d end up creating a wrapper
class—for example,
public class ShowPersonViewData
{
public Person Person { get; set; }
public string StatusMessage { get; set; }
public int CurrentPageNumber { get; set; }
}
and then choosing ShowPersonViewData as the model type for a strongly typed view. That
strategy is fine, but eventually you’ll get bored of writing these wrapper classes.
CHAPTER 9 ■ CONTROLLERS AND ACTIONS272
10078ch09.qxd 3/26/09 12:11 PM Page 272
Combining Both Approaches
The great thing about ViewDataDictionary is that it lets you use both loosely typed and
strongly typed techniques at the same time. That avoids the need for wrapper classes. You can

SaveRecord() action method, duplicating the
code that’s already in
Index() (clearly, that’s bad news).

F
r
om y
our
SaveRecord() method, invoke the Index() method dir
ectly:
public ViewResult SaveRecord(int id, string newName)
{
// Get the domain model to save the data
DomainModel.SaveUpdatedRecord(id, newName);
// Now render the grid of all items
return Index();
}
CHAPTER 9 ■ CONTROLLERS AND ACTIONS 273
10078ch09.qxd 3/26/09 12:11 PM Page 273
That reduces code duplication. However, this can cause a few things to break—for
e
xample, if
I
ndex()
t
ries to render its default view, it will actually render the default
view for the
SaveRecord action, because RouteData.Values["action"] will still equal
SaveRecord.
• From your

return RedirectToAction("SomeAction");
CHAPTER 9 ■ CONTROLLERS AND ACTIONS274
5
.
S
tr
ictly speaking, the HT
TP specification says that br
owsers should keep using the same HTTP
method to follow up on a 302 redirection, so if
SaveRecord was requested with a POST, the browser
should also use a POST to request Index. There’s a special status code (303) that means “redirect using
GET.” However, all current mainstream browsers defy the specification by using a GET request after
any 302 redirection. This is convenient, since there isn’t such an easy way to issue a 303.
10078ch09.qxd 3/26/09 12:11 PM Page 274
This returns a RedirectToRouteResult object, which internally uses the routing system’s
outbound URL-generation features to determine the target URL according to your routing
configuration.
If you don’t specify a controller (as previously), it’s understood to mean “on the same
controller,” but you can also specify an explicit controller name, and if you wish, you can
supply other arbitrary custom routing parameters that affect the URL generated:
return RedirectToAction("Index", "Products", new { color = "Red", page = 2 } );
As always, under the MVC Framework’s naming convention, you should just give the
controller’s routing name (e.g.,
Products), not its class name (e.g., ProductsController).
Finally, if you’re working with named
RouteTable.Route entries, you nominate them
by name:
return RedirectToRoute("MyNamedRoute", new { customParam = "SomeValue" });
These URL-generating redirection methods, their many overloads, and how they actually

CHAPTER 9 ■ CONTROLLERS AND ACTIONS 275
6. It’s the logical equivalent to :flash in R
ub
y on Rails, and to the
Flash[] collection in M
onoR
ail.
10078ch09.qxd 3/26/09 12:11 PM Page 275
made by a given visitor. That makes it ideal for extremely short-term data storage across a
r
edirection.
Let’s go back to the previous example with
SaveRecord and Index. After saving a record,
it’s polite to confirm to the user that their changes were accepted and stored. But how can
the
Index() action method know what happened on the previous request? Use TempData
like this:
public RedirectToRouteResult SaveRecord(int id, string newName)
{
// Get the domain model to save the data
DomainModel.SaveUpdatedRecord(id, newName);
// Now redirect to the grid of all items, putting a status message in TempData
TempData["message"] = "Your changes to " + newName + " have been saved";
return RedirectToAction("Index");
}
Then during the subsequent request, in Index’s view, render that value:
<% if(TempData["message"] != null) { %>
<div class="StatusMessage"><%= Html.Encode(TempData["message"]) %></div>
<% } %>
Before TempData, the traditional way to do this was to pass the status message as a query

TempDataProvider property
.
The MVC Futures assembly contains a ready-made alter-
native provider
,
CookieTempDataProvider,
which works by serializing
TempData contents out to a
browser cookie.
CHAPTER 9 ■ CONTROLLERS AND ACTIONS276
10078ch09.qxd 3/26/09 12:11 PM Page 276
Returning Textual Data
Besides HTML, there are many other text-based data formats that your web application might
wish to generate. Common examples include
• XML
• RSS and ATOM (subsets of XML)
• JSON (usually for Ajax applications)
• CSV (usually for exporting tabular data to Excel)
• Plain text
ASP.NET MVC has special, built-in support for generating JSON data (described shortly),
but for all the others, you can use the general purpose
ContentResult action result type. To
successfully return any text-based data format, there are three things for you to specify:
• The data itself as a
string.
• The
content-type header to send (e.g., text/xml for XML, text/csv for CSV, and
application/rss+xml for RSS—you can easily look these up online, or pick from the
values on the
System.Net.Mime.MediaTypeNames class). The browser uses this to decide

ContentResult using that value. This can be handy in some Ajax scenarios, for
example if you simply want to return a
Guid or other token to the browser. Note that it will not
specify any
contentType parameter, so the default (text/html) will be used.
■Tip It’s possible to change this behavior of converting result objects to strings. For example, you might
decide that action methods should be allowed to return arbitrary domain entities, and that when they do,
the object should be packaged and delivered to the browser in some particular way (perhaps varying
according to the incoming
Accept HTTP header). This could be the basis of a REST application framework.
To do this, make a custom action invoker by subclassing
ControllerActionInvoker and override its
CreateActionResult() method. Then assign to your controller’s ActionInvoker property an instance
of your custom action invoker.
Generating an RSS Feed
As an example of using ContentResult, see how easy it is to create an RSS 2.0 feed. You can
construct an XML document using the elegant .NET 3.5
XDocument API, and then send it to the
browser using
Content()—for example,
class Story { public string Title, Url, Description; }
public ContentResult RSSFeed()
{
Story[] stories = GetAllStories(); // Fetch them from the database or wherever
// Build the RSS feed document
string encoding = Response.ContentEncoding.WebName;
XDocument rss = new XDocument(new XDeclaration("1.0", encoding, "yes"),
new XElement("rss", new XAttribute("version", "2.0"),
new XElement("channel", new XElement("title", "Example RSS 2.0 feed"),
from story in stories

new CityData { city = "London", temperature = 68 },
new CityData { city = "Hong Kong", temperature = 84 }
};
return Json(citiesArray);
}
This will transmit citiesArray in JSON format—for example:
[{"city":"London","temperature":68},{"city":"Hong Kong","temperature":84}]
Also, it will set the response’s content-type header to application/json.
Don’t worry if you don’t yet understand how to make use of JSON. You’ll find further
explanations and examples in Chapter 12, demonstrating its use with Ajax.
Returning JavaScript Commands
A
ction methods can handle Ajax requests just as easily as they handle regular requests. As
you’ve just learned, an action method can return an arbitrary JSON data structure using
JsonResult, and then the client-side code can do whatever it likes with that data.
S
ometimes, however, you might like to respond to an Ajax call by directly instructing the
browser to execute a certain JavaScript statement. You can do that using the
JavaScript()
method, which returns an action result of type JavaScriptResult—for example,
public JavaScriptResult SayHello()
{
return JavaScript("alert('Hello, world!');");
}
CHAPTER 9 ■ CONTROLLERS AND ACTIONS 279
10078ch09.qxd 3/26/09 12:11 PM Page 279
For this to work, you need to reference this action using an Ajax.ActionLink() helper
instead of a regular
Html.ActionLink() helper. For example, add the following to a view:
<%= Ajax.ActionLink("Click me", "SayHello", null) %>

hapter 6 when sending image data retrieved from the database.
FileResult is the abstract base class for all action results concerned with sending
binary data to the browser. ASP.NET MVC comes with three built-in concrete subclasses for
you to use:

FilePathResult sends a file directly from the server’s file system.

FileContentResult sends the contents of a byte array (byte[]) in memory.

FileStreamResult sends the contents of a System.IO.Stream object that you’ve already
opened from somewhere else.
Normally, you can forget about which
FileResult subclass you’re using, because all three
can be instantiated by calling different overloads of the
File() method. Just pick whichever
overload of
File() fits with what you’re trying to do. You’ll now see examples of each.
Sending a File Directly from Disk
You can use File() to send a file directly from disk as follows:
public FilePathResult DownloadReport()
{
string filename = @"c:\files\somefile.pdf";
return File(filename, "application/pdf", "AnnualReport.pdf");
}
This will cause the browser to pop open a “Save or Open” prompt, as shown in Figure 9-4.
Figure 9-4. Internet Explorer’s “Save or Open” prompt
This overload of File() accepts the parameters listed in Table 9-3.
CHAPTER 9 ■ CONTROLLERS AND ACTIONS 281
10078ch09.qxd 3/26/09 12:11 PM Page 281


Nhờ tải bản gốc

Tài liệu, ebook tham khảo khác

Music ♫

Copyright: Tài liệu đại học © DMCA.com Protection Status