Chapter 9
Creating Web Applications with ASP.NET MVC

Key Skills & Concepts

Image Learn What MVC Means

Image Create Models

Image Create Controllers

Image Create Views

Image Work with Data in ASP.NET MVC

ASP.NET is a .NET technology for building Web applications. VS provides support for building a Web application through windows such as the Toolbox, Designer, and Properties windows, as well as the Solution Explorer. This chapter shows you how to use ASP.NET MVC. MVC is an acronym for Model View Controller, which is a well-known design pattern for building applications. You’ll learn about how MVC works and how it is implemented in ASP.NET MVC. Let’s start by helping you understand what MVC is.

Understanding ASP.NET MVC

The essential piece of knowledge required to be successful with ASP.NET MVC is the Model View Controller pattern. In MVC, the Model, View, and Controller are three separate objects. Table 9-1 describes the purpose of each MVC object.

With MVC, you have a clear separation of concerns where Model, View, and Controller have distinct responsibilities. This makes it easier to write good programs that you can return to later for fixing bugs and adding new features. Besides knowing what each of these three objects is, you must understand their relationship. Figure 9-1 illustrates the Model, the

Image

Table 9-1 Purpose of MVC Objects

Image

Figure 9-1 The Model View Controller pattern

View, and the Controller, including relationships. There are variations of the relationship between Model, View, and Controller, so rather than a theoretically correct depiction of all scenarios, Figure 9-1 is a simplification that should help you get started.

In Figure 9-1, you can see that the Controller references both the View and the Model. This makes sense when you consider that the Controller is managing the operation of the application. The Controller executes in response to a user request. Since the Controller is also responsible for coordinating activity between the Model and the View, you can see the relationship in Figure 9-1 where the Controller references the Model. The View references the Model because the View must bind data to the user interface and needs to know what data is available. The Model does not reference the View or the Controller. The Model is an object that holds data and any other members that help manage that data, such as methods for performing validation.

A typical sequence of operations for an ASP.NET MVC operation starts with a request to a Controller. The Controller will perform the actions requested, working with the Model. The Controller will then give the Model to a View and run the View. The View will display Model data and interact with the user for any screen operations. Based on user interaction with the View, more requests will be made to a Controller to repeat this process. The rest of this chapter shows you how to write the code to make this process work, starting with creating a new ASP.NET MVC project.

Starting an ASP.NET MVC Project

Just as with any other project in VS, you open the New Project window by selecting File | New | Project. Then create an ASP.NET MVC 2 Web Application project named MyShopCS (MyShopVB for VB). VS will ask if you want to create a test project, and you have the option to choose Yes or No. Choosing Yes will add a unit testing project to the solution. You can choose either option, which won’t matter right now because we’ll not be covering this topic here, but it is definitely worth exploring on your own. Figure 9-2 shows the new project in Solution Explorer.

VS created several folders with working code:

Image The Model, View, and Controller folders hold code for the MVC Models, Views, and Controllers, respectively.

Image Previous chapters already explained the purpose of the Properties and References folders.

Image The App_Data folder is designed to allow you to ship a local database with your application and is ideal for small programs where you can use the free SQL Express database. See the accompanying note to learn how to add a database in the App_Data folder.

Image The Content folder is where you add any Cascading Style Sheets (CSS) files. CSS is a standardized language for defining layout and appearance of a Web site.

Image

Figure 9-2 A new ASP.NET MVC project

Image The Scripts folder holds JavaScript files, which include the jQuery and ASP.NET AJAX client libraries. JavaScript helps make Views more interactive and can be effective in providing a pleasing user experience.

Image The Global.asax file holds code that runs at different periods during the application life cycle; we’ll investigate this file when looking at routing later in this chapter.

Image The web.config file holds configuration information, such as database connection strings and more items that you don’t want to hard-code into the application.

NOTE

If you want to ship a local database with your application, you can right-click the App_ Data folder for your project, select Add | New Item, navigate to Data, and select SQL Server Database. This will add a blank database *.mdf file under the App_Data folder. You can work with this database through Server Explorer, using techniques learned in Chapter 7, to add tables and other objects. Remember that the server you deploy to must have SQL Server Express installed or your database operations won’t work.

The code generated by the New Project Wizard will run, and pressing F5 to execute the application will show you a screen similar to Figure 9-3. Click OK when you see a screen that asks if you want to program to run in debug mode. This will modify the web.config file to allow debugging, which is what you want while developing applications.

Image

Figure 9-3 Running the default code produced by an ASP.NET MVC project

The skeleton code produced by VS gives you some working examples that you can build on and move forward. One item that VS doesn’t produce is the Model, which is discussed next.

Creating the Models

As stated previously, the Model represents the data for the application. The example in this section uses LINQ to SQL to produce the Model for this application. To create the Model, add a LINQ to SQL entity Model by right-clicking the Models folder, selecting Add | New Item, and selecting LINQ to SQL. This creates a *.dbml file that you should add Customer and Order entities to, using the same techniques described in Chapter 7.

In more sophisticated scenarios, you would have additional objects that held business logic or other data that isn’t associated with LINQ to SQL. This book keeps tasks at a basic level so that you can understand how to use VS. You can put Model objects in the Models folder or a separate class library. This chapter uses the Models folder.

