ASP.NET MVC book: Chapter 1: Plumbing

ASP.NET MVC: From Webforms to MVC
by Jeff Putz
 
The following is a barely-edited draft from the forthcoming book
that will explore Microsoft’s ASP.NET MVC framework from the
view of a traditional Webforms developer. This chapter goes over
the basic plumbing of the framework, while subsequent chapters
will be more focused on the typical use cases that every developer
encounters, and how they relate to their Webforms analogs.
 
Again, this is a draft, so read with caution. You can find updates on
the status of the book, which will likely be published early summer,
2009, at the following locations:
 
http://weblogs.asp.net/Jeff/
 
http://twitter.com/jeffputz

Download a more formatted version in PDF form... it's much easier to read!

The text is ©2009, Jeff Putz, all rights reserved.

Feedback is welcome, especially if you find something that is outright incorrect. Thanks for reading! Did I mention the PDF version is easier to read?

EDIT, Previously:
Introduction

 

 


The M, The V, The C and Other Plumbing
 
We explained in the introduction what MVC is, the model-view-controller
pattern. Let’s get to a quick and dirty example to see it in action.
 
Hello kittehs
 
As a tribute to those illiterate captioning cats on the Internet, why don’t we
quickly build an application that gets the names of cats and displays them. We’ll
fire up Visual Studio, choose a new ASP.NET MVC Web Application from the
File -> New Project menu, and get to work immediately. For now, we’ll skip
creating a test project.

If we take a peak in the
solution explorer, we’ll see
all kinds of goodies created
for us. You’ll immediately
notice that there are several
folders that correspond with
the three facets of our
pattern, Models, Views and
Controllers. This is
where the app expects to
find the pieces by
convention, much in the
same way that it expects to
find compiled assemblies in
the /bin folder or themes in
/App_Themes.
 
You’ll also notice some
other folders for content and
scripts. The former is for
things like images and style
sheets, the latter is intended
for scripts. This project
template is good enough to
add both the jQuery library
and the ASP.NET AJAX
files for you.


Figure 1.1: The new MVC project generated
by Visual Studio.
 
 
--sidebar

You might be wondering if you’re stuck putting all of your code in
this structure, but rest assured, it’s not required. Most of the teams
that I’ve worked with break out all of their code into one or more
library projects, compiled into their own assemblies. All of your
model and controller code can go in separate projects, just make sure
that the MVC project references the other projects. You do lose the
convention-based association between controllers and views (we’ll
go over that momentarily), but that’s far from being a deal breaker.


--/sidebar

If you look a little closer, you’ll also notice that the Views folder has subfolders
that correspond to the names of the C# files found in Controllers. This allows
you to return views without having to explicitly choose them. That will be
obvious once we flesh out our simple example.
 
The Views folder contains .aspx files that make the views. They also may contain
.ascx files, which are known as partial views. They act somewhat like the user
controls you’re used to in Webforms, but they’re used in a different way.
Furthermore, it’s critical to understand that these views are not a one-to-one
mapping with URL’s in your app. There is no /views/home/index.aspx to view. If
you look at the web.config file found in the Views folder, you’ll notice an
HttpHandler is set up to block the direct viewing of these files entirely.
 
Before you start scratching your head, think back to our first description of how
MVC works. Incoming requests are routed to controllers, which based on some
logic, return a particular view. The missing link is the routing table, which takes
the URL of the request and hands it off to a controller. The routing table is
established in global.asax, so let’s take a peek at what the project template has
put there. The default is shown in Listing 1.1.
 
In the world of MVC, global.asax is used as the location for establishing the
route table, a special collection object used to map requests to controllers. The
RouteTable class has a single static property, Routes, of type
RouteCollection, used to store these mappings. It has the typical methods for
manipulating a collection, and a class of extension methods
(RouteCollectionExtensions) adds two other important methods,
IgnoreRoute() and MapRoute(). These do exactly what you expect, forcing
the MVC routing engine to ignore certain URL’s and send others to certain
controllers.


--sidebar
 
