Chapter 2. Your First ASP.NET MVC Application

The best way to appreciate a software development framework is to jump right in and use it. In this chapter, you'll create a simple data entry application using ASP.NET MVC 2.

Note

In this chapter, the pace is deliberately slow. For example, you'll be given step-by-step instructions on how to complete even small tasks such as adding new files to your project. Subsequent chapters will assume greater familiarity with C# and Visual Studio.

Preparing Your Workstation

Before you can write any ASP.NET MVC 2 code, you need to install the relevant development tools on your workstation. To build an ASP.NET MVC 2 application, you need either of the following:[6]

  • Visual Studio 2010 (any edition) or the free Visual Web Developer 2010 Express. These include ASP.NET MVC 2 by default.

  • Visual Studio 2008 with SP1 (any edition) or the free Visual Web Developer 2008 Express with SP1. These do not include ASP.NET MVC 2 by default; you must also download and install ASP.NET MVC 2 from www.asp.net/mvc/.

If you don't have any of these, then the easiest way to get started is to download and use Microsoft's Web Platform Installer, which is available free of charge from www.microsoft.com/web/. This tool automates the process of downloading and installing the latest versions of Visual Web Developer Express, ASP.NET MVC 2, SQL Server 2008 Express, IIS, and various other useful development tools.

Note

While it is possible to develop ASP.NET MVC applications in the free Visual Web Developer 2008/2010 Express, I recognize that the considerable majority of professional developers will instead use Visual Studio, because it's a much more sophisticated commercial product. Almost everywhere in this book I'll assume you're using Visual Studio 2008 or 2010, and I'll rarely refer to Visual Web Developer Express.

Creating a New ASP.NET MVC Project

Once you've installed ASP.NET MVC 2 (which is already installed by default if you're running Visual Studio 2010), you'll have a choice of two templates to start your new project:

  • The ASP.NET MVC 2 Web Application template creates a small example application that you can build on. This includes prebuilt user registration, authentication, navigation, and a relaxing, blue-themed CSS stylesheet.

  • The ASP.NET MVC 2 Empty Web Application template sets up only the minimal set of files and folders that are needed for almost every ASP.NET MVC 2 application.

To avoid distraction and ensure you gain the clearest understanding of how the framework truly works, we'll use the empty template.

Let's get started: open Visual Studio and go to File

Creating a New ASP.NET MVC Project
Creating a new ASP.NET MVC web application

Figure 2-1. Creating a new ASP.NET MVC web application

