37.2. ASP.NET MVC

Since the late 1990s the most common architectural pattern employed to build large web applications has been the n-tier architectural pattern. Most implementations of n-tier applications use three tiers: a presentation tier that contains the user interface code, a business logic tier that provides the core processing logic for the application, and a data access tier that is responsible for retrieving and saving information to a backend data store.

One of the problems with this architecture is that there can be a tendency for business-logic and data-access code to migrate up through the tiers until it is irrevocably intertwined with the user interface code. With ASP.NET Web Forms, which support the n-tier architecture, you do need a degree of discipline to ensure that this does not happen. It is all too common to see ASP.NET applications where SQL queries are hardcoded as strings and embedded into data access code within an event handler of a web control.

An alternate architectural pattern that has been growing in popularity over the past few years is Model-View-Controller (MVC). The MVC architectural pattern does a much better job of encouraging and enforcing a clear separation of components into application control, business logic, and UI presentation.

ASP.NET MVC provides a framework that enables you to easily create applications that use the MVC architectural pattern on the ASP.NET platform. First announced at the ALT.NET conference in late 2007, ASP.NET MVC is a good example of the more open and transparent side of Microsoft that has emerged in recent years. The source code for the framework has been published at CodePlex (http://codeplex.com/aspnet), and the features supported by the framework have been shaped through community feedback on frequent preview releases.

The ASP.NET MVC framework provides the following benefits:

  • It makes it easier to manage complexity by dividing an application into the Model, the View, and the Controller.

  • It does not use view state, server controls, or postbacks, which give you a programming model that is less of an abstraction of the way HTTP actually works. A related benefit is that this allows much more control over the HTML that your application generates.

  • It supports a custom routing infrastructure, which provides you with more control over the URLs that you publish. This makes it search engine– and REST-friendly.

  • It provides better support for test-driven development (TDD).

As Microsoft has been careful to state, ASP.NET MVC is not a replacement for Web Forms. It's simply an alternate way of building web applications on the ASP.NET platform that some people will find preferable. Microsoft has made it very clear that it will fully support both ASP.NET Web Forms and ASP.NET MVC into the future.

37.2.1. Model-View-Controller

If you've never come across it before, you might be surprised to learn that this "new" Model-View-Controller architecture was first described in 1979, which means it's older than some of you reading this book!

In the MVC architecture, applications are separated into the following components:

  • Model: The Model consists of classes that implement domain-specific logic for the application. Although the MVC architecture does not concern itself with the specifics of the data access layer, it is understood that the model should encapsulate any data access code. Generally, the model will call separate data access classes responsible for retrieving and storing information in a database.

  • View: The Views are the classes that take the model and render it into a format where the user can interact with it.

  • Controller: The Controller is responsible for bringing everything all together. A Controller processes and responds to events, such as a user clicking on a button. The Controller maps these events onto the Model and invokes the appropriate View.

These descriptions aren't really helpful until you understand how they interact together. The request life cycle of an MVC application normally consists of the following:

  1. The user performs an action that triggers an event, such as entering a URL or clicking a button. This generates a request to the Controller.

  2. The Controller receives the request and invokes the relevant action on the Model. Often this will cause a change in the Model's state, although not always.

  3. The Controller retrieves any necessary data from the Model and invokes the appropriate View, passing it the data from the Model.

  4. The View renders the data and sends it back to the user.

The most important thing to note here is that both the View and Controller depend on the Model. However, the Model has no dependencies, which is one of the key benefits of the architecture. This separation is what provides better testability and makes it easier to manage complexity.

Different MVC framework implementations have minor variations in the preceding life cycle. For example, in some cases the View will query the Model for the current state, instead of receiving it from the Controller.

Now that you understand the Model-View-Controller architectural pattern, you can begin to apply this newfound knowledge by building your first ASP.NET MVC application.

37.2.2. Getting Started with ASP.NET MVC

You can download the ASP.NET MVC framework from a link on the ASP.NET community site at http://asp.net/mvc/. The installer contains the Visual Studio 2008 project and item templates and three new assemblies: System.Web.Abstractions.dll, System.Web.MVC.dll, and System.Web.Routing.dll.

This chapter was written using ASP.NET MVC Preview 2 and some details will definitely have changed in subsequent releases since the time of writing. At the start of this chapter are instructions on how you can obtain an updated version of this chapter once the final version of ASP.NET MVC has been released.

Once you've installed ASP.NET MVC, open Visual Studio and select File New Project. In the Web project category of both Visual Basic and Visual C# you will see a new project template called ASP.NET MVC Web Application. Select the C# version of this project template and click OK.

Before the new project is created, Visual Studio will prompt you to generate a unit test project for the application as shown in Figure 37-6. Though this is not required, it is highly recommended, because improved testability is one of the core advantages of MVC. You can also add a test project later by selecting the ASP.NET MVC Test project template under the Test category in the New Project dialog box. Select the option to generate a unit test project and click OK.

Figure 37.6. Figure 37-6

When an ASP.NET MVC application is first created, it will generate a number of files and folders — quite a lot more than are found in other project templates when you first create them. In actual fact, the MVC application that is generated from the project template is a complete application that can be immediately run.

The folder structure that is automatically generated by Visual Studio is shown in Figure 37-7 and includes the following folders:

  • Content: A location to store static content files such as CSS files and images

  • Controllers: Contains the Controller files. A sample Controller called HomeController is created by the project template.

  • Models: Contains Model files. This is also a good place to store any data access classes that are encapsulated by the Model. The MVC project template does not create an example Model.

  • Views: Contains the View files. The MVC project template creates two folders and three files in the Views. The Home sub-folder contains two example View files that are invoked by the HomeController. The Shared folder contains a master page that is used by these Views.

Visual Studio also creates a Default.aspx file, which is simply a placeholder that is needed to ensure IIS loads the MVC application correctly. There is also a Global.asax file, which is used to configure the routing rules (more on that later).

Finally, if you elected to create a test project this will be created with a Controllers folder that contains a unit test stub for the HomeController class.

Figure 37.7. Figure 37-7

Although it doesn't do much yet, you can run the MVC application by pressing F5. When it opens in Internet Explorer it will first render the Index view, with a link that allows you to navigate to the About view. Neither of these views is particularly interesting, because they just render static content.

The next section explains how you can build your own Controller, followed by some more interesting Views that render a dynamic user interface.

37.2.3. Controllers and Action Methods

This section and the next walk through how to add a new Controller class and associated Views. This will provide an alternative implementation of the birthday calculator that was created earlier in this chapter as a Silverlight application.

Begin by right-clicking the Controllers folder in the Solution Explorer and selecting Add New Item. This will display the Add New Item dialog, shown in Figure 37-8. Under the Web category, there is a new entry called MVC, which contains the item templates specific to this project type. Select the MVC Controller class template and enter BirthdayController.cs as the name. The MVC framework requires all Controller names to end with "Controller".

You may be wondering why there is no template for creating an MVC Model. That's because a Model is an ordinary class that happens to implement some domain-specific functionality. Because there is no base class to inherit from, or a specific interface to implement, there is no item template to create a Model class.

Figure 37.8. Figure 37-8

Controller classes inherit from the System.Web.Mvc.Controller class, which performs all of the heavy lifting in terms of determining the relevant method to call for an action and mapping URL parameter values. This means that you can concentrate on the implementation details of your Actions, which typically involves invoking a method on a Model class and then selecting the View to render.

A newly created Controller class will be populated with a default Action method called Index. You can add a new Action simply by adding a public method to the class. If a method is public, it will be visible as an Action on the Controller. You can stop a public method from being exposed as an Action by adding the System.Web.Mvc.NonAction attribute to the method. The following listing contains the Controller class with the default Action that simply renders the Index view, and a public method that is not visible as an Action:

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

    [System.Web.Mvc.NonAction()]
    public void NotAnAction()
    {
            // This is not visible as an Action
    }
}

