http://www.developer.com/

Back to article

ASP.NET MVC 101


December 3, 2008

During the years that classic ASP and ASP.NET have existed, developers have learned to build applications based on web forms, session state, and server-side controls. But, the ability to dynamically route web server calls, create applications that separate logic from the user interface, and be easy to unit test are harder to build.

For these reasons, Microsoft is preparing an alternative to "traditional" ASP.NET development. This new technique is called ASP.NET MVC, short for the well-known model-view-controller pattern. Currently, ASP.NET MVC is in the public beta stage, with Beta 1 from October 2008 being the latest available version.

When designing ASP.NET MVC, Microsoft has focused on four main areas:

  • Clear separation of concerns
  • Support for Test-Driven Development (TDD)
  • Fine-grained control over HTML and JavaScript
  • Intuitive URLs

Before showing you ASP.NET MVC, let me provide a word of caution. The code listings in this article are based on Beta 1. So, they might not directly work once the final version comes out.

Separating Logic from the Representation

The MVC pattern was designed to help developers write applications that have the user interface (the representation, or the view) separated from the logic of the application. In the MVC pattern's case, the logic lives in the controller, which in turn can access data stored in the model. The model is a representation of a data object, such as a class holding information about a customer. Commonly, the model's data is read from a database.

Although this can sound complex, the pattern has many advantages. The only real drawbacks are increased complexity and (initial) development time. On the other hand, advantages include the possibility to make quick changes later; for example, to convert a GUI application into a web application. If properly done, only the view needs to change. Put another way, the parts of the application are loosely coupled, which is a design goal in many modern applications.

Other advantages include the ability to better unit test parts of the application, because for example the controller and model can be separately tested. Finally, the MVC pattern supports code re-use, because the same controller and model code often can be used almost without modifications in new projects.

When it comes to ASP.NET MVC, the MVC separation is used, but it is not enforced in any way. Thus, you can write ASP.NET MVC applications that do not follow the pattern, but if you decide not to follow it, you should have a good reason in doing so.

Understanding MVC Applications

To begin developing ASP.NET MVC applications, you should first download and install the latest ASP.NET MVC beta. Visual Studio's New Project dialog box should now have a new icon for "ASP.NET MVC Application" (see Figure 1).



Click here for a larger image.

Figure 1: After installing the ASP.NET MVC beta, you will see the new project icons in Visual Studio.

When you create a new ASP.NET MVC project, you will have several folders under the solution: namely Controllers, Models, and Views (see Figure 2). When you write code to implement your application, you should store each of the different code files into their correct folders. Otherwise, the system might not work as designed.

Figure 2: ASP.NET MVC applications use multiple folders.

If you run the skeleton application just created by pressing F5, your browser should display a page similar to that on Figure 3.



Click here for a larger image.

Figure 3: The basic ASP.NET MVC application look.

Often, the best way to learn a new technique is to see how an example application has been built. The skeleton created by the MVC project template is one such example. ASP.NET MVC applications handle the web requests thru controller classes, and the request from the browser is directed to a particular controller via routes.

Routes are a new feature in .NET 3.5 SP1, and ASP.NET MVC uses this feature to give controller classes the capability to respond to requests. ASP.NET MVC uses REST-like URLs (Representational State Transfer) that are cleaner than regular ASP.NET web application URLs. Here are examples of such URLs:

/products/show/881
/customers/list
/login/register

As you can see, REST-like URLs tend to be clean, simple, and don't expose .aspx files directly on the server. Although you can have directly addressed .aspx pages in ASP.NET MVC applications, this is not the main idea.

Through developer-defined routes, the ASP.NET MVC application can direct requests to controllers. Routes are defined once for the entire ASP.NET application, so the Global.asax file is the logical place to define these. In fact, if you take a look at the default Global.asax.cs file in an ASP.NET MVC application, you will see a method called RegisterRoutes defined. This method is called from the Application_Start method, and implemented like this:

public static void RegisterRoutes(RouteCollection routes)
{
   routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
   routes.MapRoute(
      "Default",
      "{controller}/{action}/{id}",
      new { controller = "Home",
         action = "Index",
         id = "" }
   );
}

By default, a route called "Default" is defined with three parts: controller, action, and finally an ID separated by slashes. When a request arrives at the MVC application, the URL is parsed according to this definition. For instance, given the request for "/products/show/881", this would be parsed so that the "controller" parameter would have the value of "products", the "action" parameter would be "show", and finally the "id" would be "881".