This is a very quick overview on routing. ASP.NET MVC is
extremely extensible, and you can do a lot with custom routes,
constraints, custom view engines, etc.


--/sidebar


Listing 1.1: The default global.asax file
 
using System.Web.Mvc;
using System.Web.Routing;
 
namespace HelloKittehs
{
  public class MvcApplication : System.Web.HttpApplication
  {
    public static void RegisterRoutes(RouteCollection
routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
      routes.MapRoute(
        "Default",        // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new {controller = "Home", action = "Index", id = ""}
          // Parameter defaults
      );
 
    }
 
    protected void Application_Start()
    {
      RegisterRoutes(RouteTable.Routes);
    }
  }
}
 
 
The first line of our RegisterRoutes() method tells the engine to ignore the
.axd pieces typically found in an ASP.NET application, like script handlers and
the trace page. The second one sets up the default route, using one of several
overloads. The comments generated explain what each parameter does. The first
is a name for the route, in this case, “Default.” The second parameter outlines the
URL structure by naming parameters that will establish the route, including the
controller name, the action to call on the controller and an ID to pass along. The
final parameter creates an anonymous type with the default values for the route
parameters. Following this convention, a request for “/home/getcat/3” will route
the request to the HomeController class, call its GetCat action method and
pass in “3” as a parameter.

--sidebar


Not familiar with anonymous types? They’re a key feature of C# 3.0
and essential to using LINQ, and to serialize JSON for AJAX calls.
An anonymous type creates a class with the properties you declare,
without the formal declaration of a named class. The anonymous
type in Listing 1.1 creates a class with controller, action and id
properties.

--/sidebar

One final thing to note in the default generated code, is the presence of a
Default.aspx page. Its code behind class creates and executes an instance of
the MvcHttpHandler class, which according to the routes just established will
execute the HomeController and call its Home method, since those are the
default values we established. That code is there so Internet Information Services
(IIS) knows what to do with a request that has no path.
 
Let’s get to our simple app. Delete the three folders under Views, but leave the
web.config in place. Delete the AccountController.cs file under
Controllers. Now that we have a clean start, right-click the Models folder and
add a new class file called KittehNames.cs. We’re going to write a
ridiculously simple method to return names, and you can see it Listing 1.2.
 
Listing 1.2: KittehNames.cs
 
namespace HelloKittehs.Models
{
  public class KittehNames
  {
    public static string[] Get()
    {
      string[] names = new [] {"Cosmo", "Emma", "Gideon",
"Oliver"};
      return names;
    }
  }
}
 
 
There isn’t anything remarkable about this method at all. It exists solely to return
an array of strings. Naturally you could also return an array or List<T> of
objects that offered more detail, like the cat’s gender or age, but we’ll keep it
simple with strings.
 
Let’s move on to the controller. So as not to change that which has been
generated for us, we’ll use the HomeController class, but remove the About()
method. We’ll replace the contents of the Index() method and the result will
look like Listing 1.3.
 
Requests routed to the HomeController class, taking the Index action, will
execute the Index() method. Controller actions return ActionResult objects,
which come in many derived forms, but for now we’ll stick to the ViewResult.
The System.Web.Mvc.Controller base class includes helper methods to
return results, including the View() method, which we have in our action here.
 
The only line we have added is one to retrieve the cat names, and add them to
ViewData property, a dictionary objet and member of the Controller base class.
Returning a call to the View() method, with no parameter, tells the controller to
look for a view in the Views folder, under a subfolder that matches the name of
the controller, Home, and an actual view file that matches the name of the action,
in this case Index.aspx. If it can’t find the view in the path
“/Views/Home/Index.aspx,” it will look in “/Views/Shared/Index.aspx.” These
naming conventions are built in to the MVC framework.
 
Listing 1.2: KittehNames.cs
 
using System.Web.Mvc;
using HelloKittehs.Models;
 
