Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET MVC - Part 1

0.00/5 (No votes)
7 Apr 2008 1  
A look at the ASP.NET MVC application in ASP.NET Extensions 3.5.

Introduction

There are many ways to create ASP.NET web applications; however, one common problem with them all is the difficulty in separating business logic from presentation. Separating these layers allows for more modular development, code reuse, and ease of testing. A pattern that supports these features is the Model-View-Controller, or MVC. The ASP.NET team has recognized the benefits of this pattern, and is working to incorporate it into ASP.NET. This article will focus on building an ASP.NET MVC web application from the Preview 2 release of ASP.NET 3.5 Extensions.

Prerequisites

Model-View-Controller Pattern

The first thing that must be discussed, of course, is what the MVC pattern is. MVC is a pattern that divides an application into separate areas of responsibility: Model, View, and Controller.

  • Model: Models are responsible for maintaining the state of the application, often by using a database.
  • View: These components are strictly for displaying data; they provide no functionality beyond formatting it for display.
  • Controller: Controllers are the central communication mechanism in an MVC application. They communicate actions from the views to the model and back.

One of the main points with the MVC pattern is that there is no direct communication between the model and view. This allows for reuse of model and controller code in different types of applications. The logic for a Web application could easily be applied to a Windows application by changing the view components. The controller components could also be exposed as Web Services for SOA without affecting the view. Of course, the model could also be changed without affecting the controller; for instance, if the database itself or the scheme were changed. Another benefit to separating the functions is to allow for better testing. Since the view is only concerned with display, all of the logic that needs tested is in the controller. Unit tests can easily be incorporated to test these functions.

ASP.NET Extensions

ASP.NET 3.5 Extensions Preview is a set of enhancements to ASP.NET and ADO.NET that are expected to be included in future releases but are available now as previews. These enhancements include ASP.NET Silverlight controls, ADO.NET Entity Framework, ASP.NET Dynamic Data, and ASP.NET MVC. The last is, of course, what we will be focusing on here. For more information about the others, please see the references at the end of this article.

All of the functionality for ASP.NET MVC projects is provided in three assemblies:

  • System.Web.Mvc
  • System.Web.Routing
  • System.Web.Abstractions

Creating an ASP.NET MVC project

After installing the extensions, you should see a new project type under File -> New Project -> Visual C# -> Web. This project is only available in C#. There are no plans at this time to support VB.

mvc1.png

The first thing this project wizard asks is if you want to create a unit test project. As stated earlier, one of the benefits of the MVC pattern is the separation of logic for easy testing. Although unit testing will be covered in a future article, we'll create the test project along with the main Web Application project.

mvc2.png

As we can see below, the project creation wizard will create the basic shell of the Web Application, including a Master page and two views, About and Index, and the HomeController. The test project has also been created and added to the solution, with unit test for the HomeController.

mvc3.png

Music Catalog Application

We will use this basic shell to build a simple demo app that displays artists, albums associated with the artist, and the songs associated with the album. Later in this series, we will also create forms for editing and inserting data. The database script provided with this article includes all the data we will be working with.

Applying the MVC Pattern

The Model

Since the application isn't very useful without data, we will start by creating a model for the MusicCatalog. To simplify development, we will be using LINQ to SQL.

Add New Item -> Data -> LINQ to SQL Classes. We'll name the class Music.

mvc4.png

After clicking the Add button, a designer will be displayed with two panels. The right panel allows you to drag Stored Procedures from a database onto this surface to create methods in the generated class. We'll skip this for the time being and concentrate on the left panel. This panel allows you to drag database tables from the Server Explorer onto the surface and create classes, with access methods from them. For this demo, open the Server Explorer and navigate to the MusicCatalog database. Drag the three tables, Artist, Album, and Song onto the designer surface.

A Brief Look at LINQToSQL Classes

As you can see, after the tables have been dragged to the design surface, the foreign key relationships established in the database are also represented in the designer and generated classes. If we open the Music.designer.cs file, we can see the classes that have been created.