Building Controllers

Requests come directly to a Controller, which we discussed earlier. As shown in Figure 9-2, the MVC project has a Controllers folder. Controller classes normally reside in the Controllers folder. Figure 9-2 shows two files, AccountController.cs and HomeController.cs, in the Controllers folder. Listing 9-1 shows the contents of the HomeController.cs file.

Listing 9-1 The HomeController class

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MyShopCS.Controllers
{
   [HandleError]
   public class HomeController : Controller
   {
       public ActionResult Index()
       {
          ViewData["Message"] = "Welcome to ASP.NET MVC!";
          return View();
       }

       public ActionResult About()
       {
          return View();
       }
   }
}

VB:

<HandleError()> _
Public Class HomeController
    Inherits System.Web.Mvc.Controller

    Function Index() As ActionResult
       ViewData("Message") = "Welcome to ASP.NET MVC!"

       Return View()
    End Function

    Function About() As ActionResult
       Return View()
    End Function
End Class

Listing 9-1 demonstrates how closely ASP.NET MVC is tied to conventions. Notice that the class name is HomeController. Appending Controller to a class name is a convention that ASP.NET MVC uses to identify which classes are controllers. Also, the methods in the class are referred to as Actions in ASP.NET MVC. Using the Controllers folder for a Controller, appending the class name with Controller, and available actions are all conventions that you must follow. The following URL, a browser address, demonstrates how these conventions support routing to find a Controller and invoke the About action. You can see this URL if you run the application and click the About tab:

http://localhost:1042/Home/About

The http://localhost:1042 part of the URL is a Web server that is built into VS and runs the Web application without needing a Web server such as Internet Information Server (IIS). The number 1042 is a random port number generated by the Web server, and your port number is likely to be different.

TIP

You can change your VS Web server’s port number. If you open your project’s property page by right-mouse clicking on the project in Solution Explorer and select Properties, then select the Web tab on the left, under Servers, you can specify a specific port or make other Web server choices.

For ASP.NET MVC, the important part of the URL is /Home/About. Home is the name of the Controller, and ASP.NET MVC appends Controller to the URL name, looking for the HomeController class, shown in Listing 9-1, physically located in the Controller folder, which is why it’s important to ensure you create files in the proper locations. About is an action, which corresponds to the About method shown in Listing 9-1. Similar to the About method, the Index action is run through the following URL:

http://localhost:1042/Home/Index

In a later section of this chapter, you’ll learn how ASP.NET MVC performs routing, which maps URLs to Controllers.

Both the Index and About actions in Listing 9-1 invoke a method named View. This is a convention for invoking a View with the same name as the action method. For example, calling View in the Index action will show a View named Index, and the call to View in the About method will show a View named About.

One more item to point out is how the Index action assigns a string to a collection called ViewData. The ViewData collection is one way for a Controller to pass Model data to a View. I’ll cover more on Controllers, including how to create your own, in a later part of this chapter, but now, let’s do a quick review of Views so that you can see what happens when they are invoked by the Controller.

Displaying Views

A View is what displays in the browser and allows interaction with the user. The View can display any information that a Controller passes to it. For example, notice that the Index action in Listing 9-1 assigns a string “Welcome to ASP.NET MVC!” with the “Message” key in the ViewData collection.

Looking Inside a View

Figure 9-3 shows the View in the browser, displaying the message. Listing 9-2 shows the Hypertext Markup Language (HTML) of the View displaying the message. The View actually has a combination of HTML and ASP.NET markup, sometimes referred to as ASPX, but I’ll refer to it as just HTML for the rest of the chapter.

Listing 9-2 A View’s HTML

<%@ Page Language="C#"
         MasterPageFile="~/Views/Shared/Site.Master"
         Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="indexTitle"
             ContentPlaceHolderID="TitleContent"
             runat="server">
    Home Page
</asp:Content>

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

A quick overview of Listing 9-2 shows that there is a Page directive with a couple of Content containers. The Page directive specifies a MasterPage and Inherits attributes. A MasterPage is a separate file that holds common HTML that can be shown on all pages of a site. You’ll see how the MasterPage works soon, but let’s stay focused on the current file in Listing 9-2 until then. ASP.NET MVC will compile this HTML into code behind the scenes, and the generated code will derive from the class defined by the Inherits attribute.

The first Content container can hold metadata that goes into an HTML header. The second Content container has the information that will display on the screen. Notice the Html.Encode(ViewData[“Message”]) inside of binding tags <%= and %>. Any time you add code or need to access ViewData that was passed by the Controller, you will use the binding tags. Encode is one of several helper methods of the Html class, more of which you’ll see soon. The purpose of Encode is to translate HTML tags into their encoded representations for security purposes, ensuring that you don’t show any harmful JavaScript, or other markup that could possibly execute, to the user. ViewData[“Message”] should be familiar, as it was set in the Index action in Listing 9-2 but is now being read and displayed on the screen by this View.

Organizing View Files