namespace HelloKittehs.Controllers
{
   public class HomeController : Controller
   {
      public ActionResult Index()
      {
         ViewData["AllKittehNames"] = KittehNames.Get();
         return View();
      }
   }
}
 
 
We deleted all of the views, so let’s create one now. To create one that follows
the naming conventions, right-click on our call to the View() method, as shown
in Figure 1.2. Uncheck the boxes and click OK.
 
 
Figure 1.2: Adding a view from a controller action.


 


 
The view is created with the path “/Views/Home/Index.aspx,” because it’s being
called by the HomeController class from the Index() action. Let’s fill it out
with Listing 1.3. The bold piece is new. 

 

Listing 1.3: /Views/Home/Index.aspx
 
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <title>Index</title>
</head>
<body>
  <div>
    <p>
      <% foreach (var name in ViewData["AllKittehNames"] as
string[]) { %>
        <%= name %><br />
      <% } %>
    </p>
  </div>
</body>
</html>
 
 
This view is about as simple as it gets. We’ve added a loop to cycle through all of
the values from the ViewData entry we added in the controller. Note that we use
the as keyword to indicate the type of the dictionary entry we’re calling. If the
object can’t be cast to the type indicated, it’s treated as a null. This is different
from a straight cast, which will throw an exception when the cast fails. That’s an
important consideration when choosing how you perform the cast.
 
Note the subtle, yet different syntax for executing code and for resolving a value.
Code execution happens inside of the <% %> block, while values are resolved and
output from the <%= %> block.
 
Figure 1.3: The default view, rendered.


 
 

 

If you run the site, you’ll get the results in your browser shown in Figure 1.3.
Because of the routing set up in global.asax, you can point the browser to
“/home/index” as well.
 
To recap, here’s what happens when the request is made to the server:
 
• The routing to a controller and action is determined. In the case of
“/home/index,” or the root of the site (which is handled by
Default.aspx), the routing table determines that MVC should
instantiate the HomeController class and call its Index() method.
The naming convention take the “home” and adds “Controller” to
determine the class name of the controller.
 
• The framework makes an instance of the HomeController, calling its
default constructor. We did not explicitly write one, but the compiler
makes it for us.
 
• The correct action method is called. The method must return a derivative
of ActionResult. In our example, the Index() method is called.
 
• Logic is performed as needed in the action method, but it should be
limited to handling input or, in this case, preparing data for output in the
view.
 
• The action method returns a view for rendering to the browser. Without
any parameters, we’re creating an instance of a view by naming
convention.
 
• The view executes its display logic, and uses data sent to it by the
controller.  
 
The great thing about this arrangement is the separation of concerns we’ve been
talking about from the start. The model, view and controller can all execute
without having to know anything about the internal processes of each other. This
is especially helpful for testing purposes, and we’ll get deeper into that later.
 
Models
 
The model part of MVC in the strictest sense refers to the data infrastructure for
your application. It’s the stateful part of your app, where real things are stored. In
the broader sense, the Models folder of your MVC app ends up acting much like
/App_Code, because your application logic goes here. It can also live in external
libraries, compiled projects that have no dependency on your application.
 
The most common set of tutorials you’ll see on models for MVC involve the
creation of data entities that map to a database technology, usually SQL Server.
We’ll go deeper into that in the next chapter.
 
However, it’s important to note that one of the key benefits to using ASP.NET
MVC is the ability to test the snot out of as much of your application as possible.
In that sense, solid designs that code against interfaces, combined with mocking
frameworks, make it easier to test your model code. Stay tuned for more on that.
 
Controllers
 
The System.Web.Mvc.Controller class is the basis for the magic glue that
bridges application logic and data (models) with the presentation of a user
interface (view). You’ve already seen how a request is mapped to a specific
controller. It should also be obvious that the controller must inherit from the
Controller base class.
 
By convention, these controllers must be named with the “Controller” suffix. So
or example, requests routed to the “Admin” controller require that the controller
class be named AdminController.
 
The second part of the routing equation calls a specific action method on the
controller class. These methods must have a return type of ActionResult. This
abstract class has ten derivatives (if you count the PartialViewResult). You
don’t need to create specific instances of these result classes, but can use the
helper methods that come free with the Controller class:
 
