Sunteți pe pagina 1din 50

c 


 
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.

      

 

MVC is a framework methodology that divides


an application's implementation into three component roles:
models, views, and controllers.


"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.

c  #$      c## 

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.

 %cc  c## 

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.

£  %
       & 

The default directory structure of an ASP.NET MVC Application has 3 top-level


directories:


/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.

' c  £  %     


By default ASP.NET MVC projects have a preconfigured set of URL routing rules that
enable you to easily get started on an application without needing to explicitly configure
anything. Instead you can start coding using a default set of name-based URL mapping
conventions that are declared within the ASP.NET Application class of the Global.asax
file created by the new ASP.NET MVC project template in Visual Studio.
The default naming convention is to map the leading URL path of an incoming
HTTP request (for example: /Products/) to a class whose name follows the
pattern£ Controller (for example: by default a URL leading with /Products/ would
map to a class named Controller).
To build our e-commerce product browsing functionality, we'll add a new
"ProductsController" class to our project (you can use the "Add New Item" menu in
Visual Studio to easily create a Controller class from a template):

Our ProductsController class will derive from the System.Web.MVC.Controller base


class. Deriving from this base class isn't required - but it contains some useful helper
methods and functionality that we'll want to take advantage of later:
Once we define this ProductsController class within our project, the ASP.NET MVC
framework will by default use it to process all incoming application URLs that start under
the "/Products/" URL namespace. This means it will be automatically called to process
the "/Products/Categories", "/Products/List/Beverages", and "/Products/Detail/3" URLs
that we are going to want to enable within our store-front application.
In a future blog post we'll also add a ShoppingCartController (to enable end users to
manage their shopping carts) and an AccountController (to enable end users to create
new membership accounts on the site and login/logout of it). Once we add these two
new controller classes to our project, URLs that start with /ShoppingCart/ and /Account/
will automatically be routed to them for processing.


  
 
!
" 
   
 !  ! 
!#
! !$ 
!
!
! $$   !
 %
  %
 



   $$ ! 
 ! 
        

 $$   ! 
!



!
  &
 ! 
   !'
 
( ! )
 
  

!£ 
 $$ ! $ 