[System.Data.Linq.Mapping.DatabaseAttribute(Name="MusicCatalog")]
public partial class MusicDataContext : System.Data.Linq.DataContext 

Notice here that DataContext has automatically been appended to the name we provided, Music. It is the convention to name LINQ to SQL classes this way. We can also see that LINQ to SQL uses the DatabaseAttribute to identify the database this class represents.

Partial Methods

The generated class also contains definitions for methods that have been declared as Partial.

#region Extensibility Method Definitions
partial void OnCreated(); 
partial void InsertArtist(Artist instance); 
partial void UpdateArtist(Artist instance); 
partial void DeleteArtist(Artist instance); 
partial void InsertAlbum(Album instance); 
partial void UpdateAlbum(Album instance); 
partial void DeleteAlbum(Album instance); 
partial void InsertSong(Song instance); 
partial void UpdateSong(Song instance); 
partial void DeleteSong(Song instance); 
#endregion

This is a new feature added in .NET 3.0 that is similar to the partial class that was introduced in .NET 2.0. Partial classes allow code to be separated into multiple classes and complied into a single unit. An example is the separation of ASP.NET code-behind files. Partial methods allow a designer to implement, define, and stub in methods that may be implemented by developers using the class. One difference between partial methods and virtual methods is that if the method is not implemented, the compiler just ignores it, as can be seen below.

mvc5.png

However, when the method is implemented in a partial class, it is used and compiled into the assembly.

mvc6.png

A usage for this technique is for simple lightweight messaging capability. It is also useful for providing hooks into your code that other developers can use to provide additional functionality, such as auditing, without the need to drive additional classes.

Adding accessor methods

The DataContext class isn't very useful as is, since it doesn't really provide methods to access the data we need, such as getting a list of the albums associated with a given artist. So we'll add some methods now. These could be provided by Stored Procedures in the database and dragged to the design surface to be automatically generated. However, by implementing them ourselves, we can look at some LINQ methods and techniques.

public Artist GetArtistById(int id)
{ 
   return Artists.Single(a => a.id == id); 
}
public List<Album> GetAlbumsForArtist(int id) 
{ 
   return Albums.Where(a => a.artist_id == id).ToList(); 
}

These two methods are only a sample; see the MusicDataContext class for the full implementation. They show using the Single and Where LINQ extension methods to return an artist matching the given ID and a list of albums for the given artist ID. Since this article isn't about LINQ, we'll move on and leave the details to other articles.

The Controller

All of the controllers in this application are located under the appropriately named Controllers folder. In an ASP.NET MVC application, users don't make requests for pages or resources, instead they request actions. Each public method in a controller is an action that can be requested.

The default implementation provided by the project template includes one controller, HomeController, with two actions, Index and About. When creating a new controller class, it must have the suffix Controller. MVC uses Reflection to locate controllers based on the names, as we will see shortly, with this suffix. The reason for this requirement is unknown.

public class HomeController : Controller
{ 
   public void Index() 
   { 
      RenderView("Index"); 
   } 
   public void About() 
   { 
      RenderView("About"); 
   } 
}

We'll cover the RenderView method shortly, so for now, just ignore it.

MusicController

To create the controller for the MusicCatalog application, right click on the Controllers folder in the MVC project and select Add -> New Item or Class and select MVC Controller Class in the Add New Item dialog. Notice the description stating that the class must use the Controller suffix as mentioned earlier.

mvc7.png

The class that is created is derived from System.Web.Mvc.Controller and already contains a method, Index. This is the default action for any controller.

public class MusicController : Controller
{ 
   public void Index() 
   { 
      // Add action logic here 
   } 
}

We will add an instance of the previously created Model, MusicDataContext, and methods for the actions necessary to retrieve and display artists, albums, and songs.

public class MusicController : Controller
{ 
   private MusicDataContext m_Music = new MusicDataContext(); 
   public void Index() 
   { 
      // Add action logic here 
   } 
   public void Artists(string letter) 
   { 
      RenderView("Artists", DataContext.GetArtists(letter)); 
   } 
   public void Albums(int id) 
   { 
      RenderView("Albums", DataContext.GetAlbumsForArtist(id)); 
   } 
   public void Songs(int id) 
   { 
      RenderView("Songs", DataContext.GetSongsForAlbum(id)); 
   } 
  