• View() – This is the most common helper you’ll use, as it returns
instances of the views that you create. It has several overloads, the
simplest being one that takes no parameters. It will look for a view that
matches the name of the action. So if your action is named Cat and your
controller class is AdminController, it will look for a view called  
Cat.aspx in /Views/Admin. If it can’t find it there, it will look in
/Views/Shared. There are additional overloads that allow you to
return a specific view and even specify a certain master page. By
passing an object in to View(), you can bind data to a strongly typed
view, as we’ll see in the next section.
 
• Redirect(), RedirectToAction() and RedirectToRoute() –
This set of methods and their various overloads do exactly what the
names imply. There are times when you don’t want to return a view, but
send the user to some other location. The regular Redirect() is about
the same as Response.Redirect() in Webforms. The other two take
a variety of parameters to send the user to a specific destination based
on the controller, action and route you specify. The helpful angle is that
you don’t need to know what the actual URL is, and if you change the
routing, you won’t have a hard-coded (and broken) URL in your code.
 
• Json() – The secret sauce to quick AJAX enriched Web apps is
JavaScript Object Notation, or JSON. This light-weight data format is
deal for sending short messages back to the client, and requires no
special parsing or processing in the browser. You can pass in any
serializable object to this method. We’ll be talking about it extensively
in a later chapter.
 
• JavaScript() – Returns, believe it or not, some JavaScript.
 
• File() – This one has some of the goodness you would expect from a
straight IHttpHandler in the Webforms world. You can pass it
Stream objects and byte arrays and specify the MIME type of the
response. This is ideal for returning images or documents.
 
• Content() – This is the best choice for returning string content. It too
allows you to specify the MIME type.
 
We can limit the kinds of requests that come in to controller actions with special
attributes, but we’ll cover those along with the use cases that match their use.
 
The Controller class also has several properties to get at the objects you would
typically find as part of System.Web.UI.Page, like Request, Response,
HttpContext and User.

--sidebar

As is the case with a lot of classes in the framework, there are many
members to explore, and we’re not going to mention them all.
Fortunately, they’re easy to Google by simply searching for the
class, like “system.web.mvc.controller class.” It’s even faster if you
have http://msdn.microsoft.com/ bookmarked. 

--/sidebar

 
Views
 
The views you create with .aspx file names are not pages, in the strict sense of
the word. You should think of them as, well, views on data! Several different
URL’s can eventually route to controllers and actions that serve up the same
views, so there is not a one-to-one relationship between the URL and the view.
 
At the very least, a view is any HTML that you choose. Using a series of helpers,
we can cause additional markup to be rendered around data that we pass through
to a view from a controller action. This results in what appears at first to look like
classic ASP, but it’s not. As a rule, there should be absolutely no logic in the
page unless it has something to do with the display (or validation) of data. You
would never calculate tax or make a database call from a view. On the other
hand, it’s OK to decide whether or not to display a block of HTML based on
whether or not the user is logged in.

--sidebar

Hardcore computer science people may engage in some level of
debate about what data is OK to act on. For example, checking
Request.IsAuthenticated definitely isn’t rooted in the data populated
by the controller, but it’s a common sense way to do things. My
advice: It’s a not a religion, so if it doesn’t violate the separation of
concerns by creating a difficult dependency, don’t lose sleep over it.

--/sidebar


HTML helpers create form tags, user interface elements, links and other useful
stuff for rendering in your views. The class used for this is
System.Web.Mvc.HtmlHelper, augmented by several classes full of extension
methods. Because all view pages inherit from the ViewPage class, you can use
its Html property to access these helpers. In the next chapter, we’ll demonstrate
how many of these helpers are used.
 
There are also a great many methods for use in the AjaxHelper class and its
extension methods.
 
While ViewPage does inherit from the traditional Page class, most of what you
find there is not relevant to an MVC view. The HTML helpers and straight forms
do not participate in the event or postback model, so avoid getting into the events
you’re used to using in Webforms.
 
There are several new properties that you will want to take note of.
 