!(&  !
 $$   ! * !+%   ,- !
 !
 '#
.  ! 
% $*
!' 



!  
£  ! 
! !

! %
-

£  %   c 

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.


  
 
!
" 
   
   !
!  ! !#
! !$ 
! !
  

!£  $$ ! $ 
!(& 
!
 $$   ! * !+%   ,- ! !
 

 ##%£      c 


There are several ways to access URL parameter values within the action methods of
Controller classes.
The Controller base class exposes a set of Request and Response objects that can be
used. These objects have the exact same API structure as the
HttpRequest/HttpResponse objects that you are already familiar with in ASP.NET. The
one important difference is that these objects are now interface based instead of sealed
classes (specifically: the MVC framework ships with new System.Web.IHttpRequest and
System.Web.IHttpResponse interfaces). The benefit of having these be interfaces is
that it is now easy to mock them - which enables easy unit testing of controller classes.
I'll cover this in more depth in a future blog post.
Below is an example of how we could use the Request API to manually retrieve an ID
querystring value from within our Detail action method in the ProductsController class:

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:

In addition to mapping argument values from the querystring/form collection of a


request, the ASP.NET MVC framework also allows you to use the MVC URL route
mapping infrastructure to embed parameter values within the core URL itself (for
example: instead of /Products/Detail?id=3 you could instead use /Products/Detail/3).
The default route mapping rule declared when you create a new MVC project is one
with the format: "/[controller]/[action]/[id]". What this means is that if there is any URL
sub-path after the controller and action names in the URL, it will by default be treated as
a parameter named "id" - and which can be automatically passed into our controller
action method as a method argument.
This means that we can now use our Detail method to also handle taking the ID
argument from the URL path (e.g: /Products/Detail/3):

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 a ProductsController class and three action methods on it ready to
process incoming web requests. Our next step will be to build some classes that will
help us work with our database to retrieve the appropriate data needed to handle these
web requests.
In an MVC world "models" are the components of an application that are responsible for
maintaining state. With web applications this state is typically persisted inside a
database (for example: we might have a Product object that is used to represent
product data from the Products table inside our SQL database).
The ASP.NET MVC Framework enables you to use any data access pattern or
framework you want in order to retrieve and manage your models. If you want to use
ADO.NET DataSets/DataReaders (or abstractions built on top of them) you can. If you
prefer to use an object relational mapper (ORM) like NHibernate, LLBLGen,
WilsonORMapper, LINQ to SQL/LINQ to Entities you can absolutely use those as well.
For our e-commerce sample application I'm going to use the built-in LINQ to SQL ORM
shipped in .NET 3.5 and VS 2008. You can learn more about LINQ to SQL from my
ongoing blog tutorial series that covers it (in particular make sure to check out
my Part1, Part2, Part3 and Part4 posts).
I'll start by right-clicking on the "Models" sub-directory of our MVC web project inside
VS and choose the "Add New Item" option to add a LINQ to SQL model. Within the
LINQ to SQL ORM designer I'll define three data model classes that map to the
Categories, Products, and Suppliers table inside the SQL Server Northwind
sample database (read Part 2 of my LINQ to SQL series to learn how to-do this):
Once we've defined our LINQ to SQL data model classes, I'll then add a new
NorthwindDataContext partial class to our Models directory as well:
Within this class I'll define a few helper methods that encapsulate some LINQ
expressions that we can use to retrieve the unique Category objects from our database,
retrieve all Product objects within a specific category in our database, as well as retrieve
an individual Product object based on a supplied ProductID:
These helper methods will make it easy for us to cleanly retrieve the data model objects
needed from our ProductsController class (without having to write the LINQ expressions
within the Controller class itself):

We now have all of the data code/objects we need to finish implementing our
ProductsController functionality.

%) #          

Controllers in a MVC based application are responsible for processing incoming


requests, handling user input and interactions, and executing appropriate application
logic based on them (retrieving and updating model data stored in a database, etc).
Controllers typically do not generate the specific HTML response for a request. The
task of generating an HTML response is instead owned by "View" components within
the application - which are implemented as separate classes/templates from
Controllers. Views are intended to be focused entirely on encapsulating presentation
logic, and should not contain any application logic or database retrieval code (instead all
app logic should be handled by the Controller).
In a typical MVC web workflow, Controller action methods will handle the incoming web
request, use the incoming parameter values to execute appropriate application logic
code, retrieve or update data model objects from a database, and then select a "View"
to use to render an appropriate UI response back to a browser. As part of picking the
appropriate View to render, the Controller will explicitly pass in (as arguments) all of the
data and variables required by the "View" in order to for it to render the appropriate
response:
You might be wondering - what is the benefit of separating the Controller and the View
like this? Why not just put them in the same class? The primary motivation in
partitioning the application like this is to help enforce the separation of your
application/data logic from your UI generation code. This makes it much easier to unit
test your application/data logic in isolation from your UI rendering logic. It can also help
make your application more maintainable over time - since it makes it harder for you to
accidentally add application/data logic in your view templates.
When implementing the three controller action methods of our ProductsController class,
we'll use the incoming URL parameter values to retrieve the appropriate model objects
from our database, and then pick a "View" component to use to render an appropriate
HTML response. We'll use one of the RenderView() methods on the Controller base
class to specify the View we want to use, as well as explicitly pass in the specific data
that we want the View to use to render its response.
Below is the final result of our ProductsController implementation:
Notice that the number of lines of code in our action methods above is pretty small (two
lines each). This is partly because the URL parameter parsing logic is handled entirely
for us by the MVC framework (saving us from having to write a lot of this code). This is
also partly because the product browsing scenario is fairly simple from a business logic
perspective (the action methods are all read-only display scenarios).
In general, though, you'll often find that you'll have what are sometimes called "skinny
controllers" - meaning controller methods full of relatively terse action methods (less
than 10 lines of code). This is often a good sign that you have cleanly encapsulated
your data logic and factored your controller logic well.

£%      


