c#
<% foreach (var item in Model) { %>
<tr>
<!-- ... -->
<td><%= Html.Encode(item.ProductID) %></td>
<td><%= Html.Encode(item.Name) %></td>
<!-- ... -->
</tr>
<% } %>
rendering a Ui with Views .
449
Vb
< % For Each item In Model% >
<tr>
<!-- ... -->
<td><%= Html.Encode(item.ProductID) %></td>
<td><%= Html.Encode(item.Name) %></td>
<!-- ... -->
</tr>
<% Next%>
Visual Studio is able to infer the type of model because you created a strongly
typed view. In the page directive you can see that this view doesn’t inherit from
System.Web.Mvc.Page. Instead, it inherits from the generic version, which
states that the model will be an IEnumerable collection of Product objects. This
in turn exposes a Model property with that type. Note that you can still pass the
wrong type of item to the view from the controller. In the case of a strongly
typed view this will result in a run time exception.
Each of the properties of the products is HTML encoded before it is rendered using the Encode
method on the Html helper property. This prevents common issues with malicious code injected
into the application masquerading as valid user data. ASP.NET MVC is able to take advantage of
the new
:%<
… % > markup, which uses a colon in the place of the equals sign in ASP.NET 4 to more
easily perform this encoding. Here is the same snippet again taking advantage of this technique:
c#
<% foreach (var item in Model) { %>
<tr>
<!-- ... -->
<td><%: item.ProductID %></td>
<td><%: item.Name %></td>
<!-- ... -->
</tr>
<% } %>
Vb
<% For Each item In Model%>
<tr>
<!-- ... -->
<td><%: item.ProductID %></td>
<td><%: item.Name %></td>
450 .
chaPter 21 ASp.neT mVc
<!-- ... -->
</tr>
<% Next%>
In addition to the Encode method there is one other Html helper method being used by this view.
This is the ActionLink helper. This method will emit a standard HTML anchor tag designed to
trigger the specified action. Two forms are in use here. The simplest of these is the one designed to
create a new Product record:
c#
<p>
<%= Html.ActionLink(“Create New”, “Create”) %>
</p>
Vb
<p>
<%=Html.ActionLink(“Create New”, “Create”)%>
</p>
The first parameter is the text that will be rendered inside the anchor tag. This is the text that will
be presented to the user. The second parameter is the name of the action to trigger. Because no
controller has been specified the current controller is assumed.
The more complex use of ActionLink is used to render the edit and delete links for each product.
c#
<td>
<%= Html.ActionLink(“Edit”, “Edit”, new { id=item.ProductID }) %> |
<%= Html.ActionLink(“Details”, “Details”, new { id=item.ProductID })%>
</td>
Vb
<td>
<%=Html.ActionLink(“Edit”, “Edit”, New With {.id = item.ProductID})%> |
<%=Html.ActionLink(“Details”, “Details”, New With {.id = item.ProductID})%>
</td>
The first two parameters are the same as before and represent the link text and the action name,
respectively. The third parameter is an anonymous object that contains data to be passed to the
action method when it is called.
When you run the application and enter /products/ in your address bar you will be presented with
the page displayed in Figure 21-6. Trying to click any of the links will cause a run time exception
because the target action does not yet exist.
adVanced MVc
This section provides an overview for some of the more advanced features of ASP.NET MVC.
routing
As you were navigating around the MVC site in your web browser you might have noticed that the
URLs are quite different from a normal ASP.NET web site. They do not contain fi le extensions
and they do not match up with the underlying folder structure. These URLs are mapped to action
methods and controllers with a set of classes that belong to the routing engine, which is located in
the System.Web.Routing assembly.
fiGure 21 - 6
Once you have a view and a controller you can use the shortcut Ctrl+M, Ctrl+G
to toggle between the two.
The routing engine was originally developed as a part of the ASP.NET MVC
project but was released as a standalone library before MVC shipped. Although
it is not described in this book it is possible to use the routing engine with ASP.
NET Web Forms projects.
advanced MVC . 451
452
.
chaPter 21 ASp.neT mVc
In the previous example you created a simple list view for products. This list view was based on the
standard List template, which renders the following snippet for each Product in the database being
displayed:
c#
<td>
<%= Html.ActionLink(“Edit”, “Edit”, new { id=item.ProductID }) %> |
<%= Html.ActionLink(“Details”, “Details”, new { id=item.ProductID })%>
</td>
Vb
<td>
<%=Html.ActionLink(“Edit”, “Edit”, New With {.id = item.ProductID})%> |
<%=Html.ActionLink(“Details”, “Details”, New With {.id = item.ProductID})%>
</td>
If you examine the generated HTML markup of the final page you should see that this becomes the
following:
htMl
<td>
<a href=”/Products/Edit/2”>Edit</a> |
<a href=”/Products/Details/2”>Details</a>
</td>
These URLs are made up of three parts:
.
“Products” is the name of the controller. There is a corresponding ProductsController in
the project.
.
“Edit” and “Details” are the names of action methods on the controller. The
ProductsController will have methods called Edit and Details.
.
“2” is a parameter that is called “id.”
Each of these components is defined in a route, which is set up in the Global.asax.cs file (or the
Global.asax.vb file for VB) in a method called RegisterRoutes. When the application first starts
it calls this method and passes in the System.Web.Routing.RouteTable.Routes static collection.
This collection contains all of the routes for the entire application.
c#
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
Code snippet Global.asax.cs
advanced MVC .
453
Vb
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
routes.MapRoute( _
"Default", _
"{controller}/{action}/{id}", _
New With {.controller = "Home", .action = "Index", .id = ""} _
)
End Sub
Code snippet Global.asax.vb
The first method call tells the routing engine that it should ignore all requests for .axd files. When
an incoming URL matches this route the engine will completely ignore it and allow other parts of the
application to handle it. This method can be very handy if you want to integrate Web Forms and MVC
into a single application. All you need to do is ask the routing engine to ignore .aspx and .asmx files.
The second method call defines a new Route and adds it to the collection. This overload of MapRoute
method takes three parameters. The first parameter is a name, which can be used as a handle to this
route later on. The second parameter is a URL template. This parameter can have normal text along
with special tokens inside of braces. These tokens will be used as placeholders that are filled in when
the route matches a URL. Some tokens are reserved and will be used by the MVC routing engine
to select a controller and execute the correct action. The final parameter is a dictionary of default
values. You can see that this “Default” route matches any URL in the form /controller/action/id
where the default controller is “Home,” the default action is “Index,” and the “id” parameter defaults
to an empty string.
When a new HTTP request comes in, each route in the RouteCollection tries to match the URL
against its URL template in the order that they are added. The first route that is able to do so fills
in any default values that haven’t been supplied. Once these values have all been collected then a
Controller is created and an action method is called.
Routes are also used to generate URLs inside of views. When a helper needs a URL it will consult each
route (in order again) to see if it is able to build a URL for the specified controller, action, and parameter
values. The first route to match will generate the correct URL. If a route encounters a parameter value
that it doesn’t know about, it becomes a query string parameter in the generated URL.
The following snippet declares a new route for an online store that allows for two parameters: a
category and a subcategory. Assuming that this MVC application has been deployed to the root of a
web server, requests for the URL http://servername/Shop/Accessories/Helmets will go to the
“List” action on the “Products” controller with the parameters Category set to “Accessories” and
Subcategory set to “Helmets.”
c#
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
454 .
chaPter 21 ASp.neT mVc
"ProductsDisplay",
"Shop/{category}/{subcategory}",
new {
controller = "Products",
action = "List",
category = "",
subcategory = ""
}
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
Code snippet Global.asax.cs
Vb
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
routes.MapRoute( _
"ProductsDisplay", _
"Shop/{category}/{subcategory}", _
New With { _
.controller = "Products", .action = "List", _
.category = "", .subcategory = "" _
})
routes.MapRoute( _
"Default", _
"{controller}/{action}/{id}", _
New With {.controller = "Home", .action = "Index", .id = ""} _
)
End Sub
Code snippet Global.asax.vb
Once a Route in a RouteCollection matches the URL no other Route gets
the opportunity. Because of this, the order in which Routes are added to the
RouteCollection can be quite important. If the previous snippet had placed
the new route after the Default one, it would never get to match an incoming
request because a request for /Shop/Accessories/Helmets would be looking for
an Accessories action method on a ShopController with an “id” of “Helmets.”
Because there isn’t a ShopController the whole request will fail. If your application
is not going to the expected controller action method for a URL, you might want to
add a more specific Route to the RouteCollection before the more general ones or
remove the more general ones altogether while you figure out the problem.
advanced MVC .
455
Finally, you can also add constraints to the Route that will prevent it from matching a URL unless
some other condition is met. This can be a good idea if your parameters are going to be converted
into complex data types such as date times later on and require a very specific format. The most
basic kind of restraint is a string, which is interpreted as a regular expression that a parameter must
match for the route to take effect. The following route definition uses this technique to ensure that
the zipCode parameter is exactly five digits:
c#
routes.MapRoute(
"StoreFinder",
"Stores/Find/{zipCode}",
new { controller = "StoreFinder", action = "list" },
new { zipCode = @"^\d{5}$" }
);
Vb
routes.MapRoute( _
"StoreFinder", _
"Stores/Find/{zipCode}", _
New With {.controller = "StoreFinder", .action = "list"}, _
New With {.zipCode = "^\d{5}$"} _
)
The other type of constraint is a class that implements IRouteConstraint. This interface defines
a single method Match that returns a Boolean value indicating whether the incoming request
satisfies the constraint. There is one implementation of IRouteConstraint out of the box called
HttpMethodConstraint. This constraint can be used to ensure that the correct HTTP method, such
as GET, POST, HEAD, or DELETE is used. The following route only accepts HTTP POST requests:
c#
routes.MapRoute(
"PostOnlyRoute",
"Post/{action}",
new { controller = "Post" },
new { post = new HttpMethodConstraint("POST") }
);
Vb
routes.MapRoute(
"PostOnlyRoute", _
"Post/{action}", _
New With {.controller = "Post"}, _
New With {.post = New HttpMethodConstraint("POST")} _
)
The URL routing classes are very powerful and flexible, and allow you to easily create “pretty”
URLs. This can aid users navigating around your site and even improve your site’s ranking with
search engines.
456 .
chaPter 21 ASp.neT mVc
action Method Parameters
All of the action methods in previous examples do not accept any input from outside of the
application to perform their tasks; they rely entirely on the state of the model. In real-world