• Html – As mentioned previously, this property accesses the wealth of
the HtmlHelper class and its various extensions.
 
• ViewData – Remember our simple example that rendered cat names?
This property is your one-stop access to the data assigned in the
controller.
 
• ViewContext – This property give you access to ViewContext, which
is a derivative of ControllerContext. It gives you access to the
Controller, HttpContext, RequestContext, RouteData and
TempData. Be careful when accessing these objects so as not to create
any dependencies on specific controllers.
 
• Ajax – Provides access to the AjaxHelper class and its extensions.
 
Views can also be strongly typed. ViewPage and ViewUserConrol (the class
used for partial views, later in this chapter) have a generic descendent that tie the
view to a specific model. Visual Studio will automatically set this up for you in
the create view dialog, which we’ll use later. We can rewrite our original view
and controller action method from HelloKitteh as shown in Listing 1.4

 
 
Listing 1.4: Strongly typing the view, and passing a model to it
 
HomeController.cs
namespace HelloKittehs.Controllers
{
   public class HomeController : Controller
   {
      public ActionResult Index()
      {
         var names = KittehNames.Get();
         return View(names);
      }
   }
}
 
/Views/Home/Index.aspx
<%@ Page Language="C#"
Inherits="System.Web.Mvc.ViewPage<String[]>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
   <title>Index</title>
</head>
<body>
   <div>
      <p>
         <% foreach (var name in Model) { %>
            <%= name %><br />
         <% } %>
      </p>
   </div>
</body>
</html>
 
 
The changes we made are in bold, as compared to Listings 1.2 and 1.3. We’re
passing an object to the controller’s View() method, which is designated as the
model for the view. The view itself inherits from a generic version of ViewPage,
in this case, ViewPage<String[]>. You can use any type you like here,
including complex types that encapsulate any number of different data items. If
our view were more complex, the Intellisense in the page would generate
whatever properties and methods are found on the model when we type a period
after it, because the Model property is an alias to the type you’ve designated in
the @Page declaration.

--sidebar

The cool thing going on here is that instead of a dictionary object
like ViewData, we’re enforcing some very specific contracts
between the controller and the view. Pulling stuff from the
dictionary isn’t as safe and there’s no warning at compile time about
whether or not the data will be there. It makes testing a lot easier too,
because your controller methods return strongly typed data. That
said, it may not be appropriate for every case, and you can still use a
combination of a strongly typed view and ViewData. 

--/sidebar

 

Under the hood of ASP.NET MVC
 
Let’s step back for a moment to talk about what’s going on under the hood,
especially since we glossed over the notion that there is no one-to-one
relationship between URL’s and views.
 
In a traditional Webforms application, a request comes into the Web server,
Internet Information Server (IIS), and does one of two things, depending on the
version of IIS. If it’s IIS 6, the server determines that the incoming request
should be handled by ASP.NET, usually because of the file extension, like
.aspx. If it’s IIS 7, ASP.NET already has access to everything coming in.
 
Next, ASP.NET checks web.config’s HttpHandlers section to see what class
should handle the request. If no entry is found, it checks at the machine level,
which says that .aspx requests will be handed off to
System.Web.UI.PageHandlerFactory, a class that implements the
IHttpHandlerFactory interface. This class looks at the URL of the request,
and serves up the appropriate instance of a class that implements
IHttpHandler. As you may guess, this will be your page, which inherits from
the Page class (which implements IHttpHandler). From there, all of the
familiar things happen inside your page.
 
If you’ve never implemented an IHttpHandler class yourself, the premise is
pretty simple: Make something to send back to the browser. At its most basic
level, it provides access to the stream that is sent out by the server to the client.
One of the most common uses that I’ve encountered is to send the bytes that
together make an image that is stored in a database. You set the MIME type, put
the bytes in the output stream, and enjoy.
 

--sidebar

It’s true, you can do the same thing in the Load event of a page, but
you’re creating all of that overhead and baggage that comes with
creating a page. Remember, a page is just a really robust
HttpHandler. It’s easier and more efficient to strip it down and
implement your own for something like serving an image.