The file structure in Figure 9-2 shows that Views appear in the Views folder and have a *.aspx file extension. Each subfolder under the Views folder corresponds to a Controller, and the Views within the subfolder correspond generally to Controller actions. When a Controller passes control to a View, by calling View, ASP.NET MVC searches for the View in the Views folder with the subfolder named the same as the Controller and the file named the same as the action calling the View.

Notice that there is a Shared folder. Sometimes, you’ll want to have a View that is shared by two or more Controller actions, and you can put these shared Views in the Shared subfolder. Whenever ASP.NET MVC doesn’t find a View in the Controller-named subfolder, it will search for the View in the Shared folder. An important file in the Shared subfolder is the MasterPage, which is discussed next.

Assigning MasterPage Files

Most sites on the Web have multiple pages, each with common elements. They all have the same header, menu, sidebars, and footers. When you first build a site, you can duplicate this common content with no trouble, but this copy-and-paste type duplication will cause a lot of headaches in the future. The first time you have to change the common elements, you’ll need to visit every page. If the site has only a few pages, no problem, but the reality is that most sites of any success grow to dozens or hundreds of pages. It is beyond practical to try to update every page on a site every time the common content changes.

This is where MasterPages help, allowing you to specify the common content in one place where you can have content pages that use the MasterPage. Whenever something changes in the common content, you update the MasterPage, and every page of a site that uses the MasterPage is automatically updated. Listing 9-3 shows the MasterPage, created by ASP.NET MVC, that the content page in Listing 9-2 uses.

Listing 9-3 A MasterPage

<%@ Master Language="C#"
           Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>
        <asp:ContentPlaceHolder ID="TitleContent"
                                runat="server" />
    </title>
    <link href="../../Content/Site.css"
          rel="stylesheet" type="text/css" />
</head>

<body>
    <div class="page">

      <div id="header">
          <div id="title">
             <h1>My MVC Application</h1>
          </div>

          <div id="logindisplay">
<% Html.RenderPartial("LogOnUserControl"); %>
           </div>

           <div id="menucontainer">

               <ul id="menu">
                   <li>
<%= Html.ActionLink("Home", "Index", "Home")%>
                    </li>
                    <li>
<%= Html.ActionLink("About", "About", "Home")%>
                    </li>
               </ul>
           </div>
      </div>

      <div id="main">
        <asp:ContentPlaceHolder ID="MainContent" runat="server" />

        <div id="footer">
        </div>
      </div>
    </div>
</body>
</html>

Moving from the top of Listing 9-3 down, you can see the MasterPage directive at the top of the page, which states that this is a MasterPage and ASP.NET MVC will handle the page appropriately. The DTD is a tag that specifies what Web standards this page supports, which is read by browsers to help them determine the best way to display the page.

The rest of the page is framed inside of HTML tags and ASP.NET MVC markup. The html tag states that this is an HTML document. HTML documents have two parts, a head and a body, where the head is for metadata describing the page and the body contains display content.

In HTML, a div tag blocks off a chunk of HTML and is useful for layout and organization of the page. The Hx tags, where x is a number between 1 and 6, describe headers, where hi is the largest and h6 is the smallest.

The ContentPlaceHolder controls are instrumental to the success of the MasterPage. If you look at the Content tags in Listing 9-2, you’ll see that they have a ContentPlaceHolderID that matches the ID attributes of the ContentPlaceHolder controls in Listing 9-3. What this means is that when the View renders, the MasterPage will display and ASP.NET MVC will inject the Content regions of the content pages into the matching ContentPlaceHolders of the MasterPage. ASP.NET MVC knows which MasterPage to use because the Page directive, as shown in Listing 9-2, specifies the MasterPage attribute.

If you recall from the last section, Listing 9-2 had a binding expression for the Html Encode helper method. The MasterPage in Listing 9-3 introduces a couple more Html helper methods, RenderPartial and ActionLink.

The ActionLink method has three parameters: id, controller, and action. When the ActionLink renders in the browser, it will transform into an anchor tag, a, with an id specified in the first parameter of ActionLink. When the user clicks the link in the browser, the application will navigate to the Controller in the third parameter of ActionLink and invoke the action in the second parameter of ActionLink. So, if the user clicked the link produced by ActionLink(“About”, “About”, “Home”), ASP.NET MVC will invoke the About action of the Home Controller. The next section discusses RenderPartial in more detail.

Partial Views (a.k.a. User Controls)

It’s often the case that you’ve written View content on one page and need the same identical content on two or more pages. As explained with MasterPages, you want to avoid the maintenance work that comes with updating all of the content that is the same on multiple pages. While MasterPages are good for content that decorates pages across an entire site, a Partial View is ideal for limited reuse of View content on different pages of a site.

A good example of where a Partial View is useful is illustrated in the code produced by the ASP.NET MVC Project Wizard, where it created the LogonUserControl.ascx. The terms Partial View and User Control are synonymous, where the term User Control is familiar to developers who have worked with previous versions of ASP.NET Web Forms. Partial View is consistent with the ASP.NET MVC perspective of Views, where a Partial View is not an entire View, but a chunk of View content that can be reused with multiple Views. It isn’t coincidence that this control is physically located in the Views Shared folder, considering that it can be used on multiple pages. Remember, if ASP.NET MVC can’t find a file in a View folder named after a Controller, it will look in the Shared folder. Listing 9-4 shows the contents of LogonUserControl.ascx.