You might be surprised that the next step we are going to work on is to test our
application logic and functionality. You might ask - how is that even possible? We
haven't implemented our Views, and our application currently doesn't render a single
tag of HTML. Well, part of what makes an MVC approach attractive is that we can unit
test the Controller and Model logic of applications completely independently of the
View/Html generation logic. As you'll see below we can even unit test these before we
create our Views.
To unit test the ProductsController class that we've been working on, we'll add a
ProductsControllerTest class into the Test Project that was added to our solution by
default when we created our ASP.NET MVC Application using Visual Studio:

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:

In the code example above the RenderView method's "Categories" parameter is


indicating the name of the view we want to render, and the second parameter is a list of
category objects that we want to pass to the view and have it use as data to generate
the appropriate HTML UI for.
The ASP.NET MVC Framework supports the ability to use any templating engine to
help with generating UI (including existing templating engines like NVelocity, Brail - as
well as new ones you write yourself). By default the ASP.NET MVC Framework uses
the existing ASP.NET Page (.aspx), Master Page (.master), and UserControl (.ascx)
support already within ASP.NET.
We'll be using the built-in ASP.NET view engine to implement our E-commerce
Application UI.

'%    




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

 %c##  ,+£%) 

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:

$ !! 


! !$
!
 !   !
'
.%
 !
 * !! ! $$   !   - 
 %#

! 
 #

  !
! $

  
 ! 
%&

 

!/ !
!
  
!
   

#


  


  % 
.!


This ListView server-control version of our View template will then generate the exact
same HTML as our in-line code version. Because we don't have a <form
runat="server"> control on the page, no viewstate, ID values or other markup will be
emitted. Only pure CSS friendly HTML:

¢ 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

We handled these URLs by creating a "ProductsController" class like below:


Once the above class was added to our application, the ASP.NET MVC Framework
automatically handled routing the incoming URLs to the appropriate action method on
our controller to process.
In today's blog post we are going to drill into exactly how this URL mapping happened,
as well as explore more advanced routing scenarios we can take advantage of with the
ASP.NET MVC Framework. I'll also demonstrate how you can easily unit test URL
routing scenarios.

 c  £  % " 

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.

' c  £  % 

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 ASP.NET Application class enables developers to handle application


startup/shutdown and global error handling logic.
The default ASP.NET MVC Project Template automatically adds an Application_Start
method to the class and registers two URL Routing rules with it:
The first routing rule above indicates that the ASP.NET MVC framework should by
default map URLs to Controllers using a "[controller]/[action]/[id]" format when
determining which Controller class to instantiate, and which Action method to invoke
(along with which parameters should be passed in).
This default routing rule is why a URL request for /Products/Detail/3 in our e-commerce
browsing sample from Part 1 automatically invokes the Detail method on our
ProductsController class and passes in 3 as the id method argument value:

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).

£  % )  


Routing rules are registered by adding Route instances into the
System.Web.Mvc.RouteTable's Routes collection.
The Route class defines a number of properties that you can use to configure the
mapping rules. You can set these properties using either "traditional" .NET 2.0 property
assignments:

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Ñ1 2 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):

While URLs like Ñ


Ñ
3"
45
#

 are perfectly functional, we might
decide we want slightly "prettier" URLs for our search results. Specifically we might
want to get rid of the "Results" action name from the URL, and pass in the search query
as part of the URL instead of using a QueryString argument. For example:

‘
 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:

1. Mapping incoming URLs to Controllers/Actions to handle


2. Helping construct outgoing URLs that can be used to later call back to
Controllers/Actions (for example: form posts, <a href=""> links, and AJAX calls)

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:

It internally generates a call to Response.Redirect("/Search/Beverages")


DRY
The beauty of all of the above helper methods is that they enable us to avoid having to
hard-code in URL paths within our Controller and View Logic. If at a later point we
decide to change the search URL route mapping rule back from "/Search/[query]/[page]"
to "/Search/Results/[query]/[page]" or /Search/Results?query=[query]&page=[page]" we
can easily do so by editing it in one place (our route registration code). We don't need
to change any code within our views or controllers to pick up the new URL (this
maintaining the "DRY principle").

  %(%%£    % " % ! 


/# 

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:

Can also be written as:

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

S-ar putea să vă placă și