--/sidebar

Understanding how a request is handled, you’re empowered even in Webforms to
break the notion that a request must map to an actual file. You might build an
application with a handler that intercepts URL’s like “/news/371.aspx” and
creates an instance of the page of your choosing using
System.Web.UI.PageParser.GetCompiledPageInsance(). At that point,
there’s no going back to thinking of your app as a series of pages, but rather an
application!
 
It’s not hard to figure out what’s going on with MVC once you have this
knowledge either. The differences are, however, noticeable between the two
versions of IIS. In IIS 6, an .mvc file extension is mapped to ASP.NET, and
handled by an HttpHandler specified in web.config. The reason for this is that
IIS 6 by default treats ASP.NET as a subsystem that runs as an ISAPI module
(Internet Server Application Programming Interface). We’re not going to get into
it here, but as the name slightly implies, it’s a way to plug in pretty much
anything into the server’s request/response pipeline. So now a request ending in
.mvc is routed to ASP.NET, and new entries in web.config in turn map it to the
right handler. If you want to work without these nasty file extensions, you can
simply set the wildcard map in IIS 6 to send all requests to ASP.NET.
 
IIS 7 integrates ASP.NET much closer to the Ethernet port on the server, so to
speak. That means that configuration files are considered immediately. The
system.webServer section of web.config allows you to adjust the plumbing
without having to set configuration properties in the IIS control panel.
 
This probably has you wondering how ASP.NET MVC knows how to handle a
request with no particular file extension. Enter the HttpModule! These classes
implement IHttpModule, and have access to many events in the
request/response lifecycle, set at the application level. Back in the early days of
ASP.NET this interface is what you used to implement your own logic to assign
roles to user on each request. You might use it today to implement logic that
indicates a user’s subscription status. And because it has access to the Error
event, you can build an error logging module here too.
 
ASP.NET MVC uses System.Web.Routing.UrlRoutingModule to look at
the request, compare it to the routing table, and find the right IRouteHandler
object for the route, the default being the MvcRouteHandler. IRouteHandlers
are intended to return an IHttpHandler (which is not specific to MVC, thus the
inclusion in the System.Web.Routing namespace), and MvcRouteHandler
returns an instance of MvcHandler. That class loads an instance of the
appropriate Controller derivative, which fires off an action. Got all that? One
of the important points here is that the whole system is extensible, and starting at
the top, with the routing table, you can specify alternate route handlers.
 
More on routing
 
You’ll recall in our trivial example that global.asax declares a default route to
follow:
 
routes.MapRoute(
   "Default",         // Route name
   "{controller}/{action}/{id}", // URL with parameters
   new {controller = "Home", action = "Index", id = ""}
          // Parameter defaults
      );
 
This route says the first component in the path of the URL will name the
controller, the second will name the action to take, and the third will name an ID
of some kind. When any of them are missing, from right to left, the defaults
specified in the third parameter are used. Thus, no path in the URL would call the
Index method on the HomeController, a URL of “/Admin” will call the
Index method of the AdminController, and if no ID is specified, it will try
first to call an action method with no parameters.
 
Chances are good that you don’t want to be constrained to this one routing
situation. You could have a blogging app (oh, wait, we have one of those!),
where the path should be “/post/some-post-name,” but using the default route,
this maps to the PostController class calling an action method of some-
post-name, which obviously would not exits. This is a case for a custom route,
which we’ve typed out in Listing 1.5.
 
Listing 1.5: Adding a custom route to global.asax
 
routes.MapRoute("PostRoute", "blog/{id}",  
   new {controller = "Post", action = "Detail"});
 
routes.MapRoute("Default", "{controller}/{action}/{id}",  
   new {controller = "Home", action = "Index", id = ""});
 
 
The first thing you’ll notice is that we explicitly spell out the first part of the
URL, with “blog” followed by the slash and the “id” in brackets. Because we’re
not getting a controller name or action from the URL, we specify the one to use
in the default object parameter, in this case the Detail action method of the
PostController.
 