Listing 9-4 Contents of a Partial View

<%@ Control Language="C#"
            Inherits="System.Web.Mvc.ViewUserControl" %>
<%
    if (Request.IsAuthenticated) {
%>
Welcome <b><%= Html.Encode(Page.User.Identity.Name) %></b>!
  [ <%= Html.ActionLink("Log Off", "LogOff", "Account") %> ]
<%
    }
    else {
%>
[ <%= Html.ActionLink("Log On", "LogOn", "Account") %> ]
<%
    }
%>

The Control directive at the top of Listing 9-4 indicates that this is a Partial View. Within the control, you can see an if statement, where the language syntax is surrounded by <% and %> binding symbols. The additional syntax to separate code from markup might take a little getting used to, but it is typical in an MVC application to control how markup is rendered. The IsAuthenticated property of the Request object tells whether the current user is logged in, and the logic ensures the appropriate message displays. The ActionLink Html helper methods generate action tags with a URL for actions on the Account Controller. We’ve barely touched on routing and how a URL matches controllers and actions, but the next section explains how routes work in greater depth.

Managing Routing

ASP.NET MVC has a routing system that matches URLs to controllers with actions and the parameters passed to those actions. When you start a new ASP.NET MVC project, default routing will be established via a file called Global.asax, which is where many events affecting the application are placed. When you run an ASP.NET MVC application, it will use URLs of the form http://domain/controller/action/param1/param2/.../paramN? optionalArg=optionalVal. Here’s an example:

http://localhost:1042/Home/About

In this example, localhost:1042 is the domain, Home is the Controller, and About is the action. When ASP.NET MVC sees this URL, it will instantiate the HomeController class and call the About method.

The Global.asax file has an Application_Start event that is called the first time the application runs. This is where routing is set up so that it will be in place for all of the requests while the application is running. Listing 9-5 shows the default routing for an ASP .NET MVC project.

Listing 9-5 Setting up routing

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace MyShopCS
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode,
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
               "Default",                    // Route name
               "{controller}/{action}/{id}",         // URL with parameters
               new {
                   controller = "Home",
                   action = "Index",
                   id = "" } // Parameter defaults
            );
        }
        protected void Application_Start()
        {
            RegisterRoutes(RouteTable.Routes);
        }
     }
}

VB:

' Note: For instructions on enabling IIS6 or IIS7 classic mode,
' visit http://go.microsoft.com/?LinkId=9394802

Public Class MvcApplication
       Inherits System.Web.HttpApplication

       Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
          routes.IgnoreRoute("{resource}.axd/{*pathInfo}")

          ' MapRoute takes the following parameters, in order:
          ' (1) Route name
          ' (2) URL with parameters
          ' (3) Parameter defaults
          routes.MapRoute(_
             "Default", _
             "{controller}/{action}/{id}", _
             New With
             {
                 .controller = "Home", .action = "Index", .id = ""
             }
          )

       End Sub

       Sub Application_Start()
           AreaRegistration.RegisterAllAreas()

           RegisterRoutes(RouteTable.Routes)
       End Sub
End Class

Listing 9-5 shows that the Application_Start event invokes a method named RegisterRoutes, passing the Routes property of the RouteTable class. The Routes property is a static RouteCollection, meaning that there is only one copy for the entire application, and it will hold multiple routes. When the application starts, this collection will be empty and the RegisterRoutes method will populate the collection with routes for this application.

Routing works by pattern matching, which you can see through the two statements in the RegisterRoutes method: IgnoreRoute and MapRoute. IgnoreRoute is useful for situations where you want to let IIS request the exact URL. In this case, it is any file with the *.axd extension, regardless of parameters.

The MapRoute method shows a common pattern for matching URLs to controllers, actions, and parameters. The first parameter is the name of the route. The second parameter describes the pattern, where each pattern match is defined between curly braces. Based on the URL, http://localhost:1042/Home/About, the pattern, {controller}/{action}/{id}, matches Home to {controller} and About to {action}; there is no match for {id}. Therefore, ASP.NET MVC will append “Controller” to the URL segment that matches {controller}, meaning that the Controller name to instantiate is HomeController. About is the method inside of HomeController to invoke. Since About doesn’t have parameters, supplying the {id} is unnecessary.

The third parameter for MapRoute specifies default values, where the key matches the pattern parameter and the value assigned to the key is what ASP.NET MVC uses when it doesn’t find a pattern match with the URL. Here are a couple of examples:

Image http://localhost: 1042 invokes the Index method of HomeController because no Controller or action matches and the defaults are Home for {controller} and Index for {action}.

Image http://localhost:1042/Home invokes the Index method of HomeController because no action was specified and the default value for {action} is Index.

You can create your own custom route by using the MapRoute method and specifying other default values for the parameters.

Building a Customer Management Application

Now, we’ll pull together the ASP.NET MVC concepts you’ve learned and describe how to build a very simple application that displays, adds, modifies, and deletes customers. In so doing, you’ll see how to build up a Model that supports customers, how to create a custom Controller with actions for managing customers, and how to create multiple views to handle interaction with the users as they work with customers.