Before you go any further you will need to create a class for the Model. In the Models folder of the solution, add a new class file called Birthday.cs. Within this class, add the following code:

public class Birthday
{
    private DateTime mDoB;

    public Birthday(DateTime DoB)
    {
      mDoB = DoB;
    }

    public int AgeInYears
    {
        get
        {
            if (DateTime.Now.Month < mDoB.Month ||
               (DateTime.Now.Month == mDoB.Month && DateTime.Now.Day < mDoB.Day))
            {
                return (DateTime.Now.Year - mDoB.Year) - 1;
            }
            else
            {
                return DateTime.Now.Year - mDoB.Year;
            }
        }
    }

    public int AgeInDays
    {
        get
        {

return DateTime.Now.Subtract(mDoB).Days;
        }

    }

    public string BornOn
    {
        get
        {
            return mDoB.DayOfWeek.ToString();
        }
    }

    public string NextBirthdayOn
    {
        get
        {
            return mDoB.AddYears(AgeInYears + 1).DayOfWeek.ToString();
        }
    }
}

This code is basically a copy of the logic that was used in the Silverlight application that was created earlier in this chapter. However, you have now encapsulated it in its own class, rather than having it mixed up with the user interface code. The Birthday class consists of a constructor that takes a DateTime representing the Date of Birth, and four public properties that calculate the age, day of birth, and day of the next birthday.