As we’ll find later in more detailed examples, the kind of data you want to pass
to an action varies. In some cases, passing a string where the method wants an
integer will simply throw an exception (like “/Admin/Edit/foo” when the method
is Edit(int id), for example). That may or may not be adequate for your
exception handling skills. But there are also cases where you may actually
require one or the other. Fortunately, you can pass a fourth parameter into the
MapRoute() method that indicates a regular expression that the request must
conform to. If you only wanted to match blog entries by an integer ID, you would
map the route as indicated in Listing 1.6.
 
Listing 1.6: Adding a custom route with a constraint
 
routes.MapRoute("PostRoute", "blog/{id}",  
   new {controller = "Post", action = "Detail"},
   new {id = @"\d+"});
 
 
The regular expression forces a match for requests like “/blog/509” but not
“/blog/foo.” I know, a lot of developers hate regular expressions, but the power in
front of you is nearly limitless. You can require that the path match any arbitrary
thing you can come up with!
 
As with everything else in ASP.NET MVC, constraints are also extensible. The
framework provides a simple interface called IRouteConstraint, and with it,
you can implement almost any kind of logic you can think of to accept or decline
the matching of a request. The basic scaffolding of a custom route constraint
class is shown in Listing 1.7.
 
Listing 1.7: A bare bones custom route constraint class
 
public class SampleRouteConstraint : IRouteConstraint
{
   public bool Match(HttpContextBase httpContext,  
      Route route,  
      string parameterName,  
      RouteValueDictionary values,  
      RouteDirection routeDirection)
   {
      // do some logic
      return true;
   }
}
 
 
There isn’t much to the interface, only a single method called Match(), which
returns a Boolean value indicating whether or not the request meets the constraint
(this one isn’t useful since it always returns true). You’ve got all of the relevant
data here to act on and make a decision on, including the HttpContext, so you
could make the matching criteria consider the identity or role of a user. You also
may consider some deeper format for the path that you can’t express (or would
rather not express) with a regular expression.
 
The order in which you create routes is the order in which MVC tries to find a
match, so the default route is placed last, unless you have some clever reason to
do otherwise.


Master pages and partial views
 
By now you probably have a pretty good feel about how ASP.NET MVC works
compared to Webforms, but can’t imagine life without the beloved master page,
and probably user controls. Let’s review how those work.
 
A master page, or more to the point, a System.Web.UI.MasterPage, is a page
template from which other pages are derived from. In the Webforms world, it has
its own code behind and can conduct its own logic. It has most of the “outside”
HTML for the page, including the <head> and <html> tags, leaving
placeholders that will fill in the “inside” content from pages that use the master
page. These pages end with a .master extension.
 
User controls, typically files ending with .ascx, are a bit of an opposite from
master pages, as they’re fragments of markup and code that can dropped into a
page. Really old school developers might call them a robust version of server-
side includes, although these are much better since they can have awareness of
the page and can be loaded dynamically.
 
Master pages are still used in MVC, but in keeping with the separation of
concerns, they don’t do anything beyond simple display logic. If they need to
display data, the data should come from a controller. Let’s go back to our cat
names. Imagine that we intend to show those cat names on every page using our
master. Let’s work our way from the view backward, starting with the master
page itself in Listing 1.8.
 
Listing 1.8: /Views/Shared/Site.master
 
<%@ Master Language="C#"
Inherits="System.Web.Mvc.ViewMasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <title>Kittehs</title>
</head>
<body>
  <div>
    <p>
      <% foreach (var name in ViewData["AllKittehNames"] as
string[]) { %>
        <%= name %><br />
      <% } %>
    </p>
  </div>
 <asp:ContentPlaceHolder ID="MainContent" runat="server" />
</body>
</html>
 
 
If this reminds you of Listing 1.3, it should! The new and changed parts are in
bold. First, the directive was changed from Page to Master, and the Inherits
attribute changed to ViewMasterPage. Second, we’ve added a
ContentPlaceHolder, which is where pages using the master will put their
goodies. This part works just as it does in Webforms.
 