Creating a Repository

A common pattern for working with data is to build a repository that is responsible for all data-related operations. This is another way to promote separation of concerns so that you isolate logic into specific parts of an application, resulting in easier code to work with. A repository is a class that performs create, read, update, and delete (CRUD) operations on a specific data type. Listing 9-6 shows a repository for working with customer objects. You can create this class by right-clicking the Models folder and selecting Add | Class, and name the class CustomerRepository. The code also assumes that you’ve created a LINQ to SQL *.dbml, named MyShop, with a Customer entity for the Customers table in MyShop, which is the database created in Chapter 7.

Listing 9-6 A repository for working with customer data

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MyShopCS.Models
{
    public class CustomerRepository
    {
        private MyShopDataContext m_ctx
          = new MyShopDataContext();

        public int InsertCustomer(Customer cust)
        {
           m_ctx.Customers.InsertOnSubmit(cust);
           m_ctx.SubmitChanges();
           return cust.CustomerID;
        }

        public void UpdateCustomer(Customer cust)
        {
           var currentCust =
               (from currCust in m_ctx.Customers
               where currCust.CustomerID == cust.CustomerID
               select currCust)
               .SingleOrDefault();

           if (currentCust != null)
           {
               currentCust.Age = cust.Age;
               currentCust.Birthday = cust.Birthday;
               currentCust.Income = cust.Income;
               currentCust.Name = cust.Name;
           }

           m_ctx.SubmitChanges();
        }

        public Customer GetCustomer(int custID)
        {
           return
               (from cust in m_ctx.Customers
                where cust.CustomerID == custID
                select cust)
                .SingleOrDefault();
        }

        public List<Customer> GetCustomers()
        {
           return
                (from cust in m_ctx.Customers
                select cust)
                .ToList();
        }

        public void DeleteCustomer(int custID)
        {
           var customer =
                (from cust in m_ctx.Customers
                where cust.CustomerID == custID
                select cust)
                .SingleOrDefault();

           m_ctx.Customers.DeleteOnSubmit(customer);
           m_ctx.SubmitChanges();
        }
    }
}

VB:

Public Class CustomerRepository

      Private m_ctx As New MyShopDataContext
      Public Function InsertCustomer(
          ByVal cust As Customer) As Integer

          m_ctx.Customers.InsertOnSubmit(cust)
          m_ctx.SubmitChanges()
          Return cust.CustomerID

        End Function

        Public Sub UpdateCustomer(ByVal cust As Customer)

          Dim currentCust =
              (From currCust In m_ctx.Customers
              Where currCust.CustomerID = cust.CustomerID
              Select currCust).SingleOrDefault()


          If Not currentCust Is Nothing Then

             With currentCust
                .Age = cust.Age
                .Birthday = cust.Birthday
                .Income = cust.Income
                .Name = cust.Name
             End With

             m_ctx.SubmitChanges()

          End If

        End Sub

        Public Function GetCustomer(ByVal custID As Integer) As Customer

           Dim customer =
               (From cust In m_ctx.Customers
               Where cust.CustomerID = custID
               Select cust).SingleOrDefault()

           Return customer

        End Function

        Public Function GetCustomers() As List(Of Customer)

            Dim customers =
                (From cust In m_ctx.Customers
                Select cust).ToList()

            Return customers

        End Function

        Public Sub DeleteCustomer(ByVal custID As Integer)

            Dim customer =
                (From cust In m_ctx.Customers
                Where cust.CustomerID = custID
                Select cust).SingleOrDefault()

            m_ctx.Customers.DeleteOnSubmit(customer)
            m_ctx.SubmitChanges()

        End Sub
End Class

You can have more methods in a repository for doing whatever is required with data for the application, but the items in Listing 9-6 are typical. The LINQ to SQL operations are consistent with the material covered in Chapter 7, so there’s no need to repeat the same material here. The purpose of the repository is to give the Controller an object to work with for getting data without filling up Controller methods with data access logic. Let’s see how the Controller works with this repository next.

Creating a Customer Controller

Right-click the Controllers folder, select Add | Controller, or press CTRL-M, press CTRL-C, and name the file CustomerController. Check the box for “Add action methods for Create, Update, and Details scenarios” as shown in Figure 9-4.

Image

Figure 9-4 Creating a new Controller

This will create a new Controller with several methods for working with Customer data. Listing 9-1 already showed what a Controller looks like, and this is no different, except that it contains more action methods. The following sections explain how to perform various operations on customer data.

Displaying a Customer List

The first thing to do with customers is to display a list that will serve as a starting point for other operations. Listing 9-7 shows the Index action method of the CustomerController and how it gets a list of customers to display. The code uses the CustomerRepository, created in the preceding section. For C#, you need to add a using directive at the top of the file for the MyShopCS.Models namespace.

Listing 9-7 A Controller for displaying a list

C#:

public ActionResult Index()
{
    var customers =
        new CustomerRepository()
        .GetCustomers();

    return View(customers);
}

VB:

Function Index() As ActionResult

    Dim custRep As New CustomerRepository
    Dim customers As List(Of Customer)

    customers = custRep.GetCustomers()
    Return View(customers)
