Yan-Feng

记录经历、收藏经典、分享经验

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Overview

In Chapter 2, we discussed the Model-View-Controller pattern in general and then followed up with how ASP.NET MVC compared with ASP.NET Web Forms. Now it's time to get into a bit more detail with one of the core elements of the three-sided pattern that is MVC.

It's probably best to start out with a definition and then dive into detail from there. Keep this definition in the back of your mind as you read this chapter, as it helps to ground the discussion ahead with what a Controller is all about, and what it's supposed to do.

You might want to remember a quick definition: Controllers within the MVC pattern are responsible for responding to user input, often making changes to the Model in response to user input. In this way, Controllers in the MVC pattern are concerned with the flow of the application, working with data coming in, and providing data going out to the relevant View.

History of the Controller

Originally when MVC was conceived (as discussed in Chapter 2), things like graphical user interfaces (GUIs) with buttons and input boxes didn't exist. The Event-driven concept had not been created yet, so there needed to be a different way to "listen" for user input and work with it.

Back then, when the user pressed a key or clicked the screen, a process would "listen," and that process was the Controller. The Controller was responsible for receiving that input, interpreting it and updating whatever data class was required (the Model), and then notifying the user of changes or program updates (the View, which we cover in more detail in Chapter 6).

In the late 1970s and early 1980s, researchers at Xerox PARC (which, coincidentally, was where the MVC pattern was incubated) began working with the notion of the GUI, wherein users "worked" within a virtual "desktop" environment that they could click and drag items around on. This type of interaction with computers was something completely new, and it created a whole new way of thinking about programming.

From this came the idea of Event-driven programming — executing program actions based on events fired by a user, such as the click of a mouse or the pressing of a key on the keypad.

Over time, as GUIs became the norm, it became clear that the MVC pattern wasn't entirely appropriate for these new systems. In such a system, the GUI components themselves handle user input. If a button was pressed, and it was the button that responded to the mouse click, not a Controller. The button would, in turn, notify any observers or listeners that it had been clicked. Patterns such as the Model View Presenter (MVP) proved to be more relevant to these modern systems than the MVC pattern.

ASP.NET Web Forms is an event-based system, which is unique with respect to web application platforms. It has a rich control based event-driven programming model that developers code against, providing a nice componentized GUI for the Web. When you click a button, a Button control responds and raises an event on the server indicating that it's been clicked. The beauty of this approach is that it allows the developer to work at a higher level of abstraction when writing code.

Digging under the hood a bit, however, reveals that a lot of work is going on to simulate that componentized event-driven experience. At its core, when you click a button, your browser submits a request to the server containing the state of the controls on the page encapsulated in an encoded hidden input. On the server side, in response to this request, ASP.NET has to rebuild the entire control hierarchy and then interpret that request, using the contents of that request to restore the current state of the application for the current user. All this happens because the Web, by its nature, is stateless. With a rich client Windows GUI app, there's no need to rebuild the entire screen and control hierarchy every time the user clicks a UI widget because the app doesn't go away.

With the Web, the state of the app for the user essentially vanishes and then is restored with every click. Well, that's an oversimplification, but the user interface, in the form of HTML, is sent to the browser from the server. This raises the question: "Where is the application?" For most web pages, the application is a dance between client and server, each maintaining a tiny bit of state, perhaps a cookie on the client or chunk of memory on the server, all carefully orchestrated to cover up the Tiny Lie. The Lie is that the Internet and HTTP are stateful protocols.

The underpinning of event-driven programming (the concept of state) is lost when programming for the Web, and many are not willing to embrace the Lie of a "virtually stateful" platform. Given this, the industry has seen the resurgence of the MVC pattern, albeit with a few slight modifications.

Note 

