Overview
The best way to learn a new framework is to build something with it. This first chapter walks through how to build a small, but complete, application using ASP.NET MVC, and introduces some of the core concepts behind it.
The application we are going to build is called "NerdDinner." NerdDinner provides an easy way for people to find and organize dinners online (Figure 1-1).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig23_01.jpg)
Figure 1-1
NerdDinner enables registered users to create, edit and delete dinners. It enforces a consistent set of validation and business rules across the application (Figure 1-2).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig24_01.jpg)
Figure 1-2
Chapter 1 is licensed under the terms of Creative Commons Attribution No Derivatives 3.0 license and may be redistributed according to those terms with the following attribution: "Chapter 1 "NerdDinner" from Professional ASP.NET MVC 1.0 written by Rob Conery, Scott Hanselman, Phil Haack, Scott Guthrie published by Wrox (ISBN: 978-0-470-38461-9) may be redistributed under the terms of Creative Commons Attribution No Derivatives 3.0 license. The original electronic copy is available at http://tinyurl.com/aspnetmvc. The complete book Professional ASP.NET MVC 1.0 is copyright 2009 by Wiley Publishing Inc and may not redistributed without permission."
Visitors to the site can search to find upcoming dinners being held near them (Figure 1-3):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig24_02.jpg)
Figure 1-3
Clicking a dinner will take them to a details page where they can learn more about it (Figure 1-4):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig25_01.jpg)
Figure 1-4
If they are interested in attending the dinner they can log in or register on the site (Figure 1-5):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig25_02.jpg)
Figure 1-5
They can then easily RSVP to attend the event (Figures 1-6 and 1-7):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig26_01.jpg)
Figure 1-6
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig26_02.jpg)
Figure 1-7
We are going to begin implementing the NerdDinner application by using the File ð New Project command within Visual Studio to create a brand new ASP.NET MVC project. We'll then incrementally add functionality and features. Along the way we'll cover how to create a database, build a model with business rule validations, implement data listing/details UI, provide CRUD (Create, Update, Delete) form entry support, implement efficient data paging, reuse the UI using master pages and partials, secure the application using authentication and authorization, use AJAX to deliver dynamic updates and interactive map support, and implement automated unit testing.
You can build your own copy of NerdDinner from scratch by completing each step we walk through in this chapter. Alternatively, you can download a completed version of the source code here: http://tinyurl.com/aspnetmvc.
You can use either Visual Studio 2008 or the free Visual Web Developer 2008 Express to build the application. You can use either SQL Server or the free SQL Server Express to host the database.
You can install ASP.NET MVC, Visual Web Developer 2008, and SQL Server Express using the Microsoft Web Platform Installer available at www.microsoft.com/web/downloads.
File ð New Project
We'll begin our NerdDinner application by selecting the File ð New Project menu item within Visual Studio 2008 or the free Visual Web Developer 2008 Express.
This will bring up the New Project dialog. To create a new ASP.NET MVC application, we'll select the Web node on the left side of the dialog and then choose the ASP.NET MVC Web Application project template on the right (Figure 1-8):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig27_01.jpg)
Figure 1-8
We'll name the new project NerdDinner and then click the OK button to create it.
When we click OK, Visual Studio will bring up an additional dialog that prompts us to optionally create a unit test project for the new application as well (Figure 1-9). This unit test project enables us to create automated tests that verify the functionality and behavior of our application (something we'll cover later in this tutorial).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig28_01.jpg)
Figure 1-9
The Test framework drop-down in Figure 1-9 is populated with all available ASP.NET MVC unit test project templates installed on the machine. Versions can be downloaded for NUnit, MBUnit, and XUnit. The built-in Visual Studio Unit Test Framework is also supported.
Note | The Visual Studio Unit Test Framework is only available with Visual Studio 2008 Professional and higher versions). If you are using VS 2008 Standard Edition or Visual Web Developer 2008 Express, you will need to download and install the NUnit, MBUnit, or XUnit extensions for ASP.NET MVC in order for this dialog to be shown. The dialog will not display if there aren't any test frameworks installed. |
We'll use the default NerdDinner.Tests name for the test project we create, and use the Visual Studio Unit Test Framework option. When we click the OK button, Visual Studio will create a solution for us with two projects in it — one for our web application and one for our unit tests (Figure 1-10):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig28_02.jpg)
Figure 1-10
Examining the NerdDinner Directory Structure
When you create a new ASP.NET MVC application with Visual Studio, it automatically adds a number of files and directories to the project, as shown in Figure 1-11.
![Image from book](http://images.books24x7.com/bookimages/id_29605/fig29_01.jpg)
Figure 1-11
ASP.NET MVC projects by default have six top-level directories, shown in the following table:
![b24-bluearrow.gif](http://mmlviewer.books24x7.com/book/id_29605/images/b24-bluearrow.gif)
Directory
Purpose
/Controllers
Where you put Controller classes that handle URL requests
/Models
Where you put classes that represent and manipulate data
/Views
Where you put UI template files that are responsible for rendering output
/Scripts
Where you put JavaScript library files and scripts (.js)
/Content
Where you put CSS and image files, and other non-dynamic/non-JavaScript content
/App_Data
Where you store data files you want to read/write.
ASP.NET MVC does not require this structure. In fact, developers working on large applications will typically partition the application up across multiple projects to make it more manageable (for example: data model classes often go in a separate class library project from the web application). The default project structure, however, does provide a nice default directory convention that we can use to keep our application concerns clean.
When we expand the /Controllers directory, we'll find that Visual Studio added two controller classes (Figure 1-12) — HomeController and AccountController — by default to the project:
![Image from book](http://images.books24x7.com/bookimages/id_29605/fig30_01.jpg)
Figure 1-12
When we expand the /Views directory, we'll find three subdirectories — /Home, /Account and /Shared — as well as several template files within them, were also added to the project by default (Figure 1-13):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig30_02.jpg)
Figure 1-13
When we expand the /Content and /Scripts directories, we'll find a Site.css file that is used to style all HTML on the site, as well as JavaScript libraries that can enable ASP.NET AJAX and jQuery support within the application (Figure 1-14):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig31_01.jpg)
Figure 1-14
When we expand the NerdDinner.Tests project we'll find two classes that contain unit tests for our controller classes (Figure 1-15):
![Image from book](http://images.books24x7.com/bookimages/id_29605/fig31_02.jpg)
Figure 1-15
These default files, added by Visual Studio, provide us with a basic structure for a working application — complete with home page, about page, account login/logout/registration pages, and an unhandled error page (all wired-up and working out of the box).
Running the NerdDinner Application
We can run the project by choosing either the Debug ð Start Debugging or Debug ð Start Without Debugging menu items (Figure 1-16):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig32_01.jpg)
Figure 1-16
This will launch the built-in ASP.NET web server that comes with Visual Studio, and run our application (Figure 1-17):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig32_02.jpg)
Figure 1-17
FIgure 1-18 is the home page for our new project (URL: /) when it runs:
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig32_03.jpg)
Figure 1-18
Clicking the About tab displays an About page (URL: /Home/About, shown in Figure 1-19):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig33_01.jpg)
Figure 1-19
Clicking the Log On link on the top right takes us to a Login page shown in Figure 1-20 (URL: /Account/LogOn)
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig33_02.jpg)
Figure 1-20
If we don't have a login account, we can click the Register link (URL: /Account/Register) to create one (Figure 1-21):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig34_01.jpg)
Figure 1-21
The code to implement the above home, about, and login/register functionality was added by default when we created our new project. We'll use it as the starting point of our application.
Testing the NerdDinner Application
If we are using the Professional Edition or higher version of Visual Studio 2008, we can use the built-in unit-testing IDE support within Visual Studio to test the project.
Choosing one of the above options in Figure 1-22 will open the Test Results pane within the IDE (Figure 1-23) and provide us with pass/fail status on the 27 unit tests included in our new project that cover the built-in functionality.
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig35_01.jpg)
Figure 1-22
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig35_02.jpg)
Figure 1-23
![]() |
Creating the Database
We'll be using a database to store all of the Dinner and RSVP data for our NerdDinner application.
The steps below show creating the database using the free SQL Server Express edition. All of the code we'll write works with both SQL Server Express and the full SQL Server.
Creating a New SQL Server Express Database
We'll begin by right-clicking on our web project, and then selecting the Add ð New Item menu command (Figure 1-24).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig36_01.jpg)
Figure 1-24
This will bring up the Add New Item dialog (Figure 1-25). We'll filter by the Data category and select the SQL Server Database item template.
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig36_02.jpg)
Figure 1-25
We'll name the SQL Server Express database we want to create NerdDinner.mdf and hit OK. Visual Studio will then ask us if we want to add this file to our \App_Data directory (Figure 1-26), which is a directory already set up with both read and write security ACLs.
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig37_01.jpg)
Figure 1-26
We'll click Yes and our new database will be created and added to our Solution Explorer (Figure 1-27).
Creating Tables within Our Database
We now have a new empty database. Let's add some tables to it.
To do this we'll navigate to the Server Explorer tab window within Visual Studio, which enables us to manage databases and servers. SQL Server Express databases stored in the \App_Data folder of our application will automatically show up within the Server Explorer. We can optionally use the Connect to Database icon on the top of the Server Explorer window to add additional SQL Server databases (both local and remote) to the list as well (Figure 1-28).
We will add two tables to our NerdDinner database — one to store our Dinners, and the other to track RSVP acceptances to them. We can create new tables by right-clicking on the Tables folder within our database and choosing the Add New Table menu command (Figure 1-29).
This will open up a table designer that allows us to configure the schema of our table. For our Dinners table, we will add 10 columns of data (Figure 1-30).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig38_02.jpg)
Figure 1-30
We want the DinnerID column to be a unique primary key for the table. We can configure this by right-clicking on the DinnerID column and choosing the Set Primary Key menu item (Figure 1-31).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig39_01.jpg)
Figure 1-31
In addition to making DinnerID a primary key, we also want configure it as an identity column whose value is automatically incremented as new rows of data are added to the table (meaning the first inserted Dinner row will have a DinnerID of 1, the second inserted row will have a DinnerID of 2, etc.).
We can do this by selecting the DinnerID column and then using the Column Properties editor to set the "(Is Identity)" property on the column to Yes (Figure 1-32). We will use the standard identity defaults (start at 1 and increment 1 on each new Dinner row).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig39_02.jpg)
Figure 1-32
We'll then save our table by pressing Ctrl-S or by clicking the File ð Save menu command. This will prompt us to name the table. We'll name it Dinners (Figure 1-33).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig39_03.jpg)
Figure 1-33
Our new Dinners table will then show up in our database in the Server Explorer.
We'll then repeat the above steps and create a RSVP table. This table will have three columns. We will set up the RsvpID column as the primary key, and also make it an identity column (Figure 1-34).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig40_01.jpg)
Figure 1-34
We'll save it and give it the name RSVP.
Setting Up a Foreign Key Relationship Between Tables
We now have two tables within our database. Our last schema design step will be to set up a "one-to-many" relationship between these two tables — so that we can associate each Dinner row with zero or more RSVP rows that apply to it. We will do this by configuring the RSVP table's DinnerID column to have a foreign-key relationship to the DinnerID column in the Dinners table.
To do this we'll open up the RSVP table within the table designer by double-clicking it in the Server Explorer. We'll then select the DinnerID column within it, right-click, and choose the Relationships… context menu command (Figure 1-35):
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig40_02.jpg)
Figure 1-35
This will bring up a dialog that we can use to set up relationships between tables (Figure 1-36)
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig41_01.jpg)
Figure 1-36
We'll click the Add button to add a new relationship to the dialog. Once a relationship has been added, we'll expand the Tables and Column Specification tree-view node within the property grid to the right of the dialog, and then click the "…" button to the right of it (Figure 1-37).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig41_02.jpg)
Figure 1-37
Clicking the "…" button will bring up another dialog that allows us to specify which tables and columns are involved in the relationship, as well as allow us to name the relationship.
We will change the Primary Key Table to be Dinners, and select the DinnerID column within the Dinners table as the primary key. Our RSVP table will be the foreign-key table, and the RSVP.DinnerID column will be associated as the foreign-key (Figure 1-38).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig42_01.jpg)
Figure 1-38
Now each row in the RSVP table will be associated with a row in the Dinner table. SQL Server will maintain referential integrity for us — and prevent us from adding a new RSVP row if it does not point to a valid Dinner row. It will also prevent us from deleting a Dinner row if there are still RSVP rows referring to it.
Adding Data to Our Tables
Let's finish by adding some sample data to our Dinners table. We can add data to a table by right-clicking on it in the Server Explorer and choosing the Show Table Data command (Figure 1-39):
Let's add a few rows of Dinner data that we can use later as we start implementing the application (Figure 1-40).
Building the Model
In a Model-View-Controller framework the term Model refers to the objects that represent the data of the application, as well as the corresponding domain logic that integrates validation and business rules with it. The Model is in many ways the "heart" of an MVC-based application, and as we'll see later, it fundamentally drives the behavior of the application.
The ASP.NET MVC framework supports using any data access technology. Developers can choose from a variety of rich .NET data options to implement their models including: LINQ to Entities, LINQ to SQL, NHibernate, LLBLGen Pro, SubSonic, WilsonORM, or just raw ADO.NET DataReaders or DataSets.
For our NerdDinner application, we are going to use LINQ to SQL to create a simple domain model that corresponds fairly closely to our database design, and add some custom validation logic and business rules. We will then implement a repository class that helps abstract away the data persistence implementation from the rest of the application, and enables us to easily unit test it.
LINQ to SQL
LINQ to SQL is an ORM (object relational mapper) that ships as part of .NET 3.5.
LINQ to SQL provides an easy way to map database tables to .NET classes we can code against. For our NerdDinner application, we'll use it to map the Dinners and RSVP tables within our database to Dinner and RSVP model classes. The columns of the Dinners and RSVP tables will correspond to properties on the Dinner and RSVP classes. Each Dinner and RSVP object will represent a separate row within the Dinners or RSVP tables in the database.
LINQ to SQL allows us to avoid having to manually construct SQL statements to retrieve and update Dinner and RSVP objects with database data. Instead, we'll define the Dinner and RSVP classes, how they map to/from the database, and the relationships between them. LINQ to SQL will then take care of generating the appropriate SQL execution logic to use at runtime when we interact and use them.
We can use the LINQ language support within VB and C# to write expressive queries that retrieve Dinner and RSVP objects. This minimizes the amount of data code we need to write, and allows us to build really clean applications.
Adding LINQ to SQL Classes to Our Project
We'll begin by right-clicking on the Models folder in our project, and select the Add ð New Item menu command (Figure 1-41).
This will bring up the Add New Item dialog (Figure 1-42). We'll filter by the Data category and select the LINQ to SQL Classes template within it.
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig44_02.jpg)
Figure 1-42
We'll name the item NerdDinner and click the Add button. Visual Studio will add a NerdDinner.dbml file under our \Models directory, and then open the LINQ to SQL object relational designer (Figure 1-43).
![Click To expand Image from book](http://images.books24x7.com/bookimages/id_29605/fig45_01.jpg)
Figure 1-43
Creating Data Model Classes with LINQ to SQL
LINQ to SQL enables us to quickly create data model classes from an existing database schema. To do this we'll open the NerdDinner database in the Server Explorer, and select the Tables we want to model in it (Figure 1-44).
We can then drag the tables onto the LINQ to SQL designer surface. When we do this, LINQ to SQL will automatically create Dinner and RSVP classes using the schema of the tables (with class properties that map to the database table columns as shown in Figure 1-45).
By default the LINQ to SQL designer automatically pluralizes table and column names when it creates classes based on a database schema. For example: the "Dinners" table in our example above resulted in a Dinner class. This class naming helps make our models consistent with .NET naming conventions, and I usually find that having the designer fix this up is convenient (especially when adding lots of tables). If you don't like the name of a class or property that the designer generates, though, you can always override it and change it to any name you want. You can do this either by editing the entity/property name in-line within the designer or by modifying it via the property grid.
By default the LINQ to SQL designer also inspects the primary key/foreign key relationships of the tables, and based on them automatically creates default relationship associations between the different model classes it creates. For example, when we modeled the Dinners and RSVP tables onto the LINQ to SQL designer, a one-to-many relationship association between the two was inferred based on the fact that the RSVP table had a foreign key to the Dinners table (this is indicated by the arrow in the designer in Figure 1-46).
The association in Figure 1-46 will cause LINQ to SQL to add a strongly typed Dinner property to the RSVP class that developers can use to access the Dinner entity associated with a given RSVP. It will also cause the Dinner class to have a strongly typed RSVPs collection property that enables developers to retrieve and update RSVP objects associated with that Dinner.
In Figure 1-47, you can see an example of IntelliSense within Visual Studio when we create a new RSVP object and add it to a Dinner's RSVPs collection.
Notice how LINQ to SQL created a "RSVPs" collection on the Dinner object. We can use this to associate a foreign-key relationship between a Dinner and a RSVP row in our database (Figure 1-48):
If you don't like how the designer has modeled or named a table association, you can override it. Just click on the association arrow within the designer and access its properties via the property grid to rename, delete, or modify it. For our NerdDinner application, though, the default association rules work well for the data model classes we are building and we can just use the default behavior.
NerdDinnerDataContext Class
Visual Studio automatically generates .NET classes that represent the models and database relationships defined using the LINQ to SQL designer. A LINQ to SQL DataContext class is also generated for each LINQ to SQL designer file added to the solution. Because we named our LINQ to SQL class item "NerdDinner," the DataContext class created will be called NerdDinnerDataContext. This NerdDinnerDataContext class is the primary way we will interact with the database.
Our NerdDinnerDataContext class exposes two properties — Dinners and RSVP — that represent the two tables we modeled within the database. We can use C# to write LINQ queries against those properties to query and retrieve Dinner and RSVP objects from the database.
The following code (Figure 1-49) demonstrates how to instantiate a NerdDinnerDataContext object and perform a LINQ query against it to obtain a sequence of Dinners that occur in the future.
A NerdDinnerDataContext object tracks any changes made to Dinner and RSVP objects retrieved using it, and enable us to easily save the changes back to the database. The code that follows demonstrates how we can use a LINQ query to retrieve a single Dinner object from the database, update two of its properties, and then save the changes back to the database:
NerdDinnerDataContext db = new NerdDinnerDataContext();// Retrieve Dinner object that reprents row with DinnerID of 1Dinner dinner = db.Dinners.Single(d => d.DinnerID == 1);// Update two properties on Dinnerdinner.Title = "Changed Title";dinner.Description = "This dinner will be fun";// Persist changes to databasedb.SubmitChanges();
The NerdDinnerDataContext object in the code automatically tracked the property changes made to the Dinner object we retrieved from it. When we called the SubmitChanges method, it executed an appropriate SQL "UPDATE" statement to the database to persist the updated values back.
Creating a DinnerRepository Class
For small applications, it is sometimes fine to have Controllers work directly against a LINQ to SQL DataContext class, and embed LINQ queries within the Controllers. As applications get larger, though, this approach becomes cumbersome to maintain and test. It can also lead to us duplicating the same LINQ queries in multiple places.
One approach that can make applications easier to maintain and test is to use a repository pattern. A repository class helps encapsulate data querying and persistence logic, and abstracts away the implementation details of the data persistence from the application. In addition to making application code cleaner, using a repository pattern can make it easier to change data storage implementations in the future, and it can help facilitate unit testing an application without requiring a real database.
For our NerdDinner application we'll define a DinnerRepository class with the following signature:
public
class DinnerRepository
{
// Query Methods
public IQueryable<Dinner> FindAllDinners();
public IQueryable<Dinner> FindUpcomingDinners();
public Dinner GetDinner(int id);
// Insert/Delete
public void Add(Dinner dinner);
public void Delete(Dinner dinner);// Persistence
public void Save();
}
Note | Later in this chapter, we'll extract an IDinnerRepository interface from this class and enable dependency injection with it on our Controllers. To begin with, though, we are going to start simple and just work directly with the DinnerRepository class. |
To implement this class we'll right-click on our Models folder and choose the Add ð New Item menu command. Within the Add New Item dialog, we'll select the Class template and name the file DinnerRepository.cs (Figure 1-50).
We can then implement our DinnerRespository class using the code that follows:
public class DinnerRepository { private NerdDinnerDataContext db = new NerdDinnerDataContext(); // // Query Methods public IQueryable<Dinner> FindAllDinners() { return db.Dinners; } public IQueryable<Dinner> FindUpcomingDinners() { return from dinner in db.Dinners where dinner.EventDate > DateTime.Now orderby dinner.EventDate select dinner; } public Dinner GetDinner(int id) { return db.Dinners.SingleOrDefault(d => d.DinnerID == id); } // // Insert/Delete Methods public void Add(Dinner dinner) { db.Dinners.InsertOnSubmit(dinner); } public void Delete(Dinner dinner) { db.RSVPs.DeleteAllOnSubmit(dinner.RSVPs); db.Dinners.DeleteOnSubmit(dinner); } // // Persistence public void Save() { db.SubmitChanges(); }}
Retrieving, Updating, Inserting, and Deleting Using the DinnerRepository Class
Now that we've created our DinnerRepository class, let's look at a few code examples that demonstrate common tasks we can do with it.
Querying Examples
The code that follows retrieves a single Dinner using the DinnerID value:
DinnerRepository dinnerRepository = new DinnerRepository();// Retrieve specific dinner by its DinnerIDDinner dinner = dinnerRepository.GetDinner(5);
The code that follows retrieves all upcoming dinners and loops over them:
DinnerRepository dinnerRepository = new DinnerRepository();// Retrieve all upcoming Dinnersvar upcomingDinners = dinnerRepository.FindUpcomingDinners();// Loop over each upcoming Dinnerforeach (Dinner dinner in upcomingDinners) {}
Insert and Update Examples
The code that follows demonstrates adding two new dinners. Additions/modifications to the repository aren't committed to the database until the Save method is called on it. LINQ to SQL automatically wraps all changes in a database transaction — so either all changes happen or none of them does when our repository saves:
DinnerRepository dinnerRepository = new DinnerRepository();// Create First DinnerDinner newDinner1 = new Dinner();newDinner1.Title = "Dinner with Scott";newDinner1.HostedBy = "ScotGu";newDinner1.ContactPhone = "425-703-8072";// Create Second DinnerDinner newDinner2 = new Dinner();newDinner2.Title = "Dinner with Bill";newDinner2.HostedBy = "BillG";newDinner2.ContactPhone = "425-555-5151";// Add Dinners to RepositorydinnerRepository.Add(newDinner1);dinnerRepository.Add(newDinner2);// Persist ChangesdinnerRepository.Save();
The code that follows retrieves an existing Dinner object and modifies two properties on it. The changes are committed back to the database when the Save method is called on our repository:
DinnerRepository dinnerRepository = new DinnerRepository();// Retrieve specific dinner by its DinnerIDDinner dinner = dinnerRepository.GetDinner(5);// Update Dinner propertiesdinner.Title = "Update Title";dinner.HostedBy = "New Owner";// Persist changesdinnerRepository.Save();
The code that follows retrieves a dinner and then adds an RSVP to it. It does this using the RSVPs collection on the Dinner object that LINQ to SQL created for us (because there is a primary-key/foreign-key relationship between the two in the database). This change is persisted back to the database as a new RSVP table row when the Save method is called on the repository:
DinnerRepository dinnerRepository = new DinnerRepository();// Retrieve specific dinner by its DinnerIDDinner dinner = dinnerRepository.GetDinner(5);// Create a new RSVP objectRSVP myRSVP = new RSVP();myRSVP.AttendeeName = "ScottGu";// Add RSVP to Dinner's RSVP Collectiondinner.RSVPs.Add(myRSVP);// Persist changesdinnerRepository.Save();
Delete Example
The code that follows retrieves an existing Dinner object, and then marks it to be deleted. When the Save method is called on the repository, it will commit the delete back to the database:
DinnerRepository dinnerRepository = new DinnerRepository();// Retrieve specific dinner by its DinnerIDDinner dinner = dinnerRepository.GetDinner(5);// Mark dinner to be deleteddinnerRepository.Delete(dinner);// Persist changesdinnerRepository.Save();
Integrating Validation and Business Rule Logic with Model Classes
Integrating validation and business rule logic is a key part of any application that works with data.
Schema Validation
When model classes are defined using the LINQ to SQL designer, the datatypes of the properties in the data model classes will correspond to the datatypes of the database table. For example: if the EventDate column in the Dinners table is a datetime, the data model class created by LINQ to SQL will be of type DateTime (which is a built-in .NET datatype). This means you will get compile errors if you attempt to assign an integer or boolean to it from code, and it will raise an error automatically if you attempt to implicitly convert a non-valid string type to it at runtime.
LINQ to SQL will also automatically handle escaping SQL values for you when you use strings — so you don't need to worry about SQL injection attacks when using it.
Validation and Business Rule Logic
Datatype validation is useful as a first step but is rarely sufficient. Most real-world scenarios require the ability to specify richer validation logic that can span multiple properties, execute code, and often have awareness of a model's state (for example: is it being created /updated/deleted, or within a domain-specific state like "archived").
There are a variety of different patterns and frameworks that can be used to define and apply validation rules to model classes, and there are several .NET based frameworks out there that can be used to help with this. You can use pretty much any of them within ASP.NET MVC applications.
For the purposes of our NerdDinner application, we'll use a relatively simple and straightforward pattern where we expose an IsValid property and a GetRuleViolations method on our Dinner model object. The IsValid property will return true or false depending on whether the validation and business rules are all valid. The GetRuleViolations method will return a list of any rule errors.
We'll implement IsValid and GetRuleViolations by adding a partial class to our project. Partial classes can be used to add methods/properties/events to classes maintained by a VS designer (like the Dinner class generated by the LINQ to SQL designer) and help avoid having the tool from messing with our code.
We can add a new partial class to our project by right-clicking on the \Models folder, and then selecting the Add New Item menu command. We can then choose the Class template within the Add New Item dialog (Figure 1-51) and name it Dinner.cs.
Clicking the Add button will add a Dinner.cs file to our project and open it within the IDE. We can then implement a basic rule/validation enforcement framework using the following code:
public partial class Dinner { public bool IsValid { get { return (GetRuleViolations().Count() == 0); } } public IEnumerable<RuleViolation> GetRuleViolations() { yield break; } partial void OnValidate(ChangeAction action) { if (!IsValid) throw new ApplicationException("Rule violations prevent saving"); }}public class RuleViolation { public string ErrorMessage { get; private set; } public string PropertyName { get; private set; } public RuleViolation(string errorMessage) { ErrorMessage = errorMessage; } public RuleViolation(string errorMessage, string propertyName) { ErrorMessage = errorMessage; PropertyName = propertyName; }}
A few notes about this code:
-
The Dinner class is prefaced with a partial keyword — which means the code contained within it will be combined with the class generated/maintained by the LINQ to SQL designer and compiled into a single class.
-
Invoking the GetRuleViolations method will cause our validation and business rules to be evaluated (we'll implement them shortly). The GetRuleViolations method returns back a sequence of RuleViolation objects that provide more details about each rule error.
-
The IsValid property provides a convenient helper property that indicates whether the Dinner object has any active RuleViolations. It can be proactively checked by a developer using the Dinner object at any time (and does not raise an exception).
-
The OnValidate partial method is a hook that LINQ to SQL provides that allows us to be notified any time the Dinner object is about to be persisted within the database. Our OnValidate implementation in the previous code ensures that the Dinner has no RuleViolations before it is saved. If it is in an invalid state, it raises an exception, which will cause LINQ to SQL to abort the transaction.
This approach provides a simple framework that we can integrate validation and business rules into. For now let's add the below rules to our GetRuleViolations method:
public IEnumerable<RuleViolation> GetRuleViolations() { if (String.IsNullOrEmpty(Title)) yield return new RuleViolation("Title required", "Title"); if (String.IsNullOrEmpty(Description)) yield return new RuleViolation("Description required"," Description"); if (String.IsNullOrEmpty(HostedBy)) yield return new RuleViolation("HostedBy required", "HostedBy"); if (String.IsNullOrEmpty(Address)) yield return new RuleViolation("Address required", "Address"); if (String.IsNullOrEmpty(Country)) yield return new RuleViolation("Country required", "Country"); if (String.IsNullOrEmpty(ContactPhone)) yield return new RuleViolation("Phone# required", "ContactPhone"); if (!PhoneValidator.IsValidNumber(ContactPhone, Country)) yield return new RuleViolation("Phone# does not match country", "ContactPhone"); yield break;}
We are using the yield return feature of C# to return a sequence of any RuleViolations. The first six rule checks in the previous code simply enforce that string properties on our Dinner cannot be null or empty. The last rule is a little more interesting and calls a PhoneValidator.IsValidNumber helper method that we can add to our project to verify that the ContactPhone number format matches the Dinner's country.
We can use .NET's regular expression support to implement this phone validation support. The code that follows is a simple PhoneValidator implementation that we can add to our project that enables us to add country-specific Regex pattern checks:
public class PhoneValidator { static IDictionary<string, Regex> countryRegex =new Dictionary<string, Regex>() { { "USA", new Regex("^[2-9]\\d{2}-\\d{3}-\\d{4}$")}, { "UK", new Regex("(^1300\\d{6}$)|(^1800|1900|1902\\d{6}$)|(^0[2|3|7|8]{1}[0-9]{8}$)|(^13\\d{4}$)|(^04\\d{2,3}\\d{6}$)")}, { "Netherlands", new Regex("(^\\+[0-9]{2}|^\\+[0-9]{2}\\(0\\)|^\\(\\+[0-9]{2}\\)\\(0\\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\\-\\s]{10}$)")}, }; public static bool IsValidNumber(string phoneNumber, string country) { if (country != null && countryRegex.ContainsKey(country)) return countryRegex[country].IsMatch(phoneNumber); else return false; } public static IEnumerable<string> Countries { get { return countryRegex.Keys; } }}
Now when we try to create or update a Dinner, our validation logic rules will be enforced. Developers can proactively determine if a Dinner object is valid, and retrieve a list of all violations in it without raising any exceptions:
Dinner dinner = dinnerRepository.GetDinner(5);dinner.Country = "USA";dinner.ContactPhone = "425-555-BOGUS";if (!dinner.IsValid) { var errors = dinner.GetRuleViolations(); // do something to fix errors}
If we attempt to save a Dinner in an invalid state, an exception will be raised when we call the Save method on the DinnerRepository. This occurs because our Dinner.OnValidate partial method raises an exception if any rule violations exist in the Dinner. We can catch this exception and reactively retrieve a list of the violations to fix:
Dinner dinner = dinnerRepository.GetDinner(5);try { dinner.Country = "USA"; dinner.ContactPhone = "425-555-BOGUS"; dinnerRepository.Save();}catch { var errors = dinner.GetRuleViolations(); // do something to fix errors}
Because our validation and business rules are implemented within our domain model layer, and not within the UI layer, they will be applied and used across all scenarios within our application. We can later change or add business rules and have all code that works with our Dinner objects honor them. Having the flexibility to change business rules in one place, without having these changes ripple throughout the application and UI logic, is a sign of a well-written application, and a benefit that an MVC framework helps encourage.
Controllers and Views
With traditional web frameworks (classic ASP, PHP, ASP.NET Web Forms, etc.), incoming URLs are typically mapped to files on disk. For example: a request for a URL like /Products.aspx or /Products.php might be processed by a Products.aspx or Products.php file.
Web-based MVC frameworks map URLs to server code in a slightly different way. Instead of mapping incoming URLs to files, they instead map URLs to methods on classes. These classes are called Controllers and they are responsible for processing incoming HTTP requests, handling user input, retrieving and saving data, and determining the response to send back to the client (display HTML, download a file, redirect to a different URL, etc.).
Now that we have built up a basic model for our NerdDinner application, our next step will be to add a Controller to the application that takes advantage of it to provide users with a data listing/details navigation experience for dinners on our site.
Adding a DinnersController Controller
We'll begin by right-clicking on the Controllers folder within our web project, and then selecting the Add ð Controller menu command (Figure 1-52).
Note | You can also execute this command by typing Ctrl-M, Ctrl-C. |
This will bring up the Add Controller dialog (Figure 1-53):
We'll name the new controller DinnersController and click the Add button. Visual Studio will then add a DinnersController.cs file under our \Controllers directory (Figure 1-54).
It will also open up the new DinnersController class within the code-editor.
Adding Index and Details Action Methods to the DinnersController Class
We want to enable visitors using our application to browse the list of upcoming dinners, and enable them to click on any dinner in the list to see specific details about it. We'll do this by publishing the following URLs from our application:
![b24-bluearrow.gif](http://mmlviewer.books24x7.com/book/id_29605/images/b24-bluearrow.gif)
URL
Purpose
/Dinners/
Display an HTML list of upcoming dinners.
/Dinners/Details/[id]
Display details about a specific dinner indicated by an "id" parameter embedded within the URL — which will match the DinnerID of the dinner in the database.
For example: /Dinners/Details/2 would display an HTML page with details about the Dinner whose DinnerID value is 2.
We can publish initial implementations of these URLs by adding two public "action methods" to our DinnersController class:
public class DinnersController : Controller { // // GET: /Dinners/ public void Index() { Response.Write("<h1>Coming Soon: Dinners</h1>"); } // // GET: /Dinners/Details/2 public void Details(int id) { Response.Write("<h1>Details DinnerID: "+ id + "</h1>"); }}
We can then run the application and use our browser to invoke them. Typing in the /Dinners/ URL will cause our Index method to run, and it will send back the following response (Figure 1-55):
Typing in the /Dinners/Details/2 URL will cause our Details method to run, and send back the response in Figure 1-56.
You might be wondering — how did ASP.NET MVC know to create our DinnersController class and invoke those methods? To understand that let's take a quick look at how routing works.
Understanding ASP.NET MVC Routing
ASP.NET MVC includes a powerful URL routing engine that provides a lot of flexibility in controlling how URLs are mapped to controller classes. It allows us to completely customize how ASP.NET MVC chooses which controller class to create, which method to invoke on it, as well as configure different ways that variables can be automatically parsed from the URL/querystring and passed to the method as parameter arguments. It delivers the flexibility to totally optimize a site for SEO (search engine optimization) as well as publish any URL structure we want from an application.
By default, new ASP.NET MVC projects come with a preconfigured set of URL routing rules already registered. This enables us to easily get started on an application without having to explicitly configure anything. The default routing rule registrations can be found within the Application class of our projects — which we can open by double-clicking the Global.asax file in the root of our project (Figure 1-57).
The default ASP.NET MVC routing rules are registered within the RegisterRoutes method of this class:
public void RegisterRoutes(RouteCollection routes){ routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL w/ params new { controller=" Home", action=" Index", id=" "} // Param defaults );}
The routes.MapRoute method call in the previous code registers a default routing rule that maps incoming URLs to controller classes using the URL format: /{controller}/{action}/{id} — where controller is the name of the controller class to instantiate, action is the name of a public method toinvoke on it, and id is an optional parameter embedded within the URL that can be passed as an argument to the method. The third parameter passed to the MapRoute method call is a set of default values to use for the controller/action/id values in the event that they are not present in the URL (controller = "Home", action=" Index", id="").
The following table demonstrates how a variety of URLs are mapped using the default /{controllers}/ {action}/{id} route rule:
![b24-bluearrow.gif](http://mmlviewer.books24x7.com/book/id_29605/images/b24-bluearrow.gif)
URL
Controller Class
Action Method
Parameters Passed
/Dinners/Details/2
DinnersController
Details(id)
id=2
/Dinners/Edit/5
DinnersController
Edit(id)
id=5
/Dinners/Create
DinnersController
Create()
N/A
/Dinners
DinnersController
Index()
N/A
/Home
HomeController
Index()
N/A
/
HomeController
Index()
N/A
The last three rows show the default values (Controller = Home, Action = Index, Id = "") being used. Because the Index method is registered as the default action name if one isn't specified, the /Dinners and /Home URLs cause the Index action method to be invoked on their Controller classes. Because the "Home" controller is registered as the default controller if one isn't specified, the / URL causes the HomeController to be created, and the Index action method on it to be invoked.
If you don't like these default URL routing rules, the good news is that they are easy to change — just edit them within the RegisterRoutes method in the previous code. For our NerdDinner application, though, we aren't going to change any of the default URL routing rules — instead we'll just use them as-is.
Using the DinnerRepository from Our DinnersController
Let's now replace the current implementation of our Index and Details action methods with implementations that use our model.
We'll use the DinnerRepository class we built earlier to implement the behavior. We'll begin by adding a using statement that references the NerdDinner.Models namespace, and then declare an instance of our DinnerRepository as a field on our DinnerController class.
Later in this chapter, we'll introduce the concept of Dependency Injection and show another way for our Controllers to obtain a reference to a DinnerRepository that enables better unit testing — but for right now we'll just create an instance of our DinnerRepository inline like the code that follows.
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mvc;using NerdDinner.Models;namespace NerdDinner.Controllers { public class DinnersController : Controller { DinnerRepository dinnerRepository = new DinnerRepository(); // // GET: /Dinners/ public void Index() { var dinners = dinnerRepository.FindUpcomingDinners().ToList(); } // // GET: /Dinners/Details/2 public void Details(int id) { Dinner dinner = dinnerRepository.GetDinner(id); } }}
Now we are ready to generate a HTML response back using our retrieved data model objects.
Using Views with Our Controller
While it is possible to write code within our action methods to assemble HTML and then use the Response.Write helper method to send it back to the client, that approach becomes fairly unwieldy quickly. A much better approach is for us to only perform application and data logic inside our DinnersController action methods, and to then pass the data needed to render a HTML response to a separate view template that is responsible for outputting the HTML representation of it. As we'll see in a moment, a view template is a text file that typically contains a combination of HTML markup and embedded rendering code.
Separating our controller logic from our view rendering brings several big benefits. In particular it helps enforce a clear separation of concerns between the application code and UI formatting/rendering code. This makes it much easier to unit test application logic in isolation from UI rendering logic. It makes it easier to later modify the UI rendering templates without having to make application code changes. And it can make it easier for developers and designers to collaborate together on projects.
We can update our DinnersController class to indicate that we want to use a view template to send back an HTML UI response by changing the method signatures of our two action methods from having a return type of "void" to instead have a return type of ActionResult. We can then call the View helper method on the Controller base class to return back a ViewResult object:
public class DinnersController : Controller { DinnerRepository dinnerRepository = new DinnerRepository(); // // GET: /Dinners/ public ActionResult Index() { var dinners = dinnerRepository.FindUpcomingDinners().ToList(); return View("Index", dinners); } // // GET: /Dinners/Details/2 public ActionResult Details(int id) { Dinner dinner = dinnerRepository.GetDinner(id); if (dinner == null) return View("NotFound"); else return View("Details", dinner); }}
The signature of the View helper method we are using in the previous code looks like Figure 1-58.
The first parameter to the View helper method is the name of the view template file we want to use to render the HTML response. The second parameter is a model object that contains the data that the view template needs in order to render the HTML response.
Within our Index action method we are calling the View helper method and indicating that we want to render an HTML listing of dinners using an "Index" view template. We are passing the view template a sequence of Dinner objects to generate the list from:
GET: /Dinners/public ActionResult Index() { var dinners = dinnerRepository.FindUpcomingDinners().ToList(); return View("Index", dinners);}
Within our Details action method, we attempt to retrieve a Dinner object using the id provided within the URL. If a valid Dinner is found we call the View helper method, indicating we want to use a "Details" view template to render the retrieved Dinner object. If an invalid dinner is requested, we render a helpful error message that indicates that the dinner doesn't exist using a "NotFound" view template (and an overloaded version of the View() helper method that just takes the template name):
GET: /Dinners/Details/2public ActionResult Details(int id) { Dinner dinner = dinnerRepository.FindDinner(id); if (dinner == null) return View("NotFound"); else return View("Details", dinner);}
Let's now implement the "NotFound", "Details", and "Index" view templates.
Implementing the "NotFound" View Template
We'll begin by implementing the "NotFound" view template — which displays a friendly error message indicating that the requested dinner can't be found.
We'll create a new view template by positioning our text cursor within a controller action method, and then by right clicking and choosing the Add View menu command (Figure 1-59; we can also execute this command by pressing Ctrl-M, Ctrl-V):
This will bring up an Add View dialog shown in Figure 1-60. By default, the dialog will pre-populate the name of the view to create to match the name of the action method the cursor was in when the dialog was launched (in this case "Details"). Because we want to first implement the "NotFound" template, we'll override this view name and set it instead to be NotFound:
When we click the Add button, Visual Studio will create a new NotFound.aspx (Figure 1-61) view template for us within the \Views\Dinners directory (which it will also create if the directory doesn't already exist):
It will also open up our new NotFound.aspx view template within the code-editor (Figure 1-62):
View templates by default have two content regions where we can add content and code. The first allows us to customize the "title" of the HTML page sent back. The second allows us to customize the "main content" of the HTML page sent back.
To implement our "NotFound" view template, we'll add some basic content:
<asp:Content ID=" Title" ContentPlaceHolderID=" TitleContent" runat=" server"> Dinner Not Found</asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2>Dinner Not Found</h2> <p>Sorry - but the dinner you requested doesn't exist or was deleted.</p></asp:Content>
We can then try it out within the browser. To do this let's request the /Dinners/Details/9999 URL. This will refer to a dinner that doesn't currently exist in the database, and will cause our DinnersController .Details action method to render our "NotFound" view template (Figure 1-63).
One thing you'll notice in Figure 1-63 is that our basic view template has inherited a bunch of HTML that surrounds the main content on the screen. This is because our view template is using a master page template that enables us to apply a consistent layout across all views on the site. We'll discuss how master pages work more in a later part of this chapter.
Implementing the "Details" View Template
Let's now implement the "Details" view template — which will generate HTML for a single Dinner model.
We'll do this by positioning our text cursor within the Details action method, and then right-clicking and choosing the Add View menu command — Figure 1-64 — or pressing Ctrl-M, Ctrl-V.
This will bring up the Add View dialog. We'll keep the default view name (Details). We'll also select the "Create a strongly typed view" checkbox in the dialog and select (using the combobox drop-down) the name of the model type we are passing from the Controller to the View. For this view we are passing a Dinner object (the fully qualified name for this type is: NerdDinner.Models.Dinner) as shown in Figure 1-65.
Unlike the previous template, where we chose to create an "Empty View," this time we will choose to automatically scaffold the view using a "Details" template. We can indicate this by changing the View content drop-down in the dialog above.
Scaffolding will generate an initial implementation of our details view template based on the Dinner model we are passing to it. This provides an easy way for us to quickly get started on our view template implementation.
When we click the Add button, Visual Studio will create a new Details.aspx view template file for us within our \Views\Dinners directory (Figure 1-66).
It will also open up our new Details.aspx view template within the code-editor. It will contain an initial scaffold implementation of a details view based on a Dinner model. The scaffolding engine uses .NET reflection to look at the public properties exposed on the class passed to it, and will add appropriate content based on each type it finds:
<asp:Content ID=" Title" ContentPlaceHolderID=" TitleContent" runat=" server"> Details</asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2>Details</h2> <fieldset> <legend>Fields</legend> <p> DinnerID: <%= Html.Encode(Model.DinnerID) %> </p> <p> Title: <%= Html.Encode(Model.Title) %> </p> <p> EventDate: <%= Html.Encode(String.Format("{0:g}", Model.EventDate)) %> </p> <p> Description: <%= Html.Encode(Model.Description) %> </p> <p> HostedBy: <%= Html.Encode(Model.HostedBy) %> </p> <p> ContactPhone: <%= Html.Encode(Model.ContactPhone) %> </p> <p> Address: <%= Html.Encode(Model.Address) %> </p> <p> Country: <%= Html.Encode(Model.Country) %> </p> <p> Latitude: <%= Html.Encode(String.Format("{0:F}", Model.Latitude)) %> </p> <p> Longitude: <%= Html.Encode(String.Format("{0:F}", Model.Longitude)) %> </p> </fieldset> <p> <%=Html.ActionLink("Edit", "Edit", new { id=Model.DinnerID }) %> | <%=Html.ActionLink("Back to List", "Index") %> </p></asp:Content>
We can request the /Dinners/Details/1 URL to see what this "details" scaffold implementation looks like in the browser. Using this URL will display one of the dinners we manually added to our database when we first created it (Figure 1-67).
This gets us up and running quickly, and provides us with an initial implementation of our Details.aspx view. We can then go and tweak it to customize the UI to our satisfaction.
When we look at the Details.aspx template more closely, we'll find that it contains static HTML as well as embedded rendering code. <% %> code nuggets execute code when the view template renders, and <%= %> code nuggets execute the code contained within them and then render the result to the output stream of the template.
We can write code within our View that accesses the Dinner model object that was passed from our controller using a strongly typed Model property. Visual Studio provides us with full code-IntelliSense when accessing this Model property within the editor (Figure 1-68).
Let's make some tweaks so that the source for our final Details view template looks like that below:
<asp:Content ID=" Title" ContentPlaceHolderID=" TitleContent" runat=" server"> Dinner: <%= Html.Encode(Model.Title) %></asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2><%= Html.Encode(Model.Title) %></h2> <p> <strong>When:</strong> <%= Model.EventDate.ToShortDateString() %> <strong>@</strong> <%= Model.EventDate.ToShortTimeString() %> </p> <p> <strong>Where:</strong> <%= Html.Encode(Model.Address) %>, <%= Html.Encode(Model.Country) %> </p> <p> <strong>Description:</strong> <%= Html.Encode(Model.Description) %> </p> <p> <strong>Organizer:</strong> <%= Html.Encode(Model.HostedBy) %> (<%= Html.Encode(Model.ContactPhone) %>) </p> <%= Html.ActionLink("Edit Dinner", "Edit", new { id=Model.DinnerID })%> | <%= Html.ActionLink("Delete Dinner"," Delete", new { id=Model.DinnerID})%></asp:Content>
When we access the /Dinners/Details/1 URL again, it will render like so (Figure 1-69):
Implementing the "Index" View Template
Let's now implement the "Index" view template — which will generate a listing of upcoming dinners. To do this we'll position our text cursor within the Index action method, and then right-click and choose the Add View menu command (or press Ctrl-M, Ctrl-V).
Within the Add View dialog (Figure 1-70), we'll keep the view template named Index and select the "Create a strongly-typed view" checkbox. This time we will choose to automatically generate a List view template, and select NerdDinner.Models.Dinner as the model type passed to the view (which because we have indicated we are creating a List scaffold will cause the Add View dialog to assume we are passing a sequence of Dinner objects from our Controller to the View):
When we click the Add button, Visual Studio will create a new Index.aspx view template file for us within our \Views\Dinners directory. It will scaffold an initial implementation within it that provides an HTML table listing of the Dinners we pass to the view.
When we run the application and access the /Dinners/ URL, it will render our list of dinners like so (Figure 1-71):
The table solution in Figure 1-71 gives us a grid-like layout of our Dinner data — which isn't quite what we want for our consumer-facing Dinner listing. We can update the Index.aspx view template and modify it to list fewer columns of data, and use a <ul> element to render them instead of a table using the code that follows:
<asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2>Upcoming Dinners</h2> <ul> <% foreach (var dinner in Model) { %> <li> <%= Html.Encode(dinner.Title) %> on <%= Html.Encode(dinner.EventDate.ToShortDateString())%> @ <%= Html.Encode(dinner.EventDate.ToShortTimeString())%> </li> <% } %> </ul></asp:Content>
We are using the var keyword within the foreach statement as we loop over each dinner in our model. Those unfamiliar with C# 3.0 might think that using var means that the Dinner object is latebound. It, instead, means that the compiler is using type-inference against the strongly typed Model property (which is of type IEnumerable<Dinner>) and compiling the local "dinner" variable as a Dinner type — which means we get full IntelliSense and compile-time checking for it within code blocks (Figure 1-72).
When we press the Refresh button on the /Dinners URL in our browser, our updated view now looks like Figure 1-73.
This is looking better — but isn't entirely there yet. Our last step is to enable end users to click individual dinners in the list and see details about them. We'll implement this by rendering HTML hyperlink elements that link to the Details action method on our DinnersController.
We can generate these hyperlinks within our Index view in one of two ways. The first is to manually create HTML <a> elements like Figure 1-74, where we embed <% %> blocks within the <a> HTML element:
An alternative approach we can use is to take advantage of the built-in Html.ActionLink helper method within ASP.NET MVC that supports programmatically creating an HTML <a> element that links to another action method on a Controller:
<%= Html.ActionLink(dinner.Title, "Details", new { id=dinner.DinnerID }) %>
The first parameter to the Html.ActionLink helper method is the link-text to display (in this case the title of the dinner), the second parameter is the Controller action name we want to generate the link to (in this case the "Details" method), and the third parameter is a set of parameters to send to the action (implemented as an anonymous type with property name/values). In this case we are specifying the id parameter of the dinner we want to link to, and because the default URL routing rule in ASP.NET MVC is {Controller}/{Action}/{id} the Html.ActionLink helper method will generate the following output:
<a href="/Dinners/Details/1">.NET Futures</a>
For our Index.aspx view we'll use the Html.ActionLink helper method approach and have each dinner in the list link to the appropriate details URL:
<asp:Content ID=" Title" ContentPlaceHolderID=" TitleContent" runat=" server"> Upcoming Dinners</asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2>Upcoming Dinners</h2> <ul> <% foreach (var dinner in Model) { %> <li> <%= Html.ActionLink(dinner.Title, "Details",new { id=dinner.DinnerID }) %> on <%= Html.Encode(dinner.EventDate.ToShortDateString())%> @ <%= Html.Encode(dinner.EventDate.ToShortTimeString())%> </li> <% } %> </ul></asp:Content>
And now when we hit the /Dinners URL, our dinner list looks like Figure 1-75:
When we click any of the dinners in the list, we'll navigate to see details about it (Figure 1-76):
Convention-Based Naming and the \Views Directory Structure
ASP.NET MVC applications, by default, use a convention-based directory naming structure when resolving view templates. This allows developers to avoid having to fully qualify a location path when referencing views from within a Controller class. By default ASP.NET MVC will look for the view template file within the \Views\[ControllerName]\ directory underneath the application.
For example, we've been working on the DinnersController class — which explicitly references three view templates: "Index", "Details", and "NotFound". ASP.NET MVC will, by default, look for these views within the \Views\Dinners directory underneath our application root directory (Figure 1-77).
Notice in Figure 1-77 how there are currently three controller classes within the project (DinnersController, HomeController, and AccountController — the last two were added by default when we created the project), and there are three subdirectories (one for each controller) within the \Views directory.
Views referenced from the Home and Accounts controllers will automatically resolve their view templates from the respective \Views\Home and \Views\Account directories. The \Views\Shared subdirectory provides a way to store view templates that are reused across multiple controllers within the application. When ASP.NET MVC attempts to resolve a view template, it will first check within the \Views\[Controller] specific directory, and if it can't find the view template there it will look within the \Views\Shared directory.
When it comes to naming individual view templates, the recommended guidance is to have the view template share the same name as the action method that caused it to render. For example, above our Index action method is using the "Index" view to render the view result, and the Details action method is using the "Details" view to render its results. This makes it easy to quickly see which template is associated with each action.
Developers do not need to explicitly specify the view template name when the view template has the same name as the action method being invoked on the controller. We can instead just pass the model object to the View helper method (without specifying the view name), and ASP.NET MVC will automatically infer that we want to use the \Views\[ControllerName]\[ActionName] view template on disk to render it.
This allows us to clean up our controller code a little, and avoid duplicating the name twice in our code:
public class DinnersController : Controller { DinnerRepository dinnerRepository = new DinnerRepository(); // // GET: /Dinners/ public ActionResult Index() { var dinners = dinnerRepository.FindUpcomingDinners().ToList(); return View(dinners); } // // GET: /Dinners/Details/2 public ActionResult Details(int id) { Dinner dinner = dinnerRepository.GetDinner(id); if (dinner == null) return View("NotFound"); else return View(dinner); }}
The previous code is all that is needed to implement a nice Dinner listing/details experience for the site.
We will now add action methods to implement three additional URLs: /Dinners/Edit/[id],/Dinners/Create, and /Dinners/Delete/[id]. These URLs will enable support for editing existing dinners, creating new dinners, and deleting dinners.
We will support both HTTP GET and HTTP POST verb interactions with these new URLs. HTTP GET requests to these URLs will display the initial HTML view of the data (a form populated with the Dinner data in the case of "edit," a blank form in the case of "create," and a delete confirmation screen in the case of "delete"). HTTP POST requests to these URLs will save/update/delete the Dinner data in our DinnerRepository (and from there to the database).
![b24-bluearrow.gif](http://mmlviewer.books24x7.com/book/id_29605/images/b24-bluearrow.gif)
URL
Verb
Purpose
/Dinners/Edit/[id]
GET
Display an editable HTML form populated with Dinner data.
POST
Save the form changes for a particular Dinner to the database.
/Dinners/Create
GET
Display an empty HTML form that allows users to define new Dinners.
POST
Create a new Dinner and save it in the database.
/Dinners/Delete/[id]
GET
Display a confirmation screen that asks the user whether they want to delete the specified dinner.
POST
Deletes the specified dinner from the database.
Let's begin by implementing the "edit" scenario.
Implementing the HTTP-GET Edit Action Method
We'll start by implementing the HTTP GET behavior of our edit action method. This method will be invoked when the /Dinners/Edit/[id] URL is requested. Our implementation will look like:
GET: /Dinners/Edit/2public ActionResult Edit(int id) { Dinner dinner = dinnerRepository.GetDinner(id); return View(dinner);}
The code above uses the DinnerRepository to retrieve a Dinner object. It then renders a view template using the Dinner object. Because we haven't explicitly passed a template name to the View helper method, it will use the convention based default path to resolve the view template: /Views/Dinners/Edit.aspx.
Let's now create this view template. We will do this by right-clicking within the Edit method and selecting the Add View context menu command (Figure 1-78).
Within the Add View dialog, we'll indicate that we are passing a Dinner object to our view template as its model, and choose to auto-scaffold an Edit template (Figure 1-79).
When we click the Add button, Visual Studio will add a new Edit.aspx view template file for us within the \Views\Dinners directory. It will also open up the new Edit.aspx view template within the code-editor — populated with an initial "Edit" scaffold implementation like that in Figure 1-80.
Let's make a few changes to the default "Edit" scaffold generated, and update the Edit view template to have the content below (which removes a few of the properties we don't want to expose):
<asp:Content ID=" Title" ContentPlaceHolderID=" TitleContent" runat=" server"> Edit: <%=Html.Encode(Model.Title) %></asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2>Edit Dinner</h2> <%= Html.ValidationSummary("Please correct the errors and try again.") %> <% using (Html.BeginForm()) { %> <fieldset> <p> <label for=" Title">Dinner Title:</label> <%= Html.TextBox("Title") %> <%= Html.ValidationMessage("Title", "*") %> </p> <p> <label for=" EventDate">Event Date:</label> <%= Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate)) %> <%= Html.ValidationMessage("EventDate", "*") %> </p> <p> <label for=" Description">Description:</label> <%= Html.TextArea("Description") %> <%= Html.ValidationMessage("Description", "*")%> </p> <p> <label for=" Address">Address:</label> <%= Html.TextBox("Address") %> <%= Html.ValidationMessage("Address", "*") %> </p> <p> <label for=" Country">Country:</label> <%= Html.TextBox("Country") %> <%= Html.ValidationMessage("Country", "*") %> </p> <p> <label for=" ContactPhone">Contact Phone #:</label> <%= Html.TextBox("ContactPhone") %> <%= Html.ValidationMessage("ContactPhone", "*") %> </p> <p> <label for=" Latitude">Latitude:</label> <%= Html.TextBox("Latitude") %> <%= Html.ValidationMessage("Latitude", "*") %> </p> <p> <label for=" Longitude">Longitude:</label> <%= Html.TextBox("Longitude") %> <%= Html.ValidationMessage("Longitude", "*") %> </p> <p> <input type=" submit" value=" Save" /> </p> </fieldset> <% } %></asp:Content>
When we run the application and request the /Dinners/Edit/1 URL we will see the page in Figure 1-81:
The HTML markup generated by our view looks like that below. It is standard HTML — with a <form> element that performs an HTTP POST to the /Dinners/Edit/1 URL when the Save <input type=" submit"/> button is pushed. A HTML <input type=" text"/> element has been output for each editable property (Figure 1-82).
Html.BeginForm and Html.TextBox Html Helper Methods
Our Edit.aspx view template is using several "Html Helper" methods: Html.ValidationSummary, Html.BeginForm, Html.TextBox, and Html.ValidationMessage. In addition to generating HTML markup for us, these helper methods provide built-in error handling and validation support.
Html.BeginForm Helper Method
The Html.BeginForm helper method is what output the HTML <form> element in our markup. In our Edit.aspx view template, you'll notice that we are applying a C# "using" statement when using this method. The open curly brace indicates the beginning of the <form> content, and the closing curly brace is what indicates the end of the </form> element:
<% using (Html.BeginForm()) { %> <fieldset> <! " Fields Omitted for Brevity " > <p> <input type=" submit" value=" Save" /> </p> </fieldset><% } %>
Alternatively, if you find the "using" statement approach unnatural for a scenario like this, you can use a Html.BeginForm and Html.EndForm combination (which does the same thing):
<% Html.BeginForm(); %> <fieldset> <! " Fields Omitted for Brevity " > <p> <input type=" submit" value=" Save" /> </p> </fieldset><% Html.EndForm(); %>
Calling Html.BeginForm without any parameters will cause it to output a form element that does an HTTP-POST to the current request's URL. That is why our Edit view generates a <form action="/Dinners/Edit/1" method=" post"> element. We could have alternatively passed explicit parameters to Html.BeginForm if we wanted to post to a different URL.
Html.TextBox Helper Method
Our Edit.aspx view uses the Html.TextBox helper method to output <input type=" text"/> elements:
<%= Html.TextBox("Title") %>
The Html.TextBox method above takes a single parameter — which is being used to specify both the id/name attributes of the <input type=" text"/> element to output, as well as the model property to populate the textbox value from. For example, the Dinner object we passed to the Edit view had a "Title" property value of .NET Futures, and so our Html.TextBox("Title") method call output is: <input id=" Title" name=" Title" type=" text" value=".NET Futures" />.
Alternatively, we can use the first Html.TextBox parameter to specify the id/name of the element, and then explicitly pass in the value to use as a second parameter:
<%= Html.TextBox("Title", Model.Title)%>
Often we'll want to perform custom formatting on the value that is output. The String.Format static method built into .NET is useful for these scenarios. Our Edit.aspx view template is using this to format the EventDate value (which is of type DateTime) so that it doesn't show seconds for the time:
<%= Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate)) %>
A third parameter to Html.TextBox can optionally be used to output additional HTML attributes. The code-snippet below demonstrates how to render an additional size="30" attribute and a class=" mycssclass" attribute on the <input type=" text"/> element. Note how we are escaping the name of the class attribute using a @ character because class is a reserved keyword in C#:
<%= Html.TextBox("Title", Model.Title, new { size=30, @class=" myclass" })%>
Implementing the HTTP-POST Edit Action Method
We now have the HTTP-GET version of our Edit action method implemented. When a user requests the /Dinners/Edit/1 URL they receive an HTML page like the one in Figure 1-83:
Pressing the Save button causes a form post to the /Dinners/Edit/1 URL, and submits the HTML <input> form values using the HTTP POST verb. Let's now implement the HTTP POST behavior of our edit action method — which will handle saving the dinner.
We'll begin by adding an overloaded Edit action method to our DinnersController that has an "AcceptVerbs" attribute on it that indicates it handles HTTP POST scenarios:
POST: /Dinners/Edit/2[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection formValues) { ...}
When the [AcceptVerbs] attribute is applied to overloaded action methods, ASP.NET MVC automatically handles dispatching requests to the appropriate action method depending on the incoming HTTP verb. HTTP POST requests to /Dinners/Edit/[id] URLs will go to the above Edit method, while all other HTTP verb requests to /Dinners/Edit/[id] URLs will go to the first Edit method we implemented (which did not have an [AcceptVerbs] attribute).
Retrieving Form Post Values
There are a variety of ways we can access posted form parameters within our HTTP POST Edit method. One simple approach is to just use the Request property on the Controller base class to access the form collection and retrieve the posted values directly:
POST: /Dinners/Edit/2[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection formValues) { // Retrieve existing dinner Dinner dinner = dinnerRepository.GetDinner(id); // Update dinner with form posted values dinner.Title = Request.Form["Title"]; dinner.Description = Request.Form["Description"]; dinner.EventDate = DateTime.Parse(Request.Form["EventDate"]); dinner.Address = Request.Form["Address"]; dinner.Country = Request.Form["Country"]; dinner.ContactPhone = Request.Form["ContactPhone"]; // Persist changes back to database dinnerRepository.Save(); // Perform HTTP redirect to details page for the saved Dinner return RedirectToAction("Details", new { id = dinner.DinnerID });}
The approach in the previous code is a little verbose, though, especially once we add error handling logic.
A better approach for this scenario is to leverage the built-in UpdateModel helper method on the Controller base class. It supports updating the properties of an object we pass it using the incoming form parameters. It uses reflection to determine the property names on the object, and then automatically converts and assigns values to them based on the input values submitted by the client.
We could use the UpdateModel method to implement our HTTP-POST Edit action using this code:
POST: /Dinners/Edit/2[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection formValues) { Dinner dinner = dinnerRepository.GetDinner(id); UpdateModel(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id = dinner.DinnerID });}
We can now visit the /Dinners/Edit/1 URL, and change the title of our dinner (Figure 1-84).
When we click the Save button, we'll perform a form post to our Edit action, and the updated values will be persisted in the database. We will then be redirected to the Details URL for the dinner (which will display the newly saved values like those in Figure 1-85).
Handling Edit Errors
Our current HTTP-POST implementation works fine — except when there are errors.
When a user makes a mistake editing a form, we need to make sure that the form is redisplayed with an informative error message that guides them to fix it. This includes cases where an end-user posts incorrect input (for example: a malformed date string), as well as cases where the input format is valid but there is a business rule violation. When errors occur, the form should preserve the input data the user originally entered so that they don't have to refill their changes manually. This process should repeat as many times as necessary until the form successfully completes.
ASP.NET MVC includes some nice built-in features that make error handling and form redisplay easy. To see these features in action, let's update our Edit action method with the following code:
POST: /Dinners/Edit/2[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection formValues) { Dinner dinner = dinnerRepository.GetDinner(id); try { UpdateModel(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id=dinner.DinnerID }); } catch { foreach (var issue in dinner.GetRuleViolations()) { ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage); } return View(dinner); }}
The previous code is similar to our previous implementation — except that we are now wrapping a try/catch error handling block around our work. If an exception occurs either when calling UpdateModel, or when we try and save the DinnerRepository (which will raise an exception if the Dinner object we are trying to save is invalid because of a rule violation), our catch error handling block will execute. Within it, we loop over any rule violations that exist in the Dinner object and add them to a ModelState object (which we'll discuss shortly). We then redisplay the view.
To see this working let's re-run the application, edit a dinner, and change it to have an empty Title, an Event Date of BOGUS, and use a UK phone number with a country value of USA. When we press the Save button our HTTP POST Edit method will not be able to save the dinner (because there are errors) and will redisplay the form in Figure 1-86.
Our application has a decent error experience. The text elements with the invalid input are highlighted in red, and validation error messages are displayed to the end user about them. The form is also preserving the input data the user originally entered — so that they don't have to refill anything.
How, you might ask, did this occur? How did the Title, Event Date, and Contact Phone textboxes highlight themselves in red and know to output the originally entered user values? And how did error messages get displayed in the list at the top? The good news is that this didn't occur by magic — rather it was because we used some of the built-in ASP.NET MVC features that make input validation and error handling scenarios easy.
Understanding ModelState and the Validation HTML Helper Methods
Controller classes have a ModelState property collection that provides a way to indicate that errors exist with a model object being passed to a View. Error entries within the ModelState collection identify the name of the model property with the issue (for example: "Title", "EventDate", or "ContactPhone"), and allow a human-friendly error message to be specified (for example: "Title is required").
The UpdateModel() helper method automatically populates the ModelState collection when it encounters errors while trying to assign form values to properties on the model object. For example, our Dinner object's EventDate property is of type DateTime. When the UpdateModel method was unable to assign the string value BOGUS to it in the previous scenario, the UpdateModel method added an entry to the ModelState collection indicating an assignment error had occurred with that property.
Developers can also write code to explicitly add error entries into the ModelState collection as we are doing below within our "catch" error handling block, which is populating the ModelState collection with entries based on the active Rule Violations in the Dinner object:
[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection formValues) { Dinner dinner = dinnerRepository.GetDinner(id); try { UpdateModel(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id=dinner.DinnerID }); } catch { foreach (var issue in dinner.GetRuleViolations()) { ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage); } return View(dinner); }}
Html Helper Integration with ModelState
HTML helper methods — like Html.TextBox - check the ModelState collection when rendering output. If an error for the item exists, they render the user-entered value and a CSS error class.
For example, in our "Edit" view we are using the Html.TextBox helper method to render the EventDate of our Dinner object:
<%= Html.TextBox("EventDate", String.Format("{0:g}", Model.EventDate)) %>
When the view was rendered in the error scenario, the Html.TextBox method checked the ModelState collection to see if there were any errors associated with the "EventDate" property of our Dinner object. When it determined that there was an error, it rendered the submitted user input ("BOGUS") as the value, and added a CSS error class to the <input type=" textbox"/> markup it generated:
<input class="input-validation-error " id=" EventDate" name=" EventDate" type=" text"value=" BOGUS" />
You can customize the appearance of the CSS error class to look however you want. The default CSS error class — input-validation-error - is defined in the \content\site.css stylesheet and looks like the code below:
.input-validation-error{ border: 1px solid #ff0000; background-color: #ffeeee;}
This CSS rule is what caused our invalid input elements to be highlighted, as in Figure 1-87.
Html .ValidationMessage Helper Method
The Html.ValidationMessage helper method can be used to output the ModelState error message associated with a particular model property:
<%= Html.ValidationMessage("EventDate") %>
The previous code outputs: <span class="field-validation-error "> The value ‘BOGUS' is invalid</span>
The Html.ValidationMessage helper method also supports a second parameter that allows developers to override the error text message that is displayed:
<%= Html.ValidationMessage("EventDate", "*") %>
The previous code outputs: <span class="field-validation-error ">*</span> instead of the default error text when an error is present for the EventDate property.
Html.ValidationSummary() Helper Method
The Html.ValidationSummary helper method can be used to render a summary error message, accompanied by a <ul><li/></ul> list of all detailed error messages in the ModelState collection (Figure 1-88):
The Html.ValidationSummary helper method takes an optional string parameter — which defines a summary error message to display above the list of detailed errors:
<%= Html.ValidationSummary("Please correct the errors and try again.") %>
You can optionally use CSS to override what the error list looks like.
Using a AddRuleViolations Helper Method
Our initial HTTP-POST Edit implementation used a foreach statement within its catch block to loop over the Dinner object's Rule Violations and add them to the controller's ModelState collection:
catch { foreach (var issue in dinner.GetRuleViolations()) { ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage); } return View(dinner);}
We can make this code a little cleaner by adding a ControllerHelpers class to the NerdDinner project, and implement an AddRuleViolations extension method within it that adds a helper method to the ASP.NET MVC ModelStateDictionary class. This extension method can encapsulate the logic necessary to populate the ModelStateDictionary with a list of RuleViolation errors:
public static class ControllerHelpers { public static void AddRuleViolations(this ModelStateDictionary modelState, IEnumerable<RuleViolation> errors) { foreach (RuleViolation issue in errors) { modelState.AddModelError(issue.PropertyName, issue.ErrorMessage); } }}
We can then update our HTTP-POST Edit action method to use this extension method to populate the ModelState collection with our Dinner Rule Violations.
Complete Edit Action Method Implementations
The following code implements all of the controller logic necessary for our Edit scenario:
GET: /Dinners/Edit/2public ActionResult Edit(int id) { Dinner dinner = dinnerRepository.GetDinner(id); return View(dinner);} POST: /Dinners/Edit/2[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection formValues) { Dinner dinner = dinnerRepository.GetDinner(id); try { UpdateModel(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id=dinner.DinnerID }); } catch { ModelState.AddRuleViolations(dinner.GetRuleViolations()); return View(dinner); }}
The nice thing about our Edit implementation is that neither our Controller class nor our view template has to know anything about the specific validation or business rules being enforced by our Dinner model. We can add additional rules to our model in the future and do not have to make any code changes to our controller or view in order for them to be supported. This provides us with the flexibility to easily evolve our application requirements in the future with a minimum of code changes.
Implementing the HTTP-GET Create Action Method
We've finished implementing the Edit behavior of our DinnersController class. Let's now move on to implement the Create support on it — which will enable users to add new dinners.
We'll begin by implementing the HTTP GET behavior of our create action method. This method will be called when someone visits the /Dinners/Create URL. Our implementation looks like:
GET: /Dinners/Createpublic ActionResult Create() { Dinner dinner = new Dinner() { EventDate = DateTime.Now.AddDays(7) }; return View(dinner);}
The previous code creates a new Dinner object, and assigns its EventDate property to be one week in the future. It then renders a View that is based on the new Dinner object. Because we haven't explicitly passed a name to the View helper method, it will use the convention based default path to resolve the view template: /Views/Dinners/Create.aspx.
Let's now create this view template. We can do this by right-clicking within the Create action method and selecting the Add View context menu command. Within the Add View dialog we'll indicate that we are passing a Dinner object to the view template, and choose to auto-scaffold a Create template (Figure 1-89).
When we click the Add button, Visual Studio will save a new scaffold-based Create.aspx view to the \Views\Dinners directory, and open it up within the IDE (Figure 1-90).
Let's make a few changes to the default "create" scaffold file that was generated for us, and modify it up to look like the code below:
<asp:Content ID=" Title" ContentPlaceHolderID=" TitleContent" runat=" server"> Host a Dinner</asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2>Host a Dinner</h2> <%= Html.ValidationSummary("Please correct the errors and try again.") %> <% using (Html.BeginForm()) {%> <fieldset> <p> <label for=" Title">Title:</label> <%= Html.TextBox("Title") %> <%= Html.ValidationMessage("Title", "*") %> </p> <p> <label for=" EventDate">Event Date:</label> <%= Html.TextBox("EventDate") %> <%= Html.ValidationMessage("EventDate", "*") %> </p> <p> <label for=" Description">Description:</label> <%= Html.TextArea("Description") %> <%= Html.ValidationMessage("Description", "*") %> </p> <p> <label for=" Address">Address:</label> <%= Html.TextBox("Address") %> <%= Html.ValidationMessage("Address", "*") %> </p> <p> <label for=" Country">Country:</label> <%= Html.TextBox("Country") %> <%= Html.ValidationMessage("Country", "*") %> </p> <p> <label for=" ContactPhone">ContactPhone:</label> <%= Html.TextBox("ContactPhone") %> <%= Html.ValidationMessage("ContactPhone", "*") %> </p> <p> <label for=" Latitude">Latitude:</label> <%= Html.TextBox("Latitude") %> <%= Html.ValidationMessage("Latitude", "*") %> </p> <p> <label for=" Longitude">Longitude:</label> <%= Html.TextBox("Longitude") %> <%= Html.ValidationMessage("Longitude", "*") %> </p> <p> <input type=" submit" value=" Save" /> </p> </fieldset> <% } %></asp:Content>
And now when we run our application and access the /Dinners/Create URL within the browser, it will render the UI as in Figure 1-91 from our Create action implementation.
Implementing the HTTP-POST Create Action Method
We have the HTTP-GET version of our Create action method implemented. When a user clicks the Save button, it performs a form post to the /Dinners/Create URL, and submits the HTML <input> form values using the HTTP POST verb.
Let's now implement the HTTP POST behavior of our create action method. We'll begin by adding an overloaded Create action method to our DinnersController that has an AcceptVerbs attribute on it that indicates it handles HTTP POST scenarios:
POST: /Dinners/Create[AcceptVerbs(HttpVerbs.Post)]public ActionResult Create(FormCollection formValues) { ...}
There are a variety of ways we can access the posted form parameters within our HTTP-POST-enabled Create method.
One approach is to create a new Dinner object and then use the UpdateModel helper method (as we did with the Edit action) to populate it with the posted form values. We can then add it to our DinnerRepository, persist it to the database, and redirect the user to our Details action to show the newly created dinner, using the following code:
POST: /Dinners/Create[AcceptVerbs(HttpVerbs.Post)]public ActionResult Create(FormCollection formValues) { Dinner dinner = new Dinner(); try { UpdateModel(dinner); dinnerRepository.Add(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new {id=dinner.DinnerID}); } catch { ModelState.AddRuleViolations(dinner.GetRuleViolations()); return View(dinner); }}
Alternatively, we can use an approach where we have our Create action method take a Dinner object as a method parameter. ASP.NET MVC will then automatically instantiate a new Dinner object for us, populate its properties using the form inputs, and pass it to our action method:
POST: /Dinners/Create[AcceptVerbs(HttpVerbs.Post)]public ActionResult Create(Dinner dinner) { if (ModelState.IsValid) { try { dinner.HostedBy = "SomeUser"; dinnerRepository.Add(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new {id = dinner.DinnerID }); } catch { ModelState.AddRuleViolations(dinner.GetRuleViolations()); } } return View(dinner);}
Our action method in the previous code verifies that the Dinner object has been successfully populated with the form post values by checking the ModelState.IsValid property. This will return false if there are input conversion issues (for example: a string of "BOGUS" for the EventDate property), and if there are any issues, our action method redisplays the form.
If the input values are valid, then the action method attempts to add and save the new dinner to the DinnerRepository. It wraps this work within a try/catch block and redisplays the form if there are any business rule violations (which would cause the dinnerRepository.Save method to raise an exception).
To see this error handling behavior in action, we can request the /Dinners/Create URL and fill out details about a new dinner. Incorrect input or values will cause the create form to be redisplayed with the errors highlighted in Figure 1-92.
Notice how our Create form is honoring the exact same validation and business rules as our Edit form. This is because our validation and business rules were defined in the model, and were not embedded within the UI or controller of the application. This means we can later change/evolve our validation or business rules in a single place and have them apply throughout our application. We will not have to change any code within either our Edit or Create action methods to automatically honor any new rules or modifications to existing ones.
When we fix the input values and click the Save button again, our addition to the DinnerRepository will succeed, and a new dinner will be added to the database. We will then be redirected to the /Dinners/Details/[id] URL — where we will be presented with details about the newly created dinner (Figure 1-93):
Implementing the HTTP-GET Delete Action Method
Let's now add "Delete" support to our DinnersController.
We'll begin by implementing the HTTP GET behavior of our delete action method. This method will get called when someone visits the /Dinners/Delete/[id] URL . Below is the implementation:
HTTP GET: /Dinners/Delete/1public ActionResult Delete(int id) { Dinner dinner = dinnerRepository.GetDinner(id); if (dinner == null) return View("NotFound"); else return View(dinner);}
The action method attempts to retrieve the dinner to be deleted. If the dinner exists it renders a View based on the Dinner object. If the object doesn't exist (or has already been deleted) it returns a View that renders the "NotFound" view template we created earlier for our "Details" action method.
We can create the "Delete" view template by right-clicking within the Delete action method and selecting the "Add View" context menu command. Within the "Add View" dialog we'll indicate that we are passing a Dinner object to our view template as its model, and choose to create an empty template (Figure 1-94):
When we click the Add button, Visual Studio will add a new Delete.aspx view template file for us within our \Views\Dinners directory. We'll add some HTML and code to the template to implement a delete confirmation screen as shown below:
<asp:Content ID=" Title" ContentPlaceHolderID=" head" runat=" server"> Delete Confirmation: <%=Html.Encode(Model.Title) %></asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2> Delete Confirmation </h2> <div> <p>Please confirm you want to cancel the dinner titled: <i> <%=Html.Encode(Model.Title) %>? </i> </p> </div> <% using (Html.BeginForm()) { %> <input name=" confirmButton" type=" submit" value=" Delete" /> <% } %></asp:Content>
The code above displays the title of the dinner to be deleted, and outputs a <form> element that does a POST to the /Dinners/Delete/[id] URL if the end user clicks the Delete button within it.
When we run our application and access the /Dinners/Delete/[id] URL for a valid Dinner object, it renders the UI as in Figure 1-95.
Implementing the HTTP-POST Delete Action Method
We now have the HTTP-GET version of our Delete action method implemented that displays a delete confirmation screen. When an end user clicks the Delete button, it will perform a form post to the /Dinners/Dinner/[id] URL.
Let's now implement the HTTP POST behavior of the delete action method using the code that follows:
HTTP POST: /Dinners/Delete/1[AcceptVerbs(HttpVerbs.Post)]public ActionResult Delete(int id, string confirmButton) { Dinner dinner = dinnerRepository.GetDinner(id); if (dinner == null) return View("NotFound"); dinnerRepository.Delete(dinner); dinnerRepository.Save(); return View("Deleted");}
The HTTP-POST version of our Delete action method attempts to retrieve the Dinner object to delete. If it can't find it (because it has already been deleted) it renders our "NotFound" template. If it finds the dinner, it deletes it from the DinnerRepository. It then renders a "Deleted" template.
To implement the "Deleted" template, we'll right-click in the action method and choose the Add View context menu. We'll name our view Deleted and have it be an empty template (and not take a strongly typed model object). We'll then add some HTML content to it:
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server"> Dinner Deleted</asp:Content><asp:Content ID=" Main" ContentPlaceHolderID=" MainContent" runat=" server"> <h2>Dinner Deleted</h2> <div> <p>Your dinner was successfully deleted.</p> </div> <div> <p><a href="/dinners">Click for Upcoming Dinners</a></p> </div></asp:Content>
And now when we run our application and access the /Dinners/Delete/[id] URL for a valid Dinner object, it will render our Dinner delete confirmation screen as in Figure 1-96.
When we click the Delete button, it will perform an HTTP-POST to the /Dinners/Delete/[id] URL, which will delete the dinner from our database, and display our "Deleted" view template (Figure 1-97).
Model Binding Security
We've discussed two different ways to use the built-in model-binding features of ASP.NET MVC. The first using the UpdateModel method to update properties on an existing model object, and the second using ASP.NET MVC's support for passing model objects in as action method parameters. Both of these techniques are very powerful and extremely useful.
This power also brings with it responsibility. It is important to always be paranoid about security when accepting any user input, and this is also true when binding objects to form input. You should be careful to always HTML encode any user-entered values to avoid HTML and JavaScript injection attacks, and be careful of SQL injection attacks (note: we are using LINQ to SQL for our application, which automatically encodes parameters to prevent these types of attacks). You should never rely on client-side validation alone, and always employ server-side validation to guard against hackers attempting to send you bogus values.
One additional security item to make sure you think about when using the binding features of ASP. NET MVC is the scope of the objects you are binding. Specifically, you want to make sure you understand the security implications of the properties you are allowing to be bound, and make sure you only allow those properties that really should be updatable by an end user to be updated.
By default, the UpdateModel method will attempt to update all properties on the model object that match incoming form parameter values. Likewise, objects passed as action method parameters also, by default, can have all of their properties set via form parameters.
Locking Down Binding on a Per-Usage Basis
You can lock down the binding policy on a per-usage basis by providing an explicit include list of properties that can be updated. This can be done by passing an extra string array parameter to the UpdateModel method like the following code:
string[] allowedProperties = new[]{ "Title", "Description", "ContactPhone", "Address", "EventDate", "Latitude", "Longitude"};UpdateModel(dinner, allowedProperties);
Objects passed as action method parameters also support a [Bind] attribute that enables an include list of allowed properties to be specified like the code that follows:
POST: /Dinners/Create[AcceptVerbs(HttpVerbs.Post)]public ActionResult Create([Bind(Include=" Title,Address")] Dinner dinner) { ...}
Locking Down Binding on a Type Basis
You can also lock down the binding rules on a per-type basis. This allows you to specify the binding rules once and then have them apply in all scenarios (including both UpdateModel and action method parameter scenarios) across all controllers and action methods.
You can customize the per-type binding rules by adding a [Bind] attribute onto a type, or by registering it within the Global.asax file of the application (useful for scenarios where you don't own the type). You can then use the Bind attribute's Include and Exclude properties to control which properties are bindable for the particular class or interface.
We'll use this technique for the Dinner class in our NerdDinner application, and add a [Bind] attribute to it that restricts the list of bindable properties to the following:
[Bind(Include="Title,Description,EventDate,Address,Country,ContactPhone,Latitude, Longitude")]public partial class Dinner {}
Notice we are not allowing the RSVPs collection to be manipulated via binding, nor are we allowing the DinnerID or HostedBy properties to be set via binding. For security reasons we'll instead only manipulate these particular properties using explicit code within our action methods.
CRUD Wrap-Up
ASP.NET MVC includes a number of built-in features that help with implementing form posting scenarios. We used a variety of these features to provide CRUD UI support on top of our DinnerRepository.
We are using a model-focused approach to implement our application. This means that all our validation and business rule logic is defined within our model layer — and not within our controllers or views. Neither our Controller class nor our view templates know anything about the specific business rules being enforced by our Dinner model class.
This will keep our application architecture clean and make it easier to test. We can add additional business rules to our model layer in the future and not have to make any code changes to our Controller or View in order for them to be supported. This is going to provide us with a great deal of agility to evolve and change our application in the future.
Our DinnersController now enables dinner listings/details, as well as create, edit, and delete support. The complete code for the class can be found below:
public class DinnersController : Controller { DinnerRepository dinnerRepository = new DinnerRepository(); // // GET: /Dinners/ public ActionResult Index() { var dinners = dinnerRepository.FindUpcomingDinners().ToList(); return View(dinners); } // // GET: /Dinners/Details/2 public ActionResult Details(int id) { Dinner dinner = dinnerRepository.GetDinner(id); if (dinner == null) return View("NotFound"); else return View(dinner); } // // GET: /Dinners/Edit/2 public ActionResult Edit(int id) { Dinner dinner = dinnerRepository.GetDinner(id); return View(dinner); } // // POST: /Dinners/Edit/2 [AcceptVerbs(HttpVerbs.Post)] public ActionResult Edit(int id, FormCollection formValues) { Dinner dinner = dinnerRepository.GetDinner(id); try { UpdateModel(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id = dinner.DinnerID }); } catch { ModelState.AddRuleViolations(dinner.GetRuleViolations()); return View(dinner); } } // // GET: /Dinners/Create public ActionResult Create() { Dinner dinner = new Dinner() { EventDate = DateTime.Now.AddDays(7) }; return View(dinner); } // // POST: /Dinners/Create [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(Dinner dinner) { if (ModelState.IsValid) { try { dinner.HostedBy = "SomeUser"; dinnerRepository.Add(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new{id=dinner.DinnerID}); } catch { ModelState.AddRuleViolations(dinner.GetRuleViolations()); } } return View(dinner); } // // HTTP GET: /Dinners/Delete/1 public ActionResult Delete(int id) { Dinner dinner = dinnerRepository.GetDinner(id); if (dinner == null) return View("NotFound"); else return View(dinner); } // // HTTP POST: /Dinners/Delete/1 [AcceptVerbs(HttpVerbs.Post)] public ActionResult Delete(int id, string confirmButton) { Dinner dinner = dinnerRepository.GetDinner(id); if (dinner == null) return View("NotFound"); dinnerRepository.Delete(dinner); dinnerRepository.Save(); return View("Deleted"); }}
![]() |
ViewData and ViewModel
We've covered a number of form post scenarios, and discussed how to implement create, update and delete (CRUD) support. We'll now take our DinnersController implementation further and enable support for richer form editing scenarios. While doing this we'll discuss two approaches that can be used to pass data from controllers to views: ViewData and ViewModel.
Passing Data from Controllers to View Templates
One of the defining characteristics of the MVC pattern is the strict separation of concerns it helps enforce between the different components of an application. Models, Controllers, and Views each have well defined roles and responsibilities, and they communicate amongst each other in well-defined ways. This helps promote testability and code reuse.
When a Controller class decides to render an HTML response back to a client, it is responsible for explicitly passing to the view template all of the data needed to render the response. View templates should never perform any data retrieval or application logic — and should instead limit themselves to only having rendering code that is driven off of the model/data passed to it by the controller.
Right now the model data being passed by our DinnersController class to our view templates is simple and straightforward — a list of Dinner objects in the case of Index, and a single Dinner object in the case of Details, Edit, Create, and Delete. As we add more UI capabilities to our application, we are often going to need to pass more than just this data to render HTML responses within our view templates. For example, we might want to change the Country field within our Edit and Create views from being an HTML textbox to a dropdownlist. Rather than hard-code the dropdownlist of country names in the view template, we might want to generate it from a list of supported countries that we populate dynamically. We will need a way to pass both the Dinner object and the list of supported countries from our controller to our view templates.
Let's look at two ways we can accomplish this.
Using the ViewData Dictionary
The Controller base class exposes a ViewData dictionary property that can be used to pass additional data items from Controllers to Views.
For example, to support the scenario where we want to change the Country textbox within our Edit view from being an HTML textbox to a dropdownlist, we can update our Edit action method to pass (in addition to a Dinner object) a SelectList object that can be used as the model of a countries dropdownlist.
GET: /Dinners/Edit/5[Authorize]public ActionResult Edit(int id) { Dinner dinner = dinnerRepository.GetDinner(id); ViewData["Countries"] = new SelectList(PhoneValidator.Countries, dinner.Country); return View(dinner);}
The constructor of the SelectList from the previous code is accepting a list of countries to populate the dropdownlist with, as well as the currently selected value.
We can then update our Edit.aspx view template to use the Html.DropDownList helper method instead of the Html.TextBox helper method we used previously:
<%= Html.DropDownList("Country", ViewData["Countries"] as SelectList) %>
The Html.DropDownList helper method in the previous line of code takes two parameters. The first is the name of the HTML form element to output. The second is the SelectList model we passed via the ViewData dictionary. We are using the C# "as" keyword to cast the type within the dictionary as a SelectList.
And now when we run our application and access the /Dinners/Edit/1 URL within our browser, we'll see that our edit UI has been updated to display a drop-down list of countries instead of a textbox (Figure 1-98):
Because we also render the Edit view template from the HTTP-POST Edit method (in scenarios when errors occur), we'll want to make sure that we also update this method to add the SelectList to ViewData when the view template is rendered in error scenarios:
POST: /Dinners/Edit/5[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection collection) { Dinner dinner = dinnerRepository.GetDinner(id); try { UpdateModel(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id=dinner.DinnerID }); } catch { ModelState.AddModelErrors(dinner.GetRuleViolations()); ViewData["countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country); return View(dinner); }}
And now our DinnersController edit scenario supports a drop-down list.
Using a ViewModel Pattern
The ViewData dictionary approach has the benefit of being fairly fast and easy to implement. Some developers don't like using string-based dictionaries, though, since typos can lead to errors that will not be caught at compile-time. The un-typed ViewData dictionary also requires using the "as" operator or casting when using a strongly typed language like C# in a view template.
An alternative approach that we could use is one often referred to as the ViewModel pattern. When using this pattern, we create strongly typed classes that are optimized for our specific view scenarios, and that expose properties for the dynamic values/content needed by our view templates. Our controller classes can then populate and pass these view-optimized classes to our view template to use. This enables typesafety, compile-time checking, and editor IntelliSense within view templates.
For example, to enable dinner form editing scenarios, we can create a DinnerFormViewModel class like the following code that exposes two strongly typed properties: a Dinner object and the SelectList model needed to populate the countries drop-down list:
public class DinnerFormViewModel { // Properties public Dinner Dinner { get; private set; } public SelectList Countries { get; private set; } // Constructor public DinnerFormViewModel(Dinner dinner) { Dinner = dinner; Countries = new SelectList(PhoneValidator.AllCountries, dinner.Country); }}
We can then update our Edit action method to create the DinnerFormViewModel using the Dinner object we retrieve from our repository, and then pass it to our view template:
GET: /Dinners/Edit/5[Authorize]public ActionResult Edit(int id) { Dinner dinner = dinnerRepository.GetDinner(id); return View(new DinnerFormViewModel(dinner));}
We'll then update our view template so that it expects a DinnerFormViewModel instead of a Dinner object by changing the Inherits attribute at the top of the edit.aspx page like so:
Inherits=" System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerFormViewModel>
Once we do this, the IntelliSense of the Model property within our view template will be updated to reflect the object model of the DinnerFormViewModel type we are passing it (see Figures 1-99 and 1-100):
We can then update our view code to work off of it. Notice in the following code how we are not changing the names of the input elements we are creating (the form elements will still be named "Title", "Country") — but we are updating the HTML Helper methods to retrieve the values using the DinnerFormViewModel class:
<p> <label for=" Title">Dinner Title:</label> <%= Html.TextBox("Title", Model.Dinner.Title) %> <%= Html.ValidationMessage("Title", "*") %></p><p> <label for=" Country">Country:</label> <%= Html.DropDownList("Country", Model.Countries) %> <%= Html.ValidationMessage("Country", "*") %></p>
We'll also update our Edit post method to use the DinnerFormViewModel class when rendering errors:
POST: /Dinners/Edit/5[AcceptVerbs(HttpVerbs.Post)]public ActionResult Edit(int id, FormCollection collection) { Dinner dinner = dinnerRepository.GetDinner(id); try { UpdateModel(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id=dinner.DinnerID }); } catch { ModelState.AddModelErrors(dinner.GetRuleViolations()); return View(new DinnerFormViewModel(dinner)); }}
We can also update our Create action methods to reuse the exact same DinnerFormViewModel class to enable the countries dropdownlist within those as well. The following code is the HTTP-GET implementation:
GET: /Dinners/Createpublic ActionResult Create() { Dinner dinner = new Dinner() { EventDate = DateTime.Now.AddDays(7) }; return View(new DinnerFormViewModel(dinner));}
The following code is the implementation of the HTTP-POST Create method:
POST: /Dinners/Create[AcceptVerbs(HttpVerbs.Post)]public ActionResult Create(Dinner dinner) { if (ModelState.IsValid) { try { dinner.HostedBy = "SomeUser"; dinnerRepository.Add(dinner); dinnerRepository.Save(); return RedirectToAction("Details", new { id=dinner.DinnerID }); } catch { ModelState.AddModelErrors(dinnerToCreate.GetRuleViolations()); } } return View(new DinnerFormViewModel(dinnerToCreate));}
And now both our Edit and Create screens support drop-down lists for picking the country.
Custom-Shaped ViewModel Classes
In the scenario above, our DinnerFormViewModel class directly exposes the Dinner model object as a property, along with a supporting SelectList model property. This approach works fine for scenarios where the HTML UI we want to create within our view template corresponds relatively closely to our domain model objects.
For scenarios where this isn't the case, one option that you can use is to create a custom-shaped ViewModel class whose object model is more optimized for consumption by the view — and which might look completely different from the underlying domain model object. For example, it could potentially expose different property names and/or aggregate properties collected from multiple model objects.
Custom-shaped ViewModel classes can be used both to pass data from controllers to views to render and to help handle form data posted back to a controller's action method. For this later scenario, you might have the action method update a ViewModel object with the form-posted data, and then use the ViewModel instance to map or retrieve an actual domain model object.
Custom-shaped ViewModel classes can provide a great deal of flexibility, and are something to investigate any time you find the rendering code within your view templates or the form-posting code inside your action methods starting to get too complicated. This is often a sign that your domain models don't cleanly correspond to the UI you are generating, and that an intermediate custom-shaped ViewModel class can help.