End Function

Listing 9-7 shows how the Index method uses the CustomerRepository to get the list of customers. You need to pass that list to the View for display.

To create the View, right-click anywhere in the Index method and select Add View, which will display the Add View window, shown in Figure 9-5.

The name of the View is Index, corresponding to the name of the action method invoking the View. Naming the View after the action method is the default behavior, but

Image

Figure 9-5 The Add View window

you can name the View anything you want. If the View you need to display is named differently than the action method, you can use the following View method overload:

View("SomeOtherViewName", customers);

We want to use a strongly typed View, meaning that you will have IDE support for referencing the properties of your own object when working in the View. The selected object is Customer, which is already defined as a LINQ to SQL entity, which is the same type returned by the call to the GetCustomers method in CustomerRepository.

The purpose of this View is to display a list of customers, so we’ll select List as View content. This will prepopulate the View with a template for displaying customers. You’ll be able to modify the screen as you like. Additionally, if you prefer to write your own code to populate the screen, you can select the Empty option for View content and then code the View manually yourself. Selecting List is a quick way to get started.

You learned about MasterPages earlier in this chapter, and you have the option of selecting a MasterPage of your choice and specifying which ContentPlaceHolder your code will render in.

Click Add to generate the View shown in Listing 9-8.

Listing 9-8 A Customer List View

<%@ Page Title="" Language="C#"
         MasterPageFile="~/Views/Shared/Site.Master"
         Inherits="System.Web.Mvc
.ViewPage<IEnumerable<MyShopCS.Models.Customer>>" %>

<asp:Content ID="Content1"
             ContentPlaceHolderID="TitleContent"
             runat="server">

    Index
</asp:Content>

<asp:Content ID="Content2"
             ContentPlaceHolderID="MainContent"
             runat="server">

    <h2>Index</h2>

    <table>
       <tr>

           <th></th>
           <th>
               CustomerID
           </th>
           <th>
               Name
           </th>
           <th>
               Age
           </th>
           <th>
               Birthday
           </th>
           <th>
               Income
           </th>
       </tr>

    <% foreach (var item in Model) { %>

       <tr>
           <td>
               <%= Html.ActionLink("Edit", "Edit",
                   new { id=item.CustomerID }) %> |
               <%= Html.ActionLink("Details", "Details",
                   new { id=item.CustomerID })%>
           </td>
           <td>
               <% = Html.Encode(item.CustomerID) %>
           </td>
           <td>
               <%= Html.Encode(item.Name) %>
           </td>
           <td>
               <%= Html.Encode(item.Age) %>
           </td>
           <td>
               <%= Html.Encode(String.Format("{0:g}",
                   item.Birthday)) %>
           </td>
           <td>
               <%= Html.Encode(String.Format("{0:F}",
                   item.Income)) %>
           </td>
        </tr>

  <% } %>

  </table>

  <p>
     <%= Html.ActionLink("Create New", "Create") %>
  </p>

</asp:Content>

Listing 9-8 organizes the list of Customers in a table. The tr tags are rows, th are header cells, and td are content cells. After specifying the header row, the foreach loop iterates on the Model to render each content row. If you recall from Listing 9-7, the Index action method called View with a List<Customer> (List(Of Customer) in VB). When creating the View, we specified the object type as Customer, which means that the reference to Model in the foreach statement is to List<Customer> and item contains a Customer object.

For each cell being rendered, item is the current Customer and the property for that cell is referenced by the property of Customer that should display. What is particularly important about displaying the data is that each cell uses the Html.Encode helper method instead of displaying the data directly. This is a best practice for best security to ensure that any data displayed is not treated as HTML markup or accidentally runs JavaScript that you didn’t intend. You see, a malicious hacker could add JavaScript during data entry and when you display that field, the browser would try to run the JavaScript code, which would be bad. Using Html.Encode prevents this from happening. The other Html helper methods, such as ActionLink, already encode output, so you should use Html.Encode whenever one of the other helpers isn’t used. Notice that the code for the foreach loop is enclosed in <% and %> symbols so that it is treated as code and not markup.

Next, you’ll want to be able to navigate to the Customer List page from the main menu, so open your MasterPage, Site.Master, and add the Customers ActionLink like this:

<ul id="menu">
    <li><%= Html.ActionLink("Customers", "Index", "Customer")%></li>
    <li><%= Html.ActionLink("Home", "Index", "Home")%></li>
    <li><%= Html.ActionLink("About", "About", "Home")%></li>
</ul>

The parameters to the new ActionLink, from left to right, indicate that the text for the anchor will be Customers, and ASP.NET will invoke the Index action method on the CustomerController class when the user clicks the link. Figure 9-6 shows what the Customer list looks like when the program runs.

Image

Figure 9-6 Showing a list of objects

As shown in Figure 9-6, the Customer tab appears first on the list, and clicking it shows the list of Customers. In addition to the content you see in the list, there are links, such as Edit and Create. The next section covers the Create operation.

Adding a New Customer