Note: Parameter values can be optional, in which case the route also has default values specified. For instance, if the action parameter value does not exist, the default value of "Index" is used. Similarly, "Home" is the default value for the controller. Given a request of "/" (the root), the Home controller is used with the "Index" action.

Handling Requests with Controllers

If you recall the default routing from the previous code listing, you know that in the skeleton ASP.NET MVC application, the Home controller is the default controller for requests that do not specify any controller in the request URL.

Controller classes live in the Controllers folder in the Visual Studio solution. From here, you can by default find two controller classes: the HomeController and the AccountController. The naming convention is that each controller class should have the word "Controller" in its name. This way, the MVC framework can redirect a request for "/products" for the ProductsController class.

Each of your own controller classes descends from the Controller class, which is part of the System.Web.Mvc namespace, the default namespace for all classes related to MVC. Then, each public method in your controller class can handle an action based on the method's name. For example, in the case of the previous request to "/products/show/881", the ProductsController's "Show" method would be called.

This method would be the place to write the logic to handle the request. Normally, a controller method returns a type called ActionResult (a class defined also in the System.Web.Mvc namespace), which is most commonly a view object, although it also can be something else. As you will recall, the view is the actual representation of the data, most often HTML code.

When developing your ASP.NET MVC application, you want to add our own actions and controllers. This is very easy indeed, because all you need to do is write more code! That is, if you want to add new actions such as "delete" or "insert" to your ProductsController, it is enough just to write those methods. You do not need to add anything to the routes in the Global.asax.cs file because the routing system is intelligent enough to do all this for you. The same goes with controller classes; just add your new controllers to the Controllers folder in the solution.

Returning Views to the Browser

Up to this point, you have learned about controllers. Next, you will see how controllers can manipulate a view and display data on it. Because the idea behind MVC applications is that you separate the logic from the view, it would not make much sense if the controller would construct HTML code directly to be returned to the user.

Instead, in ASP.NET MVC applications you have a separate .aspx page, which is the view (see Figure 4). However, the controller often needs to specify what data to display on the view, such as choosing the correct product. To better enable this communication between the view and the controller, the ASP.NET MVC framework contains a class called ViewData. This is a dictionary of name and value pairs, and it is accessible from both the controller class and the view's .aspx page.

Figure 4: Adding views is easy with the Add View extension command.

Here is an example of a definition of the HomeController's Index action:

public ActionResult Index()
{
   ViewData["Title"]   = "Home Page";
   ViewData["Message"] = "Welcome to ASP.NET MVC!";
   return View();
}

The Index method uses the ViewData object two times, to set a "Title" and a "Message". Because the ViewData is a collection object, you can freely choose any unique string value you prefer. The third statement in the method returns a View object, about which you will learn more soon.

In the meantime, see how the values set to the ViewData can be used from the view page. By default, a view is implemented using a regular .aspx page, whose name corresponds to the name of the action currently being served.

For instance, the preceding Index method on the controller class would correspond to the view implemented by the Index.aspx file. All views are stored in the Views folder in the solution, each in their own sub-folder matching the name of the controller. For instance, the Index.aspx file for the Home controller's Index action would be stored in Views/Home/Index.aspx inside the solution. It is important to store the views in correct folders; otherwise, they cannot be found.

Here is the implementation of the Index.aspx file of the Home action:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
    AutoEventWireup="true" CodeBehind="Index.aspx.cs"
    Inherits="MvcApplication1.Views.Home.Index" %>
<asp:Content ID="indexContent"
   ContentPlaceHolderID="MainContent" runat="server">
      <h2><%= Html.Encode(ViewData["Message"]) %></h2>
      <p>
         To learn more about ASP.NET MVC visit
         <a href="http://asp.net/mvc"
            title="ASP.NET MVC Website">http://asp.net/mvc</a>.
      </p>
</asp:Content>

In this code listing, you can find a reference to the ViewData object inside the "h2" tags. When the "Message" value has been set in the controller, the same value can be used in the .aspx page (the reference to the "Title" value appears in the Site.master file). Note how the Html.Encoding helper method is used to properly encode the string to valid HTML; it might well be that the string stored in the ViewData dictionary contains special characters which would require encoding.

The Html member is a property on both regular ASP.NET MVC pages (descending from the class System.Web.Mvc.ViewPage) and MVC master pages (descending from System.Web.Mvc.ViewMasterPage). It points to a class named HtmlHelper, also in the System.Web.Mvc namespace.