You can call your project anything you like, but since this demonstration application will handle RSVPs for a party (you'll hear more about that later), a good name would be PartyInvites.

When you click OK, Visual Studio will set up a default project structure for you. You can try to run the application now by pressing F5 (or by selecting Debug

Creating a new ASP.NET MVC web application
A newborn ASP.NET MVC 2 Empty Web Application contains no contollers, so it can't yet handle any requests.

Figure 2-2. A newborn ASP.NET MVC 2 Empty Web Application contains no contollers, so it can't yet handle any requests.

When you're done, be sure to stop debugging by closing the Internet Explorer window that appeared, or by going back to Visual Studio and pressing Shift+F5.

Adding the First Controller

In model-view-controller (MVC) architecture, incoming requests are handled by controllers. In ASP.NET MVC, controllers are just simple C# classes (usually inheriting from System.Web.Mvc.Controller, the framework's built-in controller base class).[7] Each public method on a controller is known as an action method, which means you can invoke it from the Web via some URL.

The default project template includes a folder called Controllers. It isn't compulsory to put your controllers here, but it is a helpful convention. So, to add the first controller, right-click the Controllers folder in Visual Studio's Solution Explorer and choose Add

Adding the First Controller
Adding a new controller, HomeController, to the project

Figure 2-3. Adding a new controller, HomeController, to the project

Next, when HomeController.cs appears, remove any code that it already contains, and replace the whole HomeController class with this:

public class HomeController : Controller
{
    public string Index()
    {
        return "Hello, world!";
    }
}

It isn't very exciting—it's just a way of getting right down to basics. Try running the project now (press F5 again), and you should see your message displayed in a browser (Figure 2-4).

The initial application output

Figure 2-4. The initial application output

How Does It Know to Invoke HomeController?

As well as models, views, and controllers, ASP.NET MVC applications also use the routing system. This piece of infrastructure decides how URLs map onto particular controllers and actions. Under the default routing configuration, you could request any of the following URLs and it would be handled by the Index action on HomeController:

  • /

  • /Home

  • /Home/Index

So, when a browser requests http://yoursite/ or http://yoursite/Home, it gets back the output from HomeController's Index method. Right now, the output is the string Hello, world!.

You can see and edit your routing configuration by opening your project's Global.asax.cs file, but for this chapter's simple example, the default configuration will suffice. In Chapter 4 you'll set up custom routing entries, and in Chapter 8 you'll learn much more about what routing can do.

Rendering Web Pages

If you got this far, well done—your development environment is working perfectly, and you've already created a working, minimal controller. The next step is to produce some HTML output.

Creating and Rendering a View

Your existing controller, HomeController, currently sends a plain-text string to the browser. That's fine for debugging, but in real applications you're more likely to generate an HTML document, and you do so by using a view.

To render a view from your Index() method, first rewrite the method as follows:

public class HomeController : Controller
{
    public ViewResult Index()
    {
        return View();
    }
}

By returning an object of type ViewResult, you're giving the MVC Framework an instruction to render a view. Because you're generating that ViewResult object by calling View() with no parameters, you're telling the framework to render the action's default view. However, if you try to run your application now, you'll get the error message displayed in Figure 2-5.

Error message shown when ASP.NET MVC can't find a view template

Figure 2-5. Error message shown when ASP.NET MVC can't find a view template

Again, I'm showing you this error message so you can see exactly what the framework is trying to do and so you don't think there's some hidden magic happening. This error message is more helpful than most—the framework tells you not just that it couldn't find any suitable view to render, but also where it tried looking for one. Here's your first bit of convention-over-configuration: view files are normally associated with action methods by means of a naming convention, rather than by means of explicit configuration. When the framework wants to find the default view for an action called Index on a controller called HomeController, it will check the four locations listed in Figure 2-5.

To add a view for the Index action—and to make that error go away—right-click the action method (either on the Index() method name or somewhere inside the method body) and then choose Add View. This will lead to the pop-up window shown in Figure 2-6.

Adding a view template for the Index action

Figure 2-6. Adding a view template for the Index action

Uncheck "Select master page" (since we're not using master pages in this example) and then click Add. This will create a brand new view file for you at the correct default location for your action method: ~/Views/Home/Index.aspx.

As Visual Studio's HTML markup editor appears,[8] you'll see something familiar: an HTML page prepopulated with the usual collection of elements—<html>, <body>, and so on. Let's move the Hello, world! greeting into the view. Replace the whole <body> section of the HTML markup with

<body>
   Hello, world (from the view)!
</body>

Press F5 to launch the application again, and you should see your view template at work (Figure 2-7).

Output from the view

Figure 2-7. Output from the view

Previously, your Index() action method simply returned a string, so the MVC Framework had nothing to do but send that string as the HTTP response. Now, though, you're returning an object of type ViewResult, which instructs the MVC Framework to render a view. You didn't specify a view name, so it picks the conventional one for this action method (i.e., ~/Views/Home/Index.aspx).

Besides ViewResult, there are other types of objects you can return from an action, which instruct the framework to do different things. For example, RedirectResult performs a redirection, and HttpUnauthorizedResult forces the visitor to log in. These things are called action results, and they all derive from the ActionResult base class. You'll learn about each of them in due course. This action results system lets you encapsulate and reuse common response types, and it simplifies unit testing tremendously.

Adding Dynamic Output

Of course, the whole point of a web application platform is the ability to construct and display dynamic output. In ASP.NET MVC, it's the controller's job to construct some data, and the view's job to render it as HTML. This separation of concerns keeps your application tidy. The data is passed from controller to view using a data structure called ViewData.

As a simple example, alter your HomeController's Index() action method (again) to add a string into ViewData:

public ViewResult Index()
{
    int hour = DateTime.Now.Hour;
    ViewData["greeting"] = (hour < 12 ? "Good morning" : "Good afternoon");
    return View();
}

and update your Index.aspx view template to display it as follows. If you're using Visual Studio 2010 and chose to target .NET Framework 4 when you first created the project, write

<body>
   <%: ViewData["greeting"] %>, world (from the view)!
</body>

Otherwise, if you're using Visual Studio 2008 or targeting .NET Framework 3.5, write

<body>
   <%= Html.Encode(ViewData["greeting"]) %>, world (from the view)!
</body>

Note

Here, we're using inline code (the <%: ... %> or <%= ... %> blocks). This practice is sometimes frowned upon in the ASP.NET Web Forms world, but it's your route to happiness with ASP.NET MVC. Put aside any prejudices you might hold right now—later in this book you'll find a full explanation of why, for MVC views, inline code works so well. Also, I'll explain the difference between <%: ... %> and <%= ... %> a page or two from here.

Not surprisingly, when you run the application again (press F5), your dynamically chosen greeting will appear in the browser (Figure 2-8).

Dynamically generated output

Figure 2-8. Dynamically generated output

A Starter Application

In the remainder of this chapter, you'll learn some more of the basic ASP.NET MVC principles by building a simple data entry application. The goal here is just to see the platform in operation, so we'll create it without slowing down to fully explain how each bit works behind the scenes.

Don't worry if some parts seem unfamiliar to you. In the next chapter, you'll find a discussion of the key MVC architectural principles, and the rest of the book will give increasingly detailed explanations and demonstrations of virtually all ASP.NET MVC 2 features.

The Story

Your friend is having a New Year's party, and she's asked you to create a web site that allows invitees to send back an electronic RSVP. This application, PartyInvites, will

  • Have a home page showing information about the party

  • Have an RSVP form into which invitees can enter their contact details and say whether or not they will attend

  • Validate form submissions, displaying a thank you page if successful

  • E-mail details of completed RSVPs to the party organizer

I can't promise that it will be enough for you to retire as a Web 3.0 billionaire, but it's a good start. You can implement the first bullet point feature immediately: just add some HTML to your existing Index.aspx view. If you're using Visual Studio 2010/.NET 4, update the view as follows:

<body>
    <h1>New Year's Party</h1>
    <p>
        <%: ViewData["greeting"] %>! </para>
        We're going to have an exciting party. </para>
        (To do: sell it better. Add pictures or something.)
    </p>
</body>

Or, if you're using Visual Studio 2008/.NET 3.5, update it as follows:

<body>
    <h1>New Year's Party</h1>
    <p>
        <%= Html.Encode(ViewData["greeting"]) %>!
        We're going to have an exciting party.
        (To do: sell it better. Add pictures or something.)
    </p>
</body>

Designing a Data Model

You could go right ahead and create lots more actions and views, but before you do that, take a step back and think about the application you're building.

In MVC, M stands for model, and it's the most important character in the story. Your model is a software representation of the real-world objects, processes, and rules that make up the subject matter, or domain, of your application. It's the central keeper of data and domain logic (i.e., business processes and rules). Everything else (controllers and views) is merely plumbing needed to expose the model's operations and data to the Web. A well-crafted MVC application isn't just an ad hoc collection of controllers and views; there's always a model, a recognizable software component in its own right. The next chapter will cover this architecture, with comparisons to others, in more detail.

You don't need much of a domain model for the PartyInvites application, but there is one obvious type of model object that we'll use, which we'll call GuestResponse. This object will be responsible for storing, validating, and ultimately confirming an invitee's RSVP.

Adding a Model Class

Use Solution Explorer to add a new, blank C# class called GuestResponse inside the /Models folder, and then give it some properties:

public class GuestResponse
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
    public bool? WillAttend { get; set; }
}

This class uses C# 3 automatic properties (i.e., { get; set; }). Don't worry if you still haven't caught up with C# 3—its syntax is covered at the end of the next chapter. Also notice that WillAttend is a nullable bool (the question mark makes it nullable). This creates a tri-state value: True, False, or null—the latter value for when the guest hasn't yet specified whether they'll attend.

Linking Between Actions

There's going to be an RSVP form, so you'll need to place a link to it. Update Index.aspx as follows if you're using Visual Studio 2010/.NET 4:

<body>
    <h1>New Year's Party</h1>
    <p>
        <%:ViewData["greeting"] %>!
        We're going to have an exciting party.
        (To do: sell it better. Add pictures or something.)
    </p>
    <%: Html.ActionLink("RSVP Now", "RsvpForm") %>
</body>

Or, if you're using Visual Studio 2008/.NET 3.5, update it as follows (don't worry, I'll stop talking about these differences in a moment):

<body>
    <h1>New Year's Party</h1>
    <p>
        <%= Html.Encode(ViewData["greeting"]) %>!
        We're going to have an exciting party.
        (To do: sell it better. Add pictures or something.)
    </p>
    <%= Html.ActionLink("RSVP Now", "RsvpForm") %>
</body>

Note

Html.ActionLink is a HTML helper method. The framework comes with a built-in collection of useful HTML helpers that give you a convenient shorthand for rendering not just HTML links, but also text input boxes, check boxes, selection boxes, and so on, and even custom controls. When you type <%: Html. or <%= Html., you'll see Visual Studio's IntelliSense spring forward to let you pick from the available HTML helper methods. They're each explained in Chapter 11, though most are obvious.

Run the project again, and you'll see the new link, as shown in Figure 2-9.

A view with a link

Figure 2-9. A view with a link

But if you click the RSVP Now link, you'll get a 404 Not Found error. Check out the browser's address bar: it will read http://yourserver/Home/RsvpForm.

That's because Html.ActionLink() inspected your routing configuration and figured out that, under the current (default) configuration, /Home/RsvpForm is the URL for an action called RsvpForm on a controller called HomeController. Unlike in traditional ASP.NET Web Forms, PHP, and many older web development platforms, URLs in ASP.NET MVC don't correspond to files on the server's hard disk—instead, they're mapped through a routing configuration onto a controller and action method. Each action method automatically has its own URL; you don't need to create a separate page or class for each URL.

Of course, the reason for the 404 Not Found error is that you haven't yet defined any action method called RsvpForm(). Add a new method to your HomeController class:

public ViewResult RsvpForm()
{
    return View();
}

Introducing Strongly Typed Views

Again, you'll need to add a view for that new action. But first, make sure you've compiled your code either by pressing F5 to run it again, by choosing Build

Introducing Strongly Typed Views

Next, to create the view for RsvpForm(), right-click inside the RsvpForm() method and then choose Add View. This time, the view will be slightly different: we'll specify that it's primarily intended to render a single specific type of model object, as opposed to the previous view, which just rendered an ad hoc collection of things found in the ViewData structure. This makes it a strongly typed view, and you'll see the benefit of it shortly.

Figure 2-10 shows the options you should use in the Add View pop-up. Make sure that "Select master page" is unchecked, and this time check the box labeled "Create a strongly typed view." In the "View data class" drop-down, select the GuestResponse type. Leave "View content" set to Empty. Finally, click Add.

To create a strongly typed view, specify a view data class.

Figure 2-10. To create a strongly typed view, specify a view data class.

When you click Add, you'll get a new view at this action's default view location, ~/Views/Home/RsvpForm.aspx. If you try running the application now, then when you click the RSVP Now link, your new view should render as a blank page in the browser.

Tip

Practice jumping quickly from an action method to its default view and back again. In Visual Studio, position the caret inside either of your action methods, right-click, and choose Go To View, or press Ctrl+M and then Ctrl+G. You'll jump directly to the action's default view. To jump from a view to its associated action, right-click anywhere in the view markup and choose Go To Controller, or press Ctrl+M and then Ctrl+G again. This saves you from hunting around when you have lots of tabs open.

Building a Form

It's now time to work on RsvpForm.aspx, turning it into a form for editing instances of GuestResponse. Go back to RsvpForm.aspx and use ASP.NET MVC's built-in helper methods to construct an HTML form:

<body>
    <h1>RSVP</h1>

    <% using(Html.BeginForm()) { %>
        <p>Your name: <%: Html.TextBoxFor(x => x.Name) %></p>
        <p>Your email: <%: Html.TextBoxFor(x => x.Email) %></p>
        <p>Your phone: <%: Html.TextBoxFor(x => x.Phone) %></p>
        <p>
            Will you attend?
            <%: Html.DropDownListFor(x => x.WillAttend, new[] {
                new SelectListItem { Text = "Yes, I'll be there",
                                     Value = bool.TrueString },
                new SelectListItem { Text = "No, I can't come",
                                     Value = bool.FalseString }
            }, "Choose an option") %>
        </p>
        <input type="submit" value="Submit RSVP" />
    <% } %>
</body>

Note

If you run this and get a compilation error saying "Invalid expression term ':'", it's because you're trying to use .NET 4's <%: ... %> syntax even though you're running on .NET 3.5. That won't work—you need to adapt the syntax to work on .NET 3.5 as I described in the preceding sidebar, "HowDoes <%: ... %> Differ from <%= ... %>?" I won't keep placing reminders throughout the chapter! From here on, if you're running on .NET 3.5, you need to replace <%: ... %> with .NET 3.5-compatible syntax in all of the view code on your own.

For each model property, you're using an HTML helper to render a suitable input control. These HTML helpers let you pick out a model property using a lambda expression (i.e., the x => x.PropertyName syntax). This is only possible because your view is strongly typed, which means the framework already knows what model type you're using and therefore what properties it has. In case you're unfamiliar with lambda expressions, which were introduced in C# 3, there's an explanation at the end of Chapter 3.

As an alternative to using a lambda expression, you could use a different HTML helper that lets you specify the target model property as a string—for example, <%: Html.TextBox("Phone") %>. However, the great benefit of lambda expressions is that they're strongly typed, so you get full IntelliSense when editing the view (as shown in Figure 2-11), and if you use a refactoring tool to rename a model property, all your views will be updated automatically.

IntelliSense while editing a strongly typed view

Figure 2-11. IntelliSense while editing a strongly typed view

I should also point out the <% using(Html.BeginForm(...)) { ... } %> helper syntax. This creative use of C#'s using syntax renders an opening HTML <form> tag where it first appears and a closing </form> tag at the end of the using block. You can pass parameters to Html.BeginForm(), telling it which action method the form should post to when submitted, but since you're not passing any parameters to Html.BeginForm(), it assumes you want the form to post to the same URL from which it was rendered. So, this helper will render the following HTML:

<form action="/Home/RsvpForm" method="post" >
    ... form contents go here ...
</form>

Note

"Traditional" ASP.NET Web Forms requires you to surround your entire page in exactly one server-side form (i.e., <form runat="server">), which is Web Forms' container for ViewState data and postback logic. However, ASP.NET MVC doesn't use server-side forms. It just uses plain, straightforward HTML forms (i.e., <form> tags, usually but not necessarily generated via a call to Html.BeginForm()). You can have as many of them as you like in a single view page, and their output is perfectly clean—they don't add any extra hidden fields (e.g., __VIEWSTATE), and they don't mangle your element IDs.

I'm sure you're itching to try your new form out, so relaunch your application and click the RSVP Now link. Figure 2-12 shows your glorious form in all its magnificent, raw beauty.[9]

Output from the RsvpForm.aspx view

Figure 2-12. Output from the RsvpForm.aspx view

Dude, Where's My Data?

If you fill out the form and click Submit RSVP, a strange thing will happen. The same form will immediately reappear, but with all the input boxes reset to a blank state. What's going on? Well, since this form posts to /Home/RsvpForm, your RsvpForm() action method will run again and render the same view again. The input boxes will be blank because there isn't any data to put in them; any user-entered values will be discarded because you haven't done anything to receive or process them.

Warning

Forms in ASP.NET MVC do not behave like forms in ASP.NET Web Forms! ASP.NET MVC deliberately does not have a concept of postbacks, so when you rerender the same form multiple times in succession, you shouldn't automatically expect a text box to retain its contents. In fact, you shouldn't even think of it as being the same text box on the next request: since HTTP is stateless, the input controls rendered for each request are totally newborn and independent of any that preceded them. However, when you do want the effect of preserving input control values, that's easy, and we'll make that happen in a moment.

Handling Form Submissions

To receive and process submitted form data, we're going to do a clever thing. We'll slice the RsvpForm action down the middle, creating the following:

  • Amethod that responds to HTTP GET requests: Note that a GET request is what a browser issues normally each time someone clicks a link. This version of the action will be responsible for displaying the initial blank form when someone first visits /Home/RsvpForm.

  • Amethod that responds to HTTP POST requests: By default, forms rendered using Html.BeginForm() are submitted by the browser as a POST request. This version of the action will be responsible for receiving submitted data and deciding what to do with it.

Writing these as two separate C# methods helps keep your code tidy, since the two methods have totally different responsibilities. However, from outside, the pair of C# methods will be seen as a single logical action, since they will have the same name and are invoked by requesting the same URL.

Replace your current single RsvpForm() method with the following:

[HttpGet]
public ViewResult RsvpForm()
{
    return View();
}

[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse)
{
    // Todo: E-mail guestResponse to the party organizer
    return View("Thanks", guestResponse);
}

Tip

You'll need to import the PartyInvites.Models namespace; otherwise, Visual Studio won't recognize the type GuestResponse. The least brain-taxing way to do this is to position the caret on the unrecognized word, GuestResponse, and then press Ctrl+dot. When the prompt appears, press Enter. Visual Studio will automatically import the correct namespace for you.

No doubt you can guess what the [HttpGet] and [HttpPost] attributes do. When present, they restrict which type of HTTP request an action will respond to. The first RsvpForm() overload will only respond to GET requests; the second RsvpForm() overload will only respond to POST requests.

Introducing Model Binding

The first overload simply renders the same default view as before. The second overload is more interesting because it takes an instance of GuestResponse as a parameter. Given that the method is being invoked via an HTTP request, and that GuestResponse is a .NET type that is totally unknown to HTTP, how can an HTTP request possibly supply a GuestResponse instance? The answer is model binding, an extremely useful feature of ASP.NET MVC whereby incoming data is automatically parsed and used to populate action method parameters by matching incoming key/value pairs with the names of properties on the desired .NET type.

This powerful, customizable mechanism eliminates much of the humdrum plumbing associated with handling HTTP requests, letting you work primarily in terms of strongly typed .NET objects rather than low-level fiddling with Request.Form[] and Request.QueryString[] dictionaries, as is often necessary in Web Forms. Because the input controls defined in RsvpForm.aspx render with names corresponding to the names of properties on GuestResponse, the framework will supply to your action method a GuestResponse instance already fully populated with whatever data the user entered into the form. Handy! You'll learn much more about this powerful mechanism, including how to customize it, in Chapter 12.

Rendering Arbitrary Views and Passing a Model Object to Them

The second overload of RsvpForm() also demonstrates how to render a specific view template that doesn't necessarily match the name of the action, and how to pass a single, specific model object that you want to render. Here's the line I'm talking about:

return View("Thanks", guestResponse);

This line tells ASP.NET MVC to find and render a view called Thanks, and to supply the guestResponse object to that view. Since this all happens in a controller called HomeController, ASP.NET MVC will expect to find the Thanks view at ~/Views/Home/Thanks.aspx, but of course no such file yet exists. Let's create it.

Create the view by right-clicking inside any action method in HomeController and then choosing Add View. This will be another strongly typed view because it will receive and render a GuestResponse instance. Figure 2-13 shows the options you should use in the Add View pop-up. Enter the view name Thanks, make sure that "Select master page" is unchecked, and again check the box labeled "Create a strongly typed view." In the "View data class" drop-down, select the GuestResponse type. Leave "View content" set to Empty. Finally, click Add.

Adding a strongly typed view to work with a particular model class

Figure 2-13. Adding a strongly typed view to work with a particular model class

Once again, Visual Studio will create a new view templatefor you at the location that follows ASP.NET MVC conventions (this time, it will go at ~/Views/Home/Thanks.aspx). This view is strongly typed to work with a GuestResponse instance, so you'll have access to a variable called Model, of type GuestResponse, which is the instance being rendered. Enter the following markup:[10]

<body>
    <h1>Thank you, <%: Model.Name %>!</h1>
    <% if(Model.WillAttend == true) { %>
           It's great that you're coming. The drinks are already in the fridge!
    <% } else { %>
           Sorry to hear you can't make it, but thanks for letting us know.
    <% } %>
</body>

You can now fire up your application, fill in the form, submit it, and see a sensible result, as shown in Figure 2-14.

Output from the Thanks.aspx view

Figure 2-14. Output from the Thanks.aspx view

Adding Validation

You may have noticed that so far, there's no validation whatsoever. You can type in any nonsense for an e-mail address, or even just submit a completely blank form.

It's time to rectify that, but before you go looking for the validation controls, remember that this is an MVC application, and following the don't-repeat-yourself principle, validation rules apply to a model, not a user interface. Validation often reflects business rules, which are most maintainable when expressed coherently in one and only one place, not scattered variously across multiple controller classes and ASPX and ASCX files.

The .NET class library has a namespace called System.ComponentModel.DataAnnotations that includes attributes you can use to define validation rules declaratively. To use them, go back to your GuestResponse model class and update it as follows:

public class GuestResponse
{
    [Required(ErrorMessage="Please enter your name")]
    public string Name { get; set; }

    [Required(ErrorMessage = "Please enter your email address")]
    [RegularExpression(".+\@.+\..+",
                       ErrorMessage = "Please enter a valid email address")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Please enter your phone number")]
    public string Phone { get; set; }

    [Required(ErrorMessage = "Please specify whether you'll attend")]
    public bool? WillAttend { get; set; }
}

Note

You'll need to add a using statement for System.ComponentModel.DataAnnotations. Again, Visual Studio can do this for you with the Ctrl+dot trick.

ASP.NET MVC automatically recognizes your model's Data Annotations attributes and uses them to validate incoming data when it performs model binding. Let's update the second RsvpForm() action method so that if there were any validation errors, it redisplays the default view instead of rendering the Thanks view:

[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse)
{
    if (ModelState.IsValid) {
        // To do: E-mail guestResponse to the party organizer
        return View("Thanks", guestResponse);
    }
    else // Validation error, so redisplay data entry form
        return View();
}

Finally, choose where to display any validation error messages by adding an Html.ValidationSummary() to the form in the RsvpForm.aspx view:

<body>
    <h1>RSVP</h1>

    <% using(Html.BeginForm()) { %>
        <%: Html.ValidationSummary() %>
        ... leave rest as before ...

And now, if you try to submit a blank form or enter invalid data, you'll see the validation kick in (Figure 2-15).

The validation feature working

Figure 2-15. The validation feature working

Model Binding Tells Input Controls to Redisplay User-Entered Values

I mentioned previously that because HTTP is stateless, you shouldn't expect input controls to retain state across multiple requests. However, because you're now using model binding to parse the incoming data, you'll find that when you redisplay the form after a validation error, the input controls will redisplay any user-entered values. This creates the appearance of controls retaining state, just as a user would expect. It's a convenient, lightweight mechanism built into ASP.NET MVC's model binding and HTML helper systems. You'll learn about this mechanism in full detail in Chapter 12.

Note

If you've worked with ASP.NET Web Forms, you'll know that Web Forms has a concept of "server controls" that retain state by serializing values into a hidden form field called __VIEWSTATE. Please rest assured that ASP.NET MVC model binding has absolutely nothing to do with Web Forms concepts of server controls, postbacks, or ViewState. ASP.NET MVC never injects a hidden __VIEWSTATE field into your rendered HTML pages.

Highlighting Invalid Fields

The built-in HTML helpers for text boxes, drop-downs, and so on have a further neat trick. The same mechanism that lets helpers reuse previously attempted values (to retain state) also tells the helpers whether the previously attempted value was valid or not. If it was invalid, the helper automatically adds an extra CSS class so that you can highlight the invalid field for the user.

For example, after a blank form submission, <%: Html.TextBoxFor(x => x.Email) %> will produce the following HTML:

<input class="input-validation-error" id="Email" name="Email" type="text" value=""/>

The easiest way to highlight invalid fields is to reference a CSS style sheet, /Content/site.css, that's included by default in all new ASP.NET MVC 2 projects. Go to your RsvpForm.aspx view and add a new stylesheet reference to its <head> section:

<head runat="server">
    <title>RsvpForm</title>
    <link rel="Stylesheet" href="~/Content/Site.css" />
</head>

Now, all input controls with the CSS class input-validation-error will be highlighted, as shown in Figure 2-16.

Retaining state and highlighting invalid fields after validation error

Figure 2-16. Retaining state and highlighting invalid fields after validation error

Finishing Off

The final requirement is to e-mail completed RSVPs to the party organizer. You could do this directly from an action method, but it's more logical to put this behavior into the model. After all, there could be other UIs that work with this same model and want to submit GuestResponse objects.

To construct the outgoing e-mail, start by adding the following method to GuestResponse:[11]

private MailMessage BuildMailMessage()
{
    var message = new StringBuilder();
    message.AppendFormat("Date: {0:yyyy-MM-dd hh:mm}
", DateTime.Now);
    message.AppendFormat("RSVP from: {0}
", Name);
    message.AppendFormat("Email: {0}
", Email);
    message.AppendFormat("Phone: {0}
", Phone);
    message.AppendFormat("Can come: {0}
", WillAttend.Value ? "Yes" : "No");
    return new MailMessage(
        "[email protected]",                                            // From
        "[email protected]",                                 // To
        Name + (WillAttend.Value ? " will attend" : " won't attend"), // Subject
        message.ToString()                                            // Body
    );
}

Next, add a further method that uses BuildMailMessage() to actually deliver the e-mail to the site administrator. If you're running Visual Studio 2010 and .NET 4, add the following to the GuestResponse class:

public void Submit() // .NET 4 version
{
    using (var smtpClient = new SmtpClient())
    using (var mailMessage = BuildMailMessage()) {
        smtpClient.Send(mailMessage);
    }
}

However, if you're running Visual Studio 2008 or targeting .NET 3.5, you'll find that neither MailMessage nor SmtpClient is a disposable type (which means you can't put it in a using block to release its resources immediately), so you should instead express the method more simply, as follows:

public void Submit() // .NET 3.5 version
{
    new SmtpClient().Send(BuildMailMessage());
}

Finally, call guestResponse.Submit() from the second RsvpForm() overload, thereby sending the guest response by e-mail only if it's valid:

[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse)
{
    if (ModelState.IsValid)
    {
        guestResponse.Submit();
        return View("Thanks", guestResponse);
    }
    else // Validation error, so redisplay data entry form
        return View();
}

Note

It's out of scope for this simple example, but the Party Invites application could be improved by moving the e-mail-sending logic into a separate service class, and using dependency injection (DI) to inject the service into other classes that depend on it. The next chapter will consider these architectural concerns in more detail, and you'll learn about service classes and DI. In Chapter 4 you'll put those concepts into practice, building a bigger application with a modern, more flexible architecture.

Of course, it's more common to store model data in a database than to send it by e-mail. The major example in Chapter 4 will demonstrate one possible way to use ASP.NET MVC with SQL Server.

Summary

You've now seen how to build a simple data entry application using ASP.NET MVC 2, getting a first glimpse of how MVC architecture works. The example so far hasn't shown the power of the MVC framework (e.g., we skipped over routing, and there's been no sign of automated testing as yet). In the next two chapters, you'll drill deeper into what makes a good, modern MVC web application, and you'll build a full-fledged e-commerce site that shows off much more of the platform.



[6] You can also use MonoDevelop, an open source IDE that also works on Linux and Mac, and strictly speaking you can even just use a plain text editor.

[7] Actually, you can build ASP.NET MVC applications using any .NET language (e.g., Visual Basic, IronPython, or IronRuby). But since C# is the focus of this book, from now on I'll just say "C#" in place of "all .NET languages."

[8] If instead you get Visual Studio's WYSIWYG designer, switch to Source view by clicking Source near the bottom of the screen, or by pressing Shift+F7.

[9] This book isn't about CSS or web design, so we'll stick with the retro chic Class of 1996 theme for most examples. ASP.NET MVC values pure, clean HTML, and gives you total control over your element IDs and layouts, so you'll have no problems using any off-the-shelf web design template or fancy JavaScript effects library.

[10] Again, if you're running Visual Studio 2008/.NET 3.5, you need to adapt the view syntax as described earlier. This really is your last reminder in this chapter.

[11] You'll need to add using System;, using System.Net.Mail;, and using System.Text; too (e.g., by using the Ctrl+dot technique again). If you're prompted to choose between System.Net.Mail and System.Web.Mail, be sure to choose System.Net.Mail—the other type is obsolete.

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

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