Creating a new customer involves presenting a screen for data entry and saving the new data when submitted. When creating a new object, your Controller needs two methods, a get method to initialize an empty Customer and a post method to save the new customer data. Listing 9-9 shows the get and post methods in the CustomerController class.

Listing 9-9 Creating a new Customer object

C#:

//
// GET: /Customer/Create

public ActionResult Create()
{
    Customer cust = new Customer
    {
        Birthday = new DateTime(1980, 1, 1)
    };

    return View(cust);
}

//
// POST: /Customer/Create

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Customer cust)
{
    try
    {
        if (string.IsNullOrEmpty(cust.Name))
        {
            ModelState.AddModelError(
            "Name", "Name is required.");
            return View();
        }
      new CustomerRepository()
          .InsertCustomer(cust);

      return RedirectToAction("Index");
    }
    catch
    {
      return View();
    }
}

VB:

'
' GET: /Customer/Create

Function Create() As ActionResult
   Dim cust As New Customer With
   {
       .Birthday = New DateTime(1980, 1, 1)
   }
   Return View(cust)
End Function

'
' POST: /Customer/Create

<HttpPost()> _
Function Create(ByVal cust As Customer) As ActionResult
   Try
       If String.IsNullOrEmpty(cust.Name) Then
          ModelState.AddModelError(
              "Name", "Name is required.")
       End If

       Dim custRep As New CustomerRepository
       custRep.InsertCustomer(cust)

       Return RedirectToAction("Index")

   Catch
       Return View()
   End Try
End Function

In the HTTP protocol, there are different types of verbs for the operation being conducted. Listing 9-9 demonstrates two of these verbs, get and post. A get is typically associated with reading data, and a post is typically associated with writing data. Listing 9-9 shows both get and post methods in the Create method overloads. In ASP.NET MVC, action methods default to get requests and you must use an HttpVerbs attribute to specify a post.

The get Create action method instantiates a new Customer object and passes it to the View. When the user fills in the form and submits, the post Create action method will execute and insert the new record into the database.

Notice how I changed the Create method parameter from FormsCollection to Customer. ASP.NET MVC will automatically read the form values and match those values up with matching properties in the object passed to the method. The method also checks to ensure that the name is filled in and adds an error to the ModelState. Whenever an error occurs, you need to return to the same View to ensure the user sees the error and can correct and resubmit. ASP.NET MVC will use this error to display error messages in the View. To create the View, right-click either Create method, select Add View, and fill in the values as shown in Figure 9-7.

Image

Figure 9-7 Adding a new Customer

The Add View screen in Figure 9-7 specifies strong typing on the Customer class, but this time it selects Create as the View Content. Listing 9-10 shows the resulting View.

Listing 9-10 View for creating a new Customer

<%@ Page Title="" Language="C#"
    MasterPageFile="~/Views/Shared/Site.Master"
    Inherits="System.Web.Mvc.ViewPage<MyShopCS.Customer>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent"
    runat="server">
    Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent"
    runat="server">

    <h2>Create</h2>

    <% using (Html.BeginForm()) {%>

       <fieldset>
          <legend>Fields</legend>

          <div class="editor-label">
             <%= Html.LabelFor(model => model.CustomerID) %>
          </div>
          <div class="editor-field">
             <%= Html.TextBoxFor(model => model.CustomerID) %>
             <%= Html.ValidationMessageFor(
                 model => model.CustomerID) %>
          </div>

          <div class="editor-label">
             <%= Html.LabelFor(model => model.Name) %>
          </div>
          <div class="editor-field">
             <%= Html.TextBoxFor(model => model.Name) %>
             <%= Html.ValidationMessageFor(model =>
                model.Name) %>
          </div>

          <div class="editor-label">
             <%= Html.LabelFor(model => model.Age) %>
          </div>
          <div class="editor-field">
             <%= Html.TextBoxFor(model => model.Age) %>
             <%= Html.ValidationMessageFor(model =>
                 model.Age) %>
          </div>

          <div class="editor-label">
              <%= Html.LabelFor(model => model.Birthday) %>
          </div>
          <div class="editor-field">
             <%= Html.TextBoxFor(model => model.Birthday) %>
             <%= Html.ValidationMessageFor(
                 model => model.Birthday) %>
          </div>

          <div class="editor-label">
             <%= Html.LabelFor(model => model.Income) %>
          </div>
          <div class="editor-field">
             <%= Html.TextBoxFor(model => model.Income) %>
             <%= Html.ValidationMessageFor(
                 model => model.Income) %>
          </div>

          <p>
             <input type="submit" value="Create" />
          </p>
       </fieldset>

   <% } %>

   <div>
        <%=Html.ActionLink("Back to List", "Index") %>
   </div>

</asp:Content>

The ValidationMessageFor Html helper displays any errors that occur on this page. The error messages are displayed whenever the Controller action method adds the error to the ModelState. When the user clicks the Submit button, this page will post back to the Create method with the AcceptVerbs attribute for post. Figure 9-8 shows the Create screen when running.

In addition to creating a new Customer, you can edit existing Customers, as is discussed next.

Image

Figure 9-8 The Create screen

Updating Existing Customers

Similar to how we created Customers, you also need two methods for editing a Customer. The get method populates an edit form with existing data, and the post method saves the changes. Listing 9-11 shows these two methods.