   #region Properties 
   private MusicDataContext DataContext 
   { 
      get { return m_Music; } 
   } 
   #endregion 
}

Rendering the View

The controller initiates the process of having a page displayed to the user by calling RenderView, which has a few overloads. In the overload version we are using, the first parameter is the name of the view page to be rendered. Notice we do not need to specify a full path, only the name. The MVC framework will look for this page in a folder matching the name of the controller under the views folder. The second parameter to the RenderView method is an object that will be passed to the view. This object is assigned to the ViewData member, which is an implementation of IDictionary<string, object>. You can assign ViewData directly and use an alternate override of RenderView, as below.

public void Artists(string letter)
{ 
   ViewData["Artists"] = DataContext.GetArtists(letter); 
   RenderView("Artists");
}

Of course, since ViewData is an IDictionary<string, object>, we can assign multiple values to be passed to the View.

public void Index()
{ 
   ViewData["TheAnswer"] = 42; 
   ViewData["Data"] = DataContext.GetArtists("A"); 
   RenderView("Artists"); 
   //RenderView("Artists", DataContext.GetArtists("A")); 
}

If the second RenderView method, commented out above, is used, it will overwrite any previous assignments of ViewData with the List<Artists> returned from DataContext.GetArtists("A").

The View

Now that we have the Model and Controller, it's time to move on to the Views. We'll start by adding the Artist view to display all of the music artists in the database.

First, add a new folder under Views called Music. The name of this folder matches the name of the controller it relates to. Notice the Home folder with views matching the actions in the HomeController that was created by the project template. Next, right click the folder you just created and select Add -> New Item.

mvc8.png

As you can see, there are four items that relate to MVC projects; Master Page and User Control should be obvious. The difference between MVC View Page and MVC View Content Page is the latter is for use with a MasterPage, while the former is a stand-alone page. We will select MVC View Content Page and name it Artists.aspx. Select Site.Master under the Shared folder when asked for a MasterPage to use.

One thing to keep in mind with MVC View pages is that they are not ASP.NET pages. Although they have the aspx extension for convenience, they have no form, as the below MVC View Page sample shows. All interactions are processed via controllers, not by forms, as in a traditional ASP.NET application. Though they are not ASP.NET forms, server controls can still be used, as we will see shortly.

<html xmlns="http://www.w3.org/1999/xhtml" > 
<head runat="server"> 
   <title></title> 
</head> 
<body> 
   <div> 
   </div> 
</body> 
</html>

ViewPage Code-behind

Opening up Music.aspx.cs, you can see the class is a Partial class, just like an ASP.NET page, but derives from ViewPage rather than Page.

public partial class Artists : ViewPage

Within this class, you can use the same methods and events you would in a normal ASP.NET page, such as the Load or DataBind events. In our case, we'll override the Load event.

protected override void OnLoad(EventArgs e) 
{ 
   AddMenu(); 
   ArtistList.DataSource = (List<Artist>)ViewData["Artists"];    
   ArtistList.DataBind();
}

We start by adding a simple alphabetic menu to the top of the page for navigation; more on that in a moment. Next, we bind the ViewData, which was passed from the controller, to the ListView control. Remember that the ViewData was also a member of the controller class. Keeping the same name between the classes is a convenience. Since ViewData is an IDictionary<string, object>, we need to cast it from an object to a generic List<Artists> before using it. This loose coupling is good, but what if we needed more strongly typed data? ViewPage also has a generic constructor that allows you to specify the type ViewData will be, making any casting, and the inherent performance penalties from possible boxing/unboxing operations, unnecessary. We also gain Intellisence support and compile time error checking.

public partial class Artists : ViewPage< List<Artist> >

Now ViewData can be used as a List rather than as a generic object, as in the first image.

mvc9.png

mvc10.png