One example of such a modification is that in traditional MVC, the Model can "observe" the View via an indirect association to the View. This allows the Model to change itself based on View events. With MVC for the Web, by the time the View is sent to the browser, the Model is generally no longer in memory and does not have the ability to observe events on the View. (Note that you'll see exceptions to this change when this book covers applying AJAX to MVC in Chapter 7.)

With MVC for the Web, the Controller is once again at the forefront. Applying this pattern requires that every user input to a web application simply take the form of a request. For example, with ASP.NET MVC, each request is routed (using Routing, discussed in Chapter 4) to a Controller method on that Controller (called an Action). The Controller is entirely responsible for interpreting that request, manipulating the Model if necessary, and then selecting a View to send back to the user via the response.

With that bit of theory out of the way, let's dig into ASP.NET MVC's specific implementation of Controllers. The next two sections of this chapter cover the basic abstractions that all Controllers implement. These sections dig under the hood a bit to provide a deep understanding of how Controllers are implemented in ASP.NET MVC, so they might not be as applicable to everyday development. The remaining sections cover the Controller class itself — which is the most important class that an ASP.NET MVC developer must understand — and provides most of the interesting functionality.

Defining the Controller: The IController Interface

As discussed in Chapters 2 and 3, among the core focuses of ASP.NET MVC are extensibility and flexibility. When building software this way, it's important to leverage abstraction as much as possible (we discuss this further in Chapter 11) by using interfaces.

For a class to be a Controller in ASP.NET MVC, it must at minimum implement the IController interface, and by convention the name of the class must end with the suffix "Controller." The naming convention is actually quite important — and you'll find that there are many of these small rules in play with ASP. NET MVC, which will make your life just a little bit easier by not making you define configuration settings and attributes. Ironically, the IController interface is quite simple given the power it is abstracting:

public interface IController{    void Execute(RequestContext requestContext);}

It's a simple process really: when a request comes in, the Routing system identifies a Controller, and it calls the Execute method. Let's look at a quick example (which assumes that you are using the default project template and thus have a standard route already configured).

Start by creating a new MVC Web Application (FileðNewðASP.NET MVC Web Application) and then a new a class in the "Controllers" folder (note: this should be a normal class file, not a new Controller) named SimpleController.

Next, implement IController by adding IController after the class name and then "press Ctrl-period" to implement the interface methods (this will stub out the Execute method for you). In the Execute method, have it simply write out "Hello World" as the response (it's not exactly groundbreaking, but it demonstrates how to write the simplest possible Controller):

using System.Web.Mvc;using System.Web.Routing;public class SimpleController : IController{    public void Execute(RequestContext requestContext)    {        var response = requestContext.HttpContext.Response;        response.Write("<h1>Hello World!</h1>");    }}

Now press Ctrl+F5 to compile the code and start your browser. In the address bar, you'll need to navigate to /simple. Because the port is chosen at random, you may not be able to tell the full URL, but on this author's machine it's http://localhost:61353/simple. Figure 5-1 shows the result.

Image from book
Figure 5-1

Apart from the large font, not exactly breathtaking, but overall the process is pretty simple.

The point of the IController interface is to provide a very simple starting point for anyone who wants to hook in their own Controller framework into ASP.NET MVC. The Controller class, which is covered later in this chapter, layers much more interesting behavior on top of this interface. This is a common extensibility pattern within ASP.NET.

For example, if you're familiar with HTTP handlers, you might have noticed that the IController interface looks very similar to IHttpHandler:

public interface IHttpHandler{    void ProcessRequest(HttpContext context);    bool IsReusable { get; }}

Ignoring the IsReusable property for a moment, IController and IHttpHandler are pretty much equivalent in terms of responsibility. The IController.Execute method and the IHttpHandler.ProcessRequest methods both respond to a request and write some output to a response. The main difference between the two is the amount of contextual information provided to the method. The IController.Execute method receives an instance of RequestContext, which includes not just the HttpContext but also other information relevant to a request for ASP.NET MVC.

The Page class, which is probably the class most familiar to ASP.NET Web Form developers as it is the default base class for an ASPX page, implements IHttpHandler.

The ControllerBase Abstract Base Class

Implementing IController is pretty easy, as you've seen, but really all it's doing is providing a facility for Routing to find your Controller and call Execute. This is the most basic "hook" into the system that you could ask for, but overall it provides little value to the Controller you're writing. This may be a good thing to you — many custom tool developers don't like it when a system they're trying to customize imposes a lot of restrictions (the ASP.NET Membership system is such a system). Others may like to work a bit closer with the API, and for that there is ControllerBase.

Important 

Product Team Aside

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

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

The ControllerBase class is an abstract base class that layers a bit more API surface on top of the IController interface. It provides the TempData and ViewData properties (which are ways of sending data to a View, discussed in Chapter 7), and the Execute method of ControllerBase is responsible for creating the ControllerContext, which provides the MVC-specific context for the current request much the same way that an instance of HttpContext provides the context for ASP.NET in general (providing request and response, URL, and server information, among elements).

This base class is still very lightweight and allows developers to provide extremely customized implementations for their own Controllers, while benefiting from the action filter infrastructure in ASP.NET MVC (ways of filtering and working with request/response data, which are discussed in Chapter 8). What it doesn't provide is the ability to convert actions into method calls. That's where the Controller class comes in.

 

The Controller Class and Actions

In theory, you could build an entire site with classes that simply implement ControllerBase or IController, and it would work. Routing would look for an IController by name and then call Execute, and you would have yourself a very, very basic web site.

This approach, however, is akin to working with ASP.NET using raw HttpHandlers — it would work, but you're left to reinvent the wheel and plumb the core framework logic yourself.

Interestingly, ASP.NET MVC itself is layered on top of HTTP handlers as you'll see later, and overall there was no need to make internal plumbing changes to ASP.NET to implement MVC. Instead, the ASP.NET MVC team simply layered this new framework on top of existing ASP.NET extensibility points.

The standard approach to writing a Controller is to have it inherit from the System.Web.Mvc. Controller abstract base class, which implements the ControllerBase base class. The Controller class is intended to serve as the base class for all Controllers, as it provides a lot of nice behaviors to Controllers that derive from it.

Action Methods

All public methods of a class that derive from Controller become action methods, which are callable via an HTTP request (the "RPC" style of system discussed in Chapter 2). So rather than one monolithic implementation of Execute, you can factor your Controller into action methods, each of which responds to a specific user input.

Important 

Product Team Aside

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

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

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

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

Let's walk through another simple Controller example, but this time let's add a public method. To get started with this example, open up the previous example and create a new Controller by right-clicking the Controllers folder and selecting AddðController, then name it Simple2Controller. Next, add the following code:

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

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

Image from book
Figure 5-2

As before, this is not exactly breathtaking, but it is a bit more interesting. Notice that the URL in the address bar directly correlates to the action method of your Controller. If you recall from the previous chapter, the default route for MVC breaks URLs into three main components: /{controller}/{action}/{id}. Let's look at how that applies to this example.

The simple2 portion of the URL corresponds to the Controller name. The MVC framework appends the "Controller" suffix to the Controller name and locates your Controller class, Simple2Controller.

/simple2/hello

The last portion of the URL corresponds to the action. The framework locates a public method with this name and attempts to call the method.

Working with Parameters

You can add any number of public methods (which we'll call Actions from here on out to keep with convention) to a Controller class, which will all be callable via this pattern. Actions may also contain parameters. Going back to the previous example, add a new action method that takes in a parameter:

public class Simple2Controller : Controller{    public void Goodbye(string name)    {        Response.Write("Goodbye "  + HttpUtility.HtmlEncode(name));    }}

This method is callable via the URL:

/simple2/goodbye?name=World

Notice that you can pass in parameters to an action method by name via the query string. You can also pass in parameters via the URL segments, discoverable by position as defined in your routes (discuss in Chapter 4). For example, the following URL is more aesthetically pleasing to many developers and Internet users:

/simple2/goodbye/world
Important 

Product Team Aside

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

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

/products/view.aspx?id=45434

which tells us nothing compared to:

/products/view/shoes

which provides more information about what we're looking at.

Working with parameters passed by URL segment requires you to define how Routing will identify these parameters in the URL. Fortunately, the default route (created for you when you click FileðNew) is already set up for you and contains a pretty common URL pattern: {controller}/{action}/{id}.

Changing the action method signature a little bit (by renaming the parameter "name" to "id") like so:

public class Simple2Controller : Controller{    public void Goodbye(string id)    {        Response.Write("Goodbye "+ HttpUtility.HtmlEncode(id));    }}

allows you to call that method using the "cleaner" URL, and Routing will pass the parameter by structured URL instead of a query string parameter:

/simple2/goodbye/world

Working with Multiple Parameters

What if you have a method with more than one parameter? This is a very common scenario, and rest assured that you can still use query strings, but if you want to pass both parameters via the URL segments, you'll need to define a new route specifically for this situation.

For example, suppose that you have an action method that calculates the distance between two points:

public void Distance(int x1, int y1, int x2, int y2){    double xSquared = Math.Pow(x2 - x1, 2);    double ySquared = Math.Pow(y2 - y1, 2);    Response.Write(Math.Sqrt(xSquared + ySquared));}

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

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

You can improve on this situation a bit by defining a route that allows you to specify the parameters in a cleaner format. This code goes inside the RegisterRoutes methods within the Global.asax.cs file, and uses the MapRoute method (discussed in Chapter 4) to define a new route:

routes.MapRoute("distance",    "simple2/distance/{x1},{y1}/{x2},{y2}",    new { Controller = "Simple2", action = "Distance" });

Notice that you are using the comma character to separate x and y coordinates. Now this action method is callable via the URL:

/simple2/distance/0,0/1,2

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

 
posted on 2010-06-26 13:20  Yan-Feng  阅读(557)  评论(0编辑  收藏  举报