Listing 9-11 Methods for editing Customers

C#:

//
// GET: /Customer/Edit/5

public ActionResult Edit(int id)
{
   Customer cust =
       new CustomerRepository()
           .GetCustomer(id);
   return View(cust);
}

//
// POST: /Customer/Edit/5

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Customer cust)
{
   try
   {
       new CustomerRepository()
           .UpdateCustomer(cust);

       return RedirectToAction("Index");
   }
   catch
   {
       return View();
   }
}

VB:

'
' GET: /Customer/Edit/5

Function Edit(ByVal id As Integer) As ActionResult
    Dim custRep As New CustomerRepository
    Dim cust As Customer

    cust = custRep.GetCustomer(id)

    Return View(cust)
End Function

'
' POST: /Customer/Edit/5

<HttpPost()> _
Function Edit(ByVal id As Integer, ByVal cust As Customer)
     As ActionResult
     Try
         Dim custRep As New CustomerRepository
         custRep.UpdateCustomer(cust)
         Return RedirectToAction("Index")
     Catch
         Return View()
     End Try
End Function

In the get Edit action method, you need to get a reference to the current record, indicated by the id being passed in, and pass that reference to the View for display. The post Edit action method accepts the modified customer and passes it to the repository for update in the database. You should also right-click in either of the Edit methods and select Add View. Make the View strongly typed, set the class to Customer, and the Content type to Edit.

The final operation to complete is discussed next, how to delete a Customer.

Deleting a Customer

The default template for creating a list added an ActionLink for Details, next to the Edit ActionLink. You can create a read-only details page if you want, or just ensure the list is in the format you want to show each customer record, but for our purposes the Details option is not necessary. So, this example replaces the Details link with one for deleting a record. Listing 9-12 shows the Delete Controller method, which replaces the Detail Controller method.

Listing 9-12 The Delete Controller method

C#:

//
// GET: /Customer/Delete/5

public ActionResult Delete(int id)
{
     new CustomerRepository()
         .DeleteCustomer(id);

     TempData["Result"] = "Customer Deleted.";

     return RedirectToAction("Index");
}

VB:

'
' GET: /Customer/Delete/5

Function Delete(ByVal id As Integer) As ActionResult
     Dim custRep As New CustomerRepository
     custRep.DeleteCustomer(id)

     TempData("Result") = "Customer Deleted."

     Return RedirectToAction("Index")
End Function

Besides showing how to use the repository for performing the delete operation, there are a couple of new items in Listing 9-12 that you’ll need to know about: TempData and specifying a View. TempData is a special object for holding data for a single display of a View. So, when the View displays, it can read the current value of TempData, but that same value will not be available on the next View unless the Controller explicitly loads it again.

In all of the other calls to View, it was assumed that a View named after the Controller method would be returned, so it wasn’t necessary to specify the name of the View. However, we don’t have a delete View, so we specify Index as the View explicitly.

To accommodate the delete operation, Listing 9-13 shows the modifications on the Index.aspx View for Customers (located under ViewsCustomer).

Listing 9-13 Deleting a Customer

C#:

… content removed

<h2>Index</h2>

<p>
    <% if (TempData["Result"] != null)
    { %>
           <label><%= Html.Encode(TempData["Result"].ToString())%>
</label>
    <% } %>
</p>
<table>

… content removed

<% foreach (var item in Model) { %>

   <tr>
       <td>
            <%= Html.ActionLink("Edit", "Edit",
                new { id=item.CustomerID }) %> |
            <%= Html.ActionLink("Delete", "Delete",
                new { id=item.CustomerID })%>
       </td>
… content removed

VB:

… content removed

    <h2>Index</h2>

    <p>
        <% If Not TempData("Result") Is Nothing Then %>
            <label>
                <%= Html.Encode(TempData("Result").ToString())%>
            </label>
        <% End If%>
    </p>
    <p>
        <%= Html.ActionLink("Create New", "Create")%>
    </p>

    <table>

… content removed

     <% For Each item In Model%>

       <tr>
          <td>
             <%=Html.ActionLink("Edit", "Edit",
                New With {.id = item.CustomerID})%> |
             <%=Html.ActionLink("Delete", "Delete",
                New With {.id = item.CustomerID})%>
          </td>

… content removed

Listing 9-13 has content removed to avoid duplicating code you’ve already seen. Near the top of the listing, you can see the if statement that will check to see if there is a value in TempData[“Result”] (TempData(“Result”) in VB) and will display that value in a label if present. Next to the Edit ActionLink, the Details ActionLink has been changed to a Delete ActionLink, passing the id of the current customer back to the Controller for deletion.

Summary

You now know the essential parts of MVC: Models, Views, and Controllers. You saw how to implement the repository pattern for managing a data access layer and simplify the code. This chapter showed how to create controllers and views. You also learned about routing and how it helps match URLs to controllers, actions, and parameters. Finally, there was a section that demonstrated how to perform CRUD operations with ASP.NET MVC.

Another popular Web technology is Silverlight, which gives you the ability to create rich user experiences. The next chapter helps you get started with Silverlight development.

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

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