Creating a Menu

To be somewhat useful, this application needs a way to list the artists in the database so users can select them. A simple alphabetic menu should serve our needs.

mvc11.png

We construct the menu by simply iterating from A to Z, creating an HTML link for each letter and adding it to a container. Nothing special here, except for creating the link.

private void AddMenu()
{ 
   // Build alphabetic menu 
   for(char c = 'A'; c <= 'Z'; c++) 
   { 
      string link = Html.ActionLink(c.ToString(), "Artists", 
         new RouteValueDictionary( new { controller = "Music", 
            letter = c.ToString() }) ); 

      
      Alphabet.Controls.Add(new LiteralControl(link)); 
      // Add seperator 
      if(c != 'Z') 
         Alphabet.Controls.Add(new LiteralControl(" | ")); 
   } 
}

ViewPage has an HTML property that exposes an HtmlHelper class. This class, as the name implies, provides helper methods for creating HTML links: ActionLink and RouteLink. Remember that in an MVC application, everything is routed through controllers that take action as necessary, hence the method ActionLink. We don't create HTML links that point to a URI, but rather links that tell what action to request from a controller.

In the above example, the first parameter is the text that will appear on the link. The next parameter is the name of the action to request. The third parameter, as we can see, is a RouteValueDictionary object that uses object initialization to assign a value to the controller and letter properties. This generates an HTML anchor tag that matches the route format, {controller}/{action}/{letter}, and looks like this: <a href="/Music/Artists/A">A</a>.

URL Routing

Now that we have the basic architecture of the application laid out, and, hopefully, an understanding of the major pieces, the question is, How does the application know how and when to display a particular page? The answer is URL Routing.

A route is the path, or URL in traditional web applications, to an action in a controller. An ASP.NET MVC application adds a RouteTable object to the application scope for this purpose. RouteTable, defined in the System.Web.Routing namespace, contains a single property, Routes, which is a RouteCollection.

The ASP.NET MVC project template adds this implementation to the Gloabal.asax.cs file.

public class GlobalApplication : System.Web.HttpApplication
{ 
   public static void RegisterRoutes(RouteCollection routes) 
   { 
      routes.Add(new Route("{controller}/{action}/{id}", new MvcRouteHandler())
      { 
         Defaults = new RouteValueDictionary(new { action = "Index", id = "" }), 
      }); 

      routes.Add(new Route("Default.aspx", new MvcRouteHandler()) 
      { 
         Defaults = new RouteValueDictionary(new { controller = "Home", 
                        action = "Index", id = "" }), 
      }); 
   } 
   protected void Application_Start(object sender, EventArgs e) 
   { 
      RegisterRoutes(RouteTable.Routes); 
   } 
}

URL Routing uses pattern matching to direct the request to the appropriate controller and action, and routes are evaluated in the order in which they have been registered. Just like exception handling, you should register routes from most specific to general.

The first route added to the collection is for requests using the format, {controller}/{action}/{id}, such as, Home/About/1. The second route is a default catch all. Typically in a web application, a request with no resource, only the application name, such as, http://www.mymvcapp, will be routed to a default page, usually default.aspx for ASP.NET applications.

In both routes, you can see that a Defaults property is also being assigned. This property is of type RouteValueDictionary, which is an IDictionary<string, object>, and as you can guess by the name, stores a collection of defaults to be used for the route. In the second route, since no value has been provided for a controller or action, the values specified for each in the Defaults property are used, Home and Index, respectively, in this case. The first route registered above will only match if a controller has been specified in the request, but if an action or ID has not been included, the defaults will be used for those values.

To be Continued...

ASP.NET MVC is a very rich and detailed technology that can't be covered in a single article. Hopefully this article has illustrated the basic concepts and can be used to evaluate the great potential of this technology. Future articles in this series will cover a more in depth look at URL routing, posting data to a database, or processing input from a page, and unit testing.

References

Caution: This series of articles is based on an earlier release, many things have changed with the preview 2 release.

Partial methods

History

  • Part 1 posted: 3/23/08.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here