Documente Academic
Documente Profesional
Documente Cultură
One of the things that many people have asked for over the
years with ASP.NET is built-in support for developing web
applications using a model-view-controller (MVC) based
architecture.
Last weekend at the Alt.NET conference in Austin I gave the
first public demonstration of a new ASP.NET MVC
framework that my team has been working on. You can
watch a video of my presentation about it on Scott
Hanselman's blog here.
We'll be releasing a public preview of this ASP.NET MVC
Framework a little later this year. We'll then ship it as a fully
supported ASP.NET feature in the first half of next year.
"Models" in a MVC based application are
the components of the application that
are responsible for maintaining state. Often this
state is persisted inside a database (for example: we
might have a Product class that is used to represent
order data from the Products table inside SQL).
"Views" in a MVC based application are the
components responsible for
displaying the application's user interface. Typically
this UI is created off of the model data (for example:
we might create an Product "Edit" view that surfaces
textboxes, dropdowns and checkboxes based on the
current state of a Product object).
"Controllers" in a MVC based application are the components responsible for handling end user
interaction, manipulating the model, and ultimately choosing a view to render to display UI. In a
MVC application the view is only about displaying information - it is the controller that handles and
responds to user input and interaction.
One of the benefits of using a MVC methodology is that it helps enforce a clean separation of concerns
between the models, views and controllers within an application. Maintaining a clean separation of
concerns makes the testing of applications much easier, since the contract between different application
components are more clearly defined and articulated.
The MVC pattern can also help enable red/green test driven development (TDD) - where you implement
automated unit tests, which define and verify the requirements of new code, first before you actually write
the code itself.
c !c
I'll be doing some in-depth tutorial posts about the new ASP.NET MVC framework in a few weeks once
the bits are available for download (in the meantime the best way to learn more is to watch the video of
my Alt.net presentation).
A few quick details to share in the meantime about the ASP.NET MVC framework:
It enables clean separation of concerns, testability, and TDD by default. All core contracts within
the MVC framework are interface based and easily mockable (it includes interface based
IHttpRequest/IHttpResponse intrinsics). You can unit test the application without having to run
the Controllers within an ASP.NET process (making unit testing fast). You can use any unit
testing framework you want to-do this testing (including NUnit, MBUnit, MS Test, etc).
It is highly extensible and pluggable. Everything in the MVC framework is designed so that it can
be easily replaced/customized (for example: you can optionally plug-in your own view engine,
routing policy, parameter serialization, etc). It also supports using existing dependency injection
and IOC container models (Windsor, Spring.Net, NHibernate, etc).
It includes a very powerful URL mapping component that enables you to build applications with
clean URLs. URLs do not need to have extensions within them, and are designed to easily
support SEO and REST-friendly naming patterns. For example, I could easily map
the /products/edit/4 URL to the "Edit" action of the ProductsController class in my project above,
or map the /Blogs/scottgu/10-10-2007/SomeTopic/ URL to a "DisplayPost" action of a
BlogEngineController class.
The MVC framework supports using the existing ASP.NET .ASPX, .ASCX, and .Master markup
files as "view templates" (meaning you can easily use existing ASP.NET features like nested
master pages, <%= %> snippets, declarative server controls, templates, data-binding,
localization, etc). It does not, however, use the existing post-back model for interactions back to
the server. Instead, you'll route all end-user interactions to a Controller class instead - which
helps ensure clean separation of concerns and testability (it also means no viewstate or page
lifecycle with MVC based views).
The ASP.NET MVC framework fully supports existing ASP.NET features like forms/windows
authentication, URL authorization, membership/roles, output and data caching, session/profile
state management, health monitoring, configuration system, the provider architecture, etc.
"
If you are looking to build your web applications using a MVC approach, I think you'll find this new
ASP.NET MVC Framework option very clean and easy to use. It will enable you to easily maintain
separation of concerns in your applications, as well as facilitate clean testing and TDD.
I'll post more tutorials in the weeks ahead on how the new MVC features work, as well as how you can
take advantage of them.
p p
Two weeks ago I blogged about a new MVC (Model View Controller) framework for
ASP.NET that we are going to be supporting as an optional feature soon. It provides a
structured model that enforces a clear separation of concerns within applications, and
makes it easier to unit test your code and support a TDD workflow. It also helps provide
more control over the URLs you publish in your applications, and can optionally provide
more control over the HTML that is emitted from them.
Since then I've been answering a lot of questions from people eager to learn more
about it. Given the level of interest I thought it might make sense to put together a few
blog posts that describe how to use it in more detail. This first post is one of several I'll
be doing in the weeks ahead.
I'm going to use a simple e-commerce store application to help illustrate how the
ASP.NET MVC Framework works. For today's post I'll be implementing a product
listing/browsing scenario in it.
Specifically, we are going to build a store-front that enables end-users to browse a list of
product categories when they visit the Ñ Ñ
URL on the site:
When a user clicks on a product category hyperlink on the above page, they'll navigate
to a product category listing URL - Ñ Ñ
Ñ
- that lists the active
products within the specific category:
When a user clicks an individual product, they'll navigate to a product details URL -
Ñ Ñ
Ñ - that displays more details about the specific product they
selected:
We'll build all of the above functionality using the new ASP.NET MVC Framework. This
will enable us to maintain a "clean separation of concerns" amongst the different
components of the application, and enable us to more easily integrate unit testing and
test driven development.
The ASP.NET MVC Framework includes Visual Studio Project Templates that make it
easy to create a new web application with it. Simply select the File->New Project menu
item and choose the "ASP.NET MVC Web Application" template to create a new web
application using it.
By default when you create a new application using this option, Visual Studio will create
a new solution for you and add two projects into it. The first project is a web project
where you'll implement your application. The second is a testing project that you can
use to write unit tests against it:
You can use any unit testing framework (including NUnit, MBUnit, MSTest, XUnit, and
others) with the ASP.NET MVC Framework. VS 2008 Professional now includes built-in
testing project support for MSTest (previously in VS 2005 this required a Visual Studio
Team System SKU), and our default ASP.NET MVC project template automatically
creates one of these projects when you use VS 2008.
We'll also be shipping project template downloads for NUnit, MBUnit and other unit test
frameworks as well, so if you prefer to use those instead you'll also have an easy one
click way to create your application and have a test project immediately ready to use
with it.
£%
&
/Controllers
/Models
/Views
As you can probably guess, we recommend putting your Controller classes underneath
the /Controllers directory, your data model classes underneath your /Models directory,
and your view templates underneath your /Views directory.
While the ASP.NET MVC framework doesn't force you to always use this structure, the
default project templates use this pattern and we recommend it as an easy way to
structure your application. Unless you have a good reason to use an alternative file
layout, I'd recommend using this default pattern.
In most web frameworks (ASP, PHP, JSP, ASP.NET WebForms, etc), incoming URLs
typically map to template files stored on disk. For example, a "/Products.aspx" or
"/Products.php" URL typically has an underlying Products.aspx or Products.php
template file on disk that handles processing it. When a http request for a web
application comes into the web server, the web framework runs code specified by the
template file on disk, and this code then owns handling the processing of the request.
Often this code uses the HTML markup within the Products.aspx or Products.php file to
help with generating the response sent back to the client.
MVC frameworks typically map URLs to server code in a different way. Instead of
mapping URLs to template files on disk, they instead map URLs directly to classes.
These classes are called "Controllers" and they own processing incoming requests,
handling user input and interactions, and executing appropriate application and data
logic based on them. A Controller class will then typically call a separate "View"
component that owns generating the actual HTML output for the request.
The ASP.NET MVC Framework includes a very powerful URL mapping engine that
provides a lot of flexibility in how you map URLs to Controller classes. You can use it to
easily setup routing rules that ASP.NET will then use to evaluate incoming URLs and
pick a Controller to execute. You can also then have the routing engine automatically
parse out variables that you define within the URL and have ASP.NET automatically
pass these to your Controller as parameter arguments. I'll be covering more advanced
scenarios involving the URL routing engine in a future blog post in this series.
Now that we have a created a ProductsController class in our project we can start
adding logic to handle the processing of incoming "/Products/" URLs to the application.
When defining our e-commerce storefront use cases at the beginning of this blog post, I
said we were going to implement three scenarios on the site: 1) Browsing all of the
Product Categories, 2) Listing Products within a specific Category, and 3) Showing
Details about a Specific Product. We are going to use the following SEO-friendly URLs
to handle each of these scenarios:
£ p
p £
p
p p
pp
p
p
m p
pp
p
p
p
p
p p
p
p
p
p
p
p
There are a couple of ways we could write code within our ProductsController class to
process these three types of incoming URLs. One way would be to override the
"Execute" method on the Controller base class and write our own manual
if/else/switching logic to look at the incoming URL being requested and then execute the
appropriate logic to process it.
A much easier approach, though, is to use a built-in feature of the MVC framework that
enables us to define "action methods" on our controller, and then have the Controller
base class automatically invoke the appropriate action method to execute based on the
URL routing rules in use for our application.
For example, we could add the below three controller action methods to our
ProductsController class to handle our three e-commerce URL scenarios above:
The URL routing rules that are configured by default when a new project is created treat
the URL sub-path that follows the controller name as the action name of the request.
So if we receive a URL request of /Products/Categories, the routing rule will treat
"Categories" as the name of the action, and the Categories() method will be invoked to
process the request. If we receive a URL request of /Products/Detail/5, the routing rule
will treat "Detail" as the name of the action, and the Detail() method will be invoked to
process the request, etc.
!
"
!
! !!#
!!$
! !
!£
$$!$
!(&
!
$$ ! *!+% ,- ! !
The ASP.NET MVC framework also supports automatically mapping incoming URL
parameter values as parameter arguments to action methods. By default, if you have a
parameter argument on your action method, the MVC framework will look at the
incoming request data to see if there is a corresponding HTTP request value with the
same name. If there is, it will automatically pass it in as a parameter to your action
method.
For example, we could re-write our Detail action method to take advantage of this
support and make it cleaner like below:
I can use a similar approach for the List action so that we can pass in the category
name as part of the URL (for example: /Products/List/Beverages). In the interest of
making the code more readable, I made one tweak to the routing rules so that instead of
having the argument name be called "id" it will be called "category" for this action.
Below is a version of our ProductsController class that now has full URL routing and
parameter mapping support implemented:
Note above how the List action takes the category parameter as part of the URL, and
then an optional page index parameter as a querystring (we'll be implementing server-
side paging and using that value to indicate which page of category data to display with
the request).
Optional parameters in our MVC framework are handled using nullable type arguments
on Controller Action methods. Because the page parameter on our List action is a
nullable int (that is what "int?" means syntactically), the MVC framework will either pass
in a value if it is present in the URL - or pass in null if not. Check out my previous post
on the ?? null coalescing operator to learn a useful tip/trick on how to work with nullable
types that are passed as arguments like this.
We now have all of the data code/objects we need to finish implementing our
ProductsController functionality.
We'll then define a simple unit test that tests the Detail action of our ProductsController:
The ASP.NET MVC framework has been designed specifically to enable easy unit
testing. All core APIs and contracts within the framework are interfaces,
and extensibility points are provided to enable easy injection and customization of
objects (including the ability to use IOC containers like Windsor, StructureMap,
Spring.NET, and ObjectBuilder). Developers will be able to use built-in mock classes,
or use any .NET type-mocking framework to simulate their own test versions of MVC
related objects.
In the unit test above, you can see an example of how we are injecting a dummy
"ViewFactory" implementation on our ProductsController class before calling the Detail()
action method. By doing this we are overriding the default ViewFactory that would
otherwise handle creating and rendering our View. We can use this test ViewFactory
implementation to isolate the testing of just our ProductController's Detail action
behavior (and not have to invoke the actual View to-do this). Notice how we can then
use the three Assert statements after the Detail() action method is called to verify that
the correct behavior occurred within it (specifically that the action retrieved the correct
Product object and then passed it to the appropriate View).
Because we can mock and simulate any object in the MVC framework (including
IHttpRequest and IHttpResponse objects), you do not have to run unit tests in the
context of an actual web-server. Instead, we can create our ProductsController within a
normal class library and test it directly. This can significantly speed up the execution of
unit tests, as well as simplify the configuration and running of them.
If we use the Visual Studio 2008 IDE, we can also easily track the success/failure of our
test runs (this functionality is now built-into VS 2008 Professional):
I think you'll find that the ASP.NET MVC Framework makes writing tests easy, and
enables a nice TDD workflow.
%£)
We've finished implementing and testing the application + data logic for the product
browsing section of our e-commerce application. Now we need to implement the HTML
UI for it.
We'll do this by implementing "Views" that render the appropriate UI using the view-
related data objects that our ProductsController action method provided when
calling the RenderView() method:
Because we are going to be building many pages for our site, we'll start our UI work
by first defining a master page that we can use to encapsulate the common HTML
layout/chrome across the site. We'll do this in a file called "Site.Master" that we'll create
under the \Views\Shared directory of our project:
We can reference an external CSS stylesheet to encapsulate all styles for the site, and
then use the master page to define the overall layout of the site, as well as identify
content placeholder regions where we'll want pages to be able to fill in page specific
content. We can optionally use all of the cool new VS 2008 designer features when
doing this (including the HTML split-view designer, CSS Authoring, and Nested Master
Pages support).
£%*' "
By default when you create a new ASP.NET MVC Project using Visual Studio, it will
create a "Shared" sub-directory underneath the "Views" directory root. This is the
recommended place to store Master Pages, User Controls, and Views that we want to
share across multiple Controllers within the application.
When building views that are specific to an individual controller, the default ASP.NET
MVC convention is to store them in sub-directories under the \Views root. By default
the name of a sub-directory should correspond to the Controller name. For example,
because the Controller class we have been building is called "ProductsController", we
will by default store the Views specific to it within the \Views\Products sub-directory:
When we call the RenderView(string viewName) method within a specific Controller, the
MVC framework will automatically first look for a corresponding .aspx or .ascx view
template underneath the \Views\!
directory, and then if it can't find an
appropriate view template there it will check the \Views\Shared directory for one:
We can create the "Categories" View for our ProductsController within Visual Studio by
using the "Add New Item" menu option on the Products directory and selecting the
"MVC View Page" item template. This will create a new .aspx page that we can
optionally associate with our Site.Master master page to pick up the overall look and
feel of the site (and just like with master pages you get full WYSIWYG designer
support):
When building applications using an MVC pattern, you want to keep your View code as
simple as possible, and make sure that the View code is purely about rendering UI.
Application and data retrieval logic should only be written inside Controller classes.
Controller classes can then choose to pass on the necessary data objects needed to
render a view when they call their RenderView method. For example, below in the
Categories action method of our ProductsController class we are passing a List
collection of Category objects to the Categories view:
MVC View Pages by default derive from the System.Web.Mvc.ViewPage base class,
which provides a number of MVC specific helper methods and properties that we can
use in constructing our UI. One of these ViewPage properties is named "ViewData",
and it provides access to the view-specific data objects that the Controller passed as
arguments to the RenderView() method.
From within your View you can access the "ViewData" in either a late-bound or strongly-
typed way. If your View derives from ViewPage, the ViewData property will be typed as
a late-bound dictionary. If your View derives from the generics based ViewPage<T> -
where T indicates the data object type of the ViewData the Controller is passing to the
View - then the ViewData property will be strongly typed to match the same type that
your Controller passed in.
For example, my Categories View code-behind class below is deriving from
ViewPage<T> - where I am indicating that T is a List of Category objects:
This means that I get full type-safety, intellisense, and compile-time checking within my
View code when working against the ProductsController.Categories() supplied
List<Category> ViewData:
If you remember from the screenshots at the very beginning of this post, we want to
display a list of the product categories within our Categories view:
I can write this HTML UI generation code in one of two ways within my Categories View
implementation: 1) Using Inline Code within the .aspx file, or 2) Using Server Controls
within the .aspx file and Databinding from my Code Behind
ASP.NET Pages, User Controls and Master Pages today support <% %> and <%= %>
syntax to embed rendering code within html markup. We could use this technique
within our Categories View to easily write a foreach loop that generates a bulleted
HTML category list:
VS 2008 provides full code intellisense within the source editor for both VB and C#.
This means we'll get intellisense against our Category model objects passed to the
View:
VS 2008 also provides full debugger support for inline code as well (allowing us to set
breakpoints and dynamically inspect anything in the View with the debugger):
%c## -+£% .
ASP.NET Pages, User Controls and Master Pages also support the ability to use
declarative server controls to encapsulate HTML UI generation. Instead of using inline
code like above, we could use the new <asp:listview> control in .NET 3.5 to generate
our bulleted list UI instead:
Notice above how the ListView control encapsulates both rendering a list of values, as
well as handles the scenario where no items are in the list (the <EmptyDataTemplate>
saves us from having to write an if/else statement in the markup). We could then
databind our list of category objects to the listview control using code-behind code like
below:
¢ c
One of the things you might have noticed in both the inline-code and the server-control
versions of the View code snippets above are calls to an Html.ActionLink method:
The Html object is a helper property on the ViewPage base class, and the ActionLink
method is a helper on it that makes it easy to dynamically generate HTML hyperlinks
that link back to action methods on Controllers. If you look at the HTML output picture in
the section above, you can see some example HTML output generated by this method:
<a href="http://weblogs.asp.net/Products/List/Beverages">Beverages</a>
The signature of the Html.ActionLink helper method I am using looks like this:
string ActionLink(string text, object values);
The first argument represents the inner content of the hyperlink to render (for example:
<a>text goes here</a>). The second argument is an anonymous object that represents
a sequence of values to use to help generate the actual URL (you can think of this as a
cleaner way to generate dictionaries). I will go into more detail on how exactly this
works in a future blog post that covers the URL routing engine. The short summary,
though, is that you can use the URL routing system both to process incoming URLs, as
well as to generate URLs that you can emit in outgoing HTML. If we have a routing rule
like this:
/<controller>/<action>/<category>
And then write this code within a ProductController's Category View:
<%= Html.ActionLink("Click Me to See Beverages", new { action="List",
category="Beverages" } %>
The ActionLink method will use the URL mapping rules of your application to swap in
your parameters and generate this output:
<a href="http://weblogs.asp.net/Products/List/Beverages">Click Me to See
Beverages</a>
This makes it easy within your application to generate URLs and AJAX callbacks to your
Controllers. It also means you can update your URL routing rules in one place and
have the code throughout your application automatically pick up the changes for both
incoming URL processing and outgoing URL generation.
$ !
: To help enforce testability, the MVC framework today does not support
postback events directly to server controls within your Views. Instead, ASP.NET MVC
applications generate hyperlink and AJAX callbacks to Controller actions - and then use
Views (and any server controls within them) solely to render output. This helps ensure
that your View logic stays minimal and solely focused on rendering, and that you can
easily unit test your Controller classes and verify all Application and Data Logic behavior
independent of your Views. I'll blog more about this in the future.
"
This first blog post is a pretty long one, but hopefully helps provide a reasonably broad
look at how all the different components of the new ASP.NET MVC Framework fit
together, and how you can build a common real world scenario with it. The first public
preview of the ASP.NET MVC bits will be available in a few weeks, and you'll be able to
use them to do all of the steps I outlined above.
While many of the concepts inherent to MVC (in particular the idea of separation of
concerns) are probably new to a lot of people reading this, hopefully this blog post has
also show how the ASP.NET MVC implementation we've been working on fits pretty
cleanly into the existing ASP.NET, .NET, and Visual Studio feature-set. You can use
.ASPX, .ASCX and .MASTER files and ASP.NET AJAX to create your ASP.NET MVC
Views. Non-UI features in ASP.NET today like Forms Authentication, Windows
Authentication, Membership, Roles, Url Authorization, Caching, Session State, Profiles,
Health Monitoring, Configuration, Compilation, Localization,
and HttpModules/HttpHandlers all fully support the MVC model.
If you don't like the MVC model or don't find it natural to your style of development, you
definitely don't have to use it. It is a totally optional offering - and does notreplace the
existing WebForms Page Controller model. Both WebForms and MVC will be fully
supported and enhanced going forward. You can even build a single application and
have parts of it written using WebForms and parts written using an MVC approach if you
want.
If you do like what you've seen from the above MVC post (or are intrigued and want to
learn more), keep an eye on my blog over the weeks ahead. I'll be covering more MVC
concepts and use them to build out our e-commerce application to show more features
of it.
Hope this helps,
Scott
Last month I blogged the first in a series of posts I'm going to write that cover the new
ASP.NET MVC Framework we are working on. The first post in this series built a
simple e-commerce product listing/browsing scenario. It covered the high-level
concepts behind MVC, and demonstrated how to create a new ASP.NET MVC project
from scratch to implement and test this e-commerce product listing functionality.
In today's blog post I'm going to drill deeper into the routing architecture of the ASP.NET
MVC Framework, and discuss some of the cool ways you can use it for more advanced
scenarios in your application.
In Part 1 of this series, we created an e-commerce site that exposed three types of
URLs:
£ p
p £
p
p p
pp
p
p
m p
pp
p
p
p
p
p p
p
p
p
p
p
p
The ASP.NET MVC framework includes a flexible URL routing system that enables you
to define URL mapping rules within your applications. The routing system has two main
purposes:
1. Map incoming URLs to the application and route them so that the right Controller
and Action method executes to process them
2. Construct outgoing URLs that can be used to call back to Controllers/Actions (for
example: form posts, <a href=""> links, and AJAX calls)
Having the ability to use URL mapping rules to handle both incoming and outgoing URL
scenarios adds a lot of flexibility to application code. It means that if we want to later
change the URL structure of our application (for example: rename /Products to
/Catalog), we could do so by modifying one set of mapping rules at the application level
- and not require changing ! code within our Controllers or View templates.
By default when you use Visual Studio to create a new project using the "ASP.NET
MVC Web Application" template it adds an ASP.NET Application class to the project.
This is implemented within the Global.asax code-behind:
The second routing rule above is added to special-case the root "Default.aspx" URL in
our application (which is sometimes passed by web servers in place of "/" when
handling requests for the root URL of an application). This rule ensures that requests
for either the root "/Default.aspx" or "/" to our application are handled by the "Index()"
action on the "HomeController" class (which is a controller automatically added by
Visual Studio when we created a new application using the "ASP.NET MVC Web
Application" project template).
Or by taking advantage of the new object initializer feature in the VS 2008 C# and VB
compilers to set the properties more tersely:
The "Url" property on the Route class defines the Url matching rule that should be used
to evaluate if a route rule applies to a particular incoming request. It also defines how
the URL should be tokenized for parameters. Replaceable parameters within the URL
are defined using a [ParamName] syntax. As we'll see later, we aren't restricted to a
fixed set of "well known" parameter names - you can have any number of arbitrary
parameters you want within the URL. For example, I could use a Url rule of
"/Blogs/[Username]/Archive/[Year]/[Month]/[Day]/[Title]" to tokenize incoming URLs to
blog posts - and have the MVC framework automatically parse and pass UserName,
Year, Month, Day and Title parameters to my Controller's action method.
The "Defaults" property on the Route class defines a dictionary of default values to use
in the event that the incoming URL doesn't include one of the parameter values
specified. For example, in the above URL mapping examples we are defining two
default URL parameter values - one for "[action]" and one for "[id]". This means that if a
URL for /Products/ is received by the application, the routing system will by default use
"Index" as the name of the action on the ProductsController to execute. Likewise, if
/Products/List/ was specified, then a null string value would be used for the "ID"
parameter.
The "RouteHandler" property on the Route class defines the IRouteHandler instance
that should be used to process the request after the URL is tokenized and the
appropriate routing rule to use is determined. In the above examples we are indicating
that we want to use the System.Web.Mvc.MvcRounteHandler class to process the
URLs we have configured. The reason for this extra step is that we want to make sure
that the URL routing system can be used for both MVC and non-MVC requests. Having
this IRouteHandler interface means we will be able to cleanly use it for non-MVC
requests as well (such as standard WebForms, Astoria REST support, etc).
There is also a "Validation" property on the Route class that we'll look at a little later in
this post. This property allows us to specify pre-conditions that need to be met for a
particular routing rule to match. For example, we could indicate that a routing rule
should only apply for a specific HTTP verb (allowing us to easily map REST
commands), or we could use a regular expression on arguments to filter whether a
routing rule should match.
!
$%$
#
!'
,
!%
*!
-0
!
,$
#
!
,
!%
!
! %
#
$
! $
*
, $
%. -
! !
! !!!
When an incoming URL is received by an ASP.NET MVC Web Application, the MVC
framework evaluates the routing rules in the RouteTable.Routes collection to determine
the appropriate Controller to handle the request.
The MVC framework chooses the Controller to use by evaluating the RouteTable rules
in the order that they have been registered. The incoming URL is tested against each
Route rule to see if it matches - and if a Route rule matches then that rule (and its
associated RouteHandler) is the one that is used to process the request (and all
subsequent rules are ignored). This means that you want to typically structure your
routing Rules in a "most specific to least specific" order.
Let's walk through using some custom routing rules in a real world scenario. We'll do
this by implementing search functionality for our e-commerce site.
We'll start by adding a new SearchController class to our project:
We'll then define two Action methods within our SearchController class. The Index()
action method will be used to present a search page that has a TextBox for a user to
enter and submit a search term. The Results() action will be used to handle the form
submission from it, perform the search against our database, and then display the
results back to the user:
Using the default Ñ1!
2Ñ1 !2Ñ12 URL route mapping rule, we could "out of the
box" use URLs like below to invoke our SearchController actions:
p £ p
p
pp
p !p
p"p
"#$%
p "p
pp
"#$%&'()*p "p
Note that the reason the root /Search URL by default maps to the Index() action method
is because the /[controller]/[action]/[id] route definition added by default when Visual
Studio creates a new project sets "Index" as the default action on Controllers (via the
"Defaults" property):
p £ p
p
pp
p !p
p"p
p "p
pp
&'()*p "p
We could enable these "prettier" search result URLs by adding two custom URL Route
mapping rules %
the default /[controller]/[action]/[id] rule like below:
With the first two rules we are now explicitly specifying the Controller and Action
parameters for /Search/ URLs. We are indicating that "/Search" should always be
handled by the "Index" action on the SearchController. Any URL with a sub-URL
hierarchy (/Search/Foo, /Search/Bar, etc) is now always handled by the "Results" action
on the SearchController.
The second routing rule above indicates that anything beyond the /Search/ prefix should
be treated as a parameter called "[query]" that will then be passed as a method
parameter to our Results action method on SearchController:
Most likely we will also want to enable paginated search results (where we only show 10
search query results at a time). We could do this via a querystring argument (for
example: /Search/Beverages?page=2) or we could optionally embed the page index
number as part of the URL as well (for example: /Search/Beverages/2). To support this
later option all we'd need to-do is add an extra optional parameter to our 2nd routing
rule:
Notice above how the new URL rule match is now "Search/[query]/[page]". We've also
configured the default page index to be 1 in cases where it is not included in the URL
(this is done via the anonymous type passed as the "Defaults" property value).
We can then update our SearchController.Results action method to take this page
parameter as a method argument:
And with that we now have "pretty URL" searching for our site (all that remains is to
implement the search algorithm - which I will leave as an exercise to the reader <g>).
As I mentioned earlier in this post, the Route class has a "Validation" property that
allows you to add validation pre-condition rules that must be true (in addition to the URL
filter) for a route rule to match. The ASP.NET MVC Framework allows you to use
regular expressions to validate each parameter argument in the URL, as well as allows
you to evaluate HTTP headers (to route URLs differently based on HTTP verbs).
Below is a custom validation rule we could enable for URLs like "/Products/Detail/43". It
specifies that the ID argument must be a number (no strings allowed), and that it must
be between 1 and 8 characters long:
If we pass in a URL like /Products/Detail/12 to our application, the above routing rule will
be valid. If we pass in /Products/Detail/abc or /Products/Detail/23232323232 it will not
match.
Earlier in this blog post I said that the URL routing system in the ASP.NET MVC
Framework was responsible for two things:
The URL routing system has a number of helper methods and classes that make it easy
to dynamically look up and construct URLs at runtime (you can also lookup URLs by
working with the RouteTable's Route's collection directly).
Html.ActionLink
In Part 1 of this blog series I briefly discussed the Html.ActionLink() view helper
method. It can be used within views and allows you to dynamically generate <a
href=""> hyperlinks. What is cool is that it generates these URLs using the URL
mapping rules defined in the MVC Routing System. For example, the two
Html.ActionLink calls below:
automatically pick up the special Search results route rule we configured earlier in this
post, and the "href" attribute they generate automatically reflect this:
In particular note above how the second call to Html.ActionLink automatically mapped
the "page" parameter as part of the URL (and note how the first call omitted the page
parameter value - since it knew a default value would be provided on the server side).
Url.Action
In addition to using Html.ActionLink, ASP.NET MVC also has a Url.Action() view helper
method. This generates raw string URLs - which you can then use however you want.
For example, the code snippet below:
would use the URL routing system to return the below raw URL (not wrapped in a <a
href=""> element):
Controller.RedirectToAction
ASP.NET MVC also supports a Controller.RedirectToAction() helper method that you
can use within controllers to perform redirects (where the URLs are computed using the
URL routing system).
For example when the below code is invoked within a controller:
The previous URL helper examples took advantage of the new anonymous type support
that VB and C# now support with VS 2008. In the examples above we are using
anonymous types to effectively pass a sequence of name/value pairs to use to help
map the URLs (you can think of this as a cleaner way to generate dictionaries).
In addition to passing parameters in a dynamic way using anonymous types, the
ASP.NET MVC framework also supports the ability to create action routes using a
strongly-typed mechanism that provides compile-time checking and intellisense for the
URL helpers. It does this using Generic types and the new VB and C# support for
Lambda Expressions.
For example, the below anonymous type ActionLink call:
In addition to being slightly terser to write, this second option has the benefit of being
type-safe, which means that you get compile-time checking of the expression as well as
Visual Studio code intellisense (you can also use refactoring tools with it):
Notice above how we can use intellisense to pick the Action method on the
SearchController we want to use - and how the parameters are strongly-typed. The
generated URLs are all driven off of the ASP.NET MVC URL Routing system.
You might be wondering - how the heck does this work? If you remember eight months
ago when I blogged about Lambda Expressions, I talked about how Lambda
expressions could be compiled both as a code delegate, as well as to an expression
tree object which can be used at runtime to analyze the Lambda expression. With the
Html.ActionLink<T> helper method we using this expression tree option and are
analyzing the lambda at runtime to look up the action method it invokes as well as the
parameters types, names and values that are being specified in the expression. We
can use these with the MVC Url Routing system to return the appropriate URL and
associated HTML.
Important: When using this Lambda Expression approach we never actually execute the
Controller action. For example, the below code
! invoke the "Results" action
method on our SearchController:
Instead it just returns this HTML hyperlink:
When this hyperlink is clicked by an end-user it will then send back a http request to the
server that will invoke the SearchController's Results action method.
£%
One of the core design principles of the ASP.NET MVC Framework is enabling great
testing support. Like the rest of the MVC framework, you can easily unit test routes and
route matching rules. The MVC Routing system can be instantiated and run
independent of ASP.NET - which means you can load and unit test route patterns within
any unit test library (no need to start up a web-server) and using any unit test framework
(NUnit, MBUnit, MSTest, etc).
Although you can unit test an ASP.NET MVC Application's global RouteTable mapping
collection directly within your unit tests, in general it is usually a bad idea to have unit
tests ever change or rely on global state. A better pattern that you can use is to
structure your route registration logic into a RegisterRoutes() helper method like below
that works against a RouteCollection that is passed in as an argument (note: we will
probably make this the default VS template pattern with the next preview update):
You can then write unit tests that create their own RouteCollection instance and call the
Application's RegisterRoutes() helper to register the application's route rules within it.
You can then simulate requests to the application and verify that the correct controller
and actions are registered for them - without having to worry about any side-effects:
"
Hopefully this post provides some more details about how the ASP.NET MVC Routing
architecture works, and how you can use it to customize the structure and layout of the
URLs you publish within your ASP.NET MVC applications.
By default when you create a new ASP.NET MVC Web Application it will pre-define a
default /[controller]/[action]/[id] routing rule that you can use (without having to manually
configure or enable anything yourself). This should enable you to build many
applications without ever having to register your own custom routing rules. But
hopefully the above post has demonstrated that if you do want to custom structure your
own URL formats it isn't hard to-do - and that the MVC framework provides a lot of
power and flexibility in doing this.
Hope this helps,
Scott
p