By using the ViewData object, you can transfer everything you need easily from the controller to the view. However, although you can use the ViewData object, it is not always the best available option. Next, you will learn how to bring a model to the equation.

Using Models Effectively

So far, you have seen how you can move data from a controller method to an appropriate view page by using the ViewData object. Although this is easy to do, oftentimes you will find yourself repetitively copying values from a given object to the ViewData object just to be able to show them in the view. Could there be an easier way?

Luckily, there is. By using a model class, you can specify that a view should directly understand the properties of the model class; this means you do not need to use the ViewData object nearly as often. For instance, following the previous example of product data, you might have a class like this to represent product information:

public class Product
{
   public int Id                    { get; set; }
   public string Name               { get; set; }
   public string Description        { get; set; }
   public float ListPrice           { get; set; }
   public float CostPrice           { get; set; }
   public string WebPage            { get; set; }
   public string SystemRequirements { get; set; }
}

This Product class has public properties for each value that a fictional product would need. Then, your application could have a database in which the products are stored, and you might use, for example, LINQ to SQL classes to read that data from the database into Product objects.

If you wanted to show product information on a view, you could, of course, copy each property value in the Product class to the ViewData object, and then use that object in the .aspx code. Although this works, this is tedious, especially for objects that have dozens of properties.

You can lessen your work considerably by specifying the Product class as the model for your view page. This way, you can directly access the properties of the specified model class, giving you also strong typing and IntelliSense support.

To specify the model for the view, you need to modify the view page's declaration. For example, for the Index.aspx view, you would edit the code-behind file Index.aspx.cs, and specify a mode type for the view:

public partial class Index : ViewPage<Models.Product>
{
}

By default, each view page descends from the ViewPage class and doesn't have any generic parameter types. But, by appending the desired model type in angle brackets, you can specify the type for the model that the view understands. Only one type can be specified here.

Once you have this declaration in place, you can directly access the model class' properties. This happens through the ViewData object's Model property like this (see Figure 5):

<p>Product ID: <%= ViewData.Model.Id %></p>

Figure 5: IntelliSense is available to help you once you define the model type.

As you can see from the listing, you can use the model's properties very easily in the .aspx page. Of course, specifying the model does not limit your ability to use the ViewData object in the regular way as a data dictionary. Being able to use the model is just an additional benefit.

The final piece of the model puzzle is "How does the view page know which Product object's data to display?" As you might guess, this is specified in the controller. When returning the view object, you can pass in an instance of the desired model object. Then, the view would use this particular instance when accessing the model's properties.

To specify the model object instance in the controller, you would modify the return statement slightly:

Models.Product product = new Models.Product();
product.Id = 123;
// ...
return View(product);

Note that the View method has multiple overloads, and passing the model object is one of them. It is recommended that you store all model classes inside the "Models" folder of the solution, but this is not mandatory. It should also go without saying that if your application uses databases, the controller would be the place to access it and store information to the appropriate model objects.

Please return to Developer.com on December the 10th for a follow-up article, "Accepting Input and Manipulating Data in ASP.NET MVC." In that article, you will learn how ASP.NET MVC applications map URL parts into controller method parameters, and how you can process those parameter values. Furthermore, you will also see how to manipulate database data, and effectively accept input from the user.

Conclusion

ASP.NET MVC is an interesting new way to develop web applications in the .NET world. It clearly raises the level of application development from mundane HTML and request/response processing to a higher stage.

Microsoft has used lots of effort to make ASP.NET MVC easily programmable, and, for example, the ability to use strongly typed methods is just one example. Although the usage of regular HTML controls versus regular ASP.NET server-side controls can cause confusion, the value of the MVC pattern is cleaner code.

In this article, you learned the basics of ASP.NET MVC applications: how they live and breathe. Testing the framework yourself is easy as installing the latest beta in your development machine, and starting a new MVC web application project. By following this article, you should have a head start to the new framework.

As it goes, the excuses for not using ASP.NET MVC are getting thin. It's time to learn a new technology before Christmas!

Jani Järvinen

Links

The following links help you get started with ASP.NET MVC.

About the Author

Jani Järvinen is a software development trainer and consultant in Finland. He is a Microsoft C# MVP and is a frequent author, and has published three books about software development. He is the group leader of a Finnish software development expert group named ITpro.fi. His blog can be found at http://www.saunalahti.fi/janij/. You can send him mail by clicking on his name at the top of the article.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date