Next, let’s build a sample page that uses the master, as in Listing 1.9. Again, this
is fairly straight forward, and works similar to the way Webforms masters work.
 
 
Listing 1.9: Some random page using our master page
 
<%@ Page Title="" Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage" %>
 
<asp:Content ID="Content1"
ContentPlaceHolderID="MainContent" runat="server">
 
   <p>Nothing exciting ever happens here.</p>
 
</asp:Content>
 
 
If this page were our home page using the default routing (“/home/index”), the
view would be in “/Views/Home/Index.aspx” as expected. Countless other views
would also use this master, and those views would likely be called by any
number of controllers and their actions. The trick is that we need a centralized
place to populate view data the master can use, regardless of the view called
upon. Setting up this data in every controller action would suck, and be a waste
of time.
 
Fortunately, your object-oriented skills force you to write better code, and do it
once. You know that all controllers inherit from the Controller class, so it only
makes sense that you extend Controller yourself, and have all of your
controllers derive from that class instead. Listing 1.10 shows us our new
improved controller base and a skeleton of a home controller.
 
Listing 1.10: A controller base class and a controller that inherits from it
 
namespace HelloKittehs.Controllers
{
   public abstract class ControllerBase : Controller
   {
      public ControllerBase()
      {
         ViewData["AllKittehNames"] = KittehNames.Get();
      }
   }

 
   public class HomeController : ControllerBase
   {
      public ActionResult Index()
      {
         return View();
      }
   }
}
 
 
Now any controller that inherits from the ControllerBase will get the names
of the cats and place them in the ViewData collection, for use by the master
page. Note that we’ve marked the base class as abstract so it’s not accidentally
used as a controller directly.
 
The approximate analog to user controls are called partial views. They live in
.ascx files, and like master pages, they should not contain any real logic beyond
display tactics. Instead of registering them, as you would in Webforms, you drop
them in the page using a simple code block like this:
 
<% Html.RenderPartial("UserPartial"); %>
 
This is from a case we’ll show you later, where we reuse components of a form
because they appear exactly the same in more than one view. For example, the
form fields used to add a user and edit a user are identical, so it’s easier to create
them once, and keep maintenance down to one place. The line of code above
follows the same conventions as the View() method of controllers, looking first
in the controller’s cousin folder (like “/Views/Admin”), and if it can’t find it
there, it’ll check the “/Views/Shared” folder. Since it’s a partial view, it’s looking
for UserPartial.ascx instead of an .aspx file.
 
Getting data to these partial views works the same way as it does for full views.
The data must be added to the ViewData in the controller action. You won’t
likely need to do it in some abstract base class, as you should know in most cases
that the partial view will be a part of the view you’re instantiating. As such, make
sure you get the partial view’s data set in the action method.

5 Comments

  • Hello Jeff,

    Could you possibly release the book as CHM or XPS? I don't think PDF is a very good format for reading, really. For me, and I suppose for many other people as well, Windows Help is much more convenient for reading.

    Why?

    1. To go to a specific chapter I don't need to scroll down, I can just select it from the navigation menu.
    2. Big PDF files tend to be slow
    3. It's much easier to search through a CHM or XPS. For instance, when I search in a PDF for "entity" it will show me every occurrence of the word. At the same time, when I search in a CHM, it will return me chapters. And most relevant chapters will be on top.

  • Seriously? A PDF has the proper navigation if it's set up that way, but seeing as how this is just a dump from Word, this one does not.

  • Hey Jeff -
    I must commend you on taking the initiative on writing another book and this time on ASP.NET MVC.
    Best wishes on the project.
    SBC

  • CHM sucks big time. Can't be printed unless you have a third-party tool. I don't wanna read 300+ page document on screen.

  • Hey Jeff -

    I've been looking for a book just like this - one that explains ASP.Net MVC from the WebForms perspective.

    The chapter you've posted is great - gave me just enough info to begin exploring on my own instead of thrashing about wildly.

    I look forward to the final product!

    Nathan

Comments have been disabled for this content.