Now you are ready to implement a new Controller Action. Within the BirthdayController.cs file add the Age method, as shown in the following code listing:

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

    public void Age(DateTime DoB)
    {
        Models.Birthday birthday = new Models.Birthday(DoB);
        ViewData["AgeInYears"] = birthday.AgeInYears;
        ViewData["AgeInDays"] = birthday.AgeInDays;
        ViewData["BornOn"] = birthday.BornOn;
        ViewData["NextBirthdayOn"] = birthday.NextBirthdayOn;
        RenderView("Age");
    }

    [System.Web.Mvc.NonAction()]
    public void NotAnAction()
    {
            // This is not visible as an Action
    }
}

As mentioned earlier, you don't need to do anything to expose this method as an Action. Simply declaring it as public is sufficient to make it accessible. As you will see in the following section, the DateTime parameter is specified on the View that invokes this Controller Action, and is automatically mapped by the MVC framework.

This Action method begins by instantiating the Birthday class, which is functioning as the Model, and passing in the DoB parameter. The ViewData is a dictionary object that can store any object or variable and is accessible by the Views. This is how the Controller passes data from the Model onto the Views. Finally the Action method calls the RenderView method, passing in the name of the View to be invoked as a string parameter.

Now that you have created your Model and Controller, all that's needed is to create the Views to display the UI.

37.2.4. Rendering a UI with Views

Views are added in much the same way as Controllers, except they must be placed in a specific folder so that the ASP.NET MVC framework can find them. Within the Views folder of the project, create a new folder that matches the name of the Controller. For example, the Controller class that was added earlier was called BirthdayController; therefore the folder you create under Views should be called Birthday.

Master/Detail View pages are supported in ASP.NET MVC. Because the example Views created by the project template use Master/Detail pages, we won't demonstrate them here.

Right-click this new folder, select Add New Item, and add an MVC View Page item called Index.aspx. The file that is created is very similar to a standard Web Forms page; for example, it contains a page directive where you can specify the usual attributes such as cache settings and whether ViewState is enabled. There is also a code-behind file for the page. However, there are some important differences that need to be highlighted.

First, instead of inheriting from System.Web.UI.Page, an MVC View inherits from System.Web.Mvc.ViewPage. Secondly, in the markup you will notice that there is no server-based form by default. No server form means that there is no ViewState emitted with the page.

The majority of the ASP.NET server controls must be placed inside a server form. Some controls such as a Literal or Repeater control will work fine outside a form; however, if you try to use a Button or DropDownList control, your page will throw an exception at runtime.

Many people will prefer to simply implement the view using standard HTML elements, because it provides the way to get the most control and produce the cleanest markup. However, if you are a developer who prefers the convenience of server controls, you can use the HTML helpers that are included with the framework. Previously referred to as the MVC Toolkit, the helpers provide a quick way to automatically generate HTML elements such as buttons and drop-down lists. These can be thought of as server controls for MVC; however, unlike server controls, they don't use ViewState or generate postbacks and they render much cleaner HTML.

The following HTML listing is for the Index.aspx View. The shaded lines are the markup that we have modified or added:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="
MvcApplication1.Views.Birthday.Index" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/
TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Birthday Calculator</title>
</head>
<body>
    <form id="myForm" action="/birthday/age" method="post">
    <div>
        Enter your Date of Birth:
        <input type="text" name="DoB" />
    </div>
    </form>
</body>
</html>

There are a couple of important things to point out about this View. First, the action attribute of the form has been formatted to match the initial routing rules. By default, the MVC framework will map a URL based on the Controller name followed by the Action method name. In the preceding example, when the form is submitted it will post the form to the URL /birthday/age, which will be mapped to the age method on the BirthdayController class. Routing rules are discussed further in the next section.

Secondly the ASP.NET MVC framework automatically maps any fields that are in the form post-data, or have been added as query parameters, into parameters on the Action method. In the preceding example, the input textbox has been given a name of DoB. Because this name matches the DoB DateTime parameter on the age method it will be automatically mapped.

If the user enters an invalid date into this form, it will throw an exception. It is possible (and good practice) for you to handle this exception and render the view again indicating the error.

Next, add another View called Age.aspx. As you saw in a previous listing, the Controller will call this View to display the results of the age Action method. The following listing uses shading to show the markup that has been modified on this page. The ViewData dictionary object, which was populated by the Controller Action method, contains the results of the action from the Model.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Age.aspx.cs"
Inherits="MvcApplication1.Views.Birthday.Age" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
   Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

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

<body>
    <div>
        You are <%= ViewData["AgeInYears"] %> years old.<br />
        You are <%= ViewData["AgeInDays"] %> days old.<br />
        You were born on a <%= ViewData["BornOn"] %>.<br />
        Your next birthday will be on a <%= ViewData["NextBirthdayOn"] %>.
    </div>
</body>
</html>

You are now ready to run the MVC application. Press F5, and the project will compile and open in Internet Explorer. The default page that is loaded belongs to a View on the HomeController Action that was first created with the project. To navigate to the BirthdayController add /Birthday to the end of the URL. Figure 37-9 (left) shows the Index Action and View, which is first displayed when the BirthdayController is invoked. Figure 37-9 (right) shows the results of submitting this form, which is the Age Action and View.

Figure 37.9. Figure 37-9

In addition to passing the ViewData as a late-bound, weak-typed dictionary object, you can use an alternate method to pass a strongly-typed class. This will give you IntelliSense and type checking from within the View. For example, rather than query the Model in the Controller Action and pass the results into the View, you could simply pass the entire Models.Birthday object through to the View, as shown in the following modified listing:

public void Age(DateTime DoB)
{
    Models.Birthday birthday = new Models.Birthday(DoB);
    RenderView("Age2", birthday);
}

In addition to the ViewPage base class that we have been using so far, the ASP.NET MVC framework also includes a ViewPage<T> class that uses generics. In the code-behind file, Age.aspx.cs, update the class to inherit from ViewPage<Models.Birthday>, as shown in the following listing:

public partial class Age : ViewPage<Models.Birthday>
{
}

The ViewData property will now be strongly typed using the Birthday Model class. This will provide you with IntelliSense as shown in Figure 37-10, as well as type checking at compile time.

Figure 37.10. Figure 37-10

37.2.5. Custom URL Routing

As mentioned previously, the ASP.NET MVC framework uses custom URL routing rules to map URLs to Controller classes and Actions. Each rule is defined according to a URL pattern that you define.

Although it is shipped along with ASP.NET MVC, the URL routing is implemented in a separate namespace and assembly called System.Web.Routing. This means that you can use it to create custom URL routing in an ASP.NET Web Forms application without introducing any dependency on MVC.

The following code listing shows the default routing rules that are created when the ASP.NET MVC project is created by Visual Studio:

public class GlobalApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        // Note: Change the URL to "{controller}.mvc/{action}/{id}" to enable
        //       automatic support on IIS6 and IIS7 classic mode

        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);
    }
}

You can modify the preceding to ensure that the new BirthdayController is used by default when the application is first opened by replacing the controller="Home" with controller="Birthday".

Routing rules are added by adding them to the System.Web.Mvc.RouteTable.Routes collection. This is a static collection of System.Web.Mvc.Route objects.

The order in which Route objects are added to the Routes collection is important. Rules are evaluated in order starting with the first route that was added to the collection. When a match is found, no more routes are evaluated. This means that typically routes are added starting with the strictest rule first, with a general catch-all route as the last route.

In addition to the default route, the ASP.NET MVC application project template creates a default rule based on the pattern {controller}/{action}/{id}. For example, assuming that the MVC application had been deployed to the root of a web server, this routing rule would match the URL http://servername/products/list/monitors, and subsequently call the Action list on the Controller products, passing in the parameter id=monitors. If no Action or Id was specified, for example in the URL http://servername/products/, the default action called Index would be called.

You can also add validation rules to a route that can be used to further control URL routing. These validation rules can use regular expressions. For example, the following route enables a URL like /news/item/123456, but not /news/item/12345678 or /news/item/1234:

routes.Add(new Route("news/item/[id]", new MvcRouteHandler())
{
   Defaults = new { controller="News", action="Item" },
   Validation = new { id =  @" ^d{6}$ " }
});

The URL routing classes are very powerful and flexible, and allow you to easily create "pretty" URLs. Though this appeals to some developers' senses of aesthetics, there are other good reasons to have URLs formatted in this way, such as making your application search engine–friendly or exposing a REST API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.168.214