Introduction
ASP.NET Web Forms became popular due to the wide range of data bound controls such as GridView, DetailsView and ListView. The earlier versions of ASP.NET, however, were a bit rigid about how data was bound to these controls. Most of the time developers had to use one or the other data source control (SQL Data Source, Object Data Source, Entity Data Source etc.) to bind data from the underlying data store with the data bound controls. ASP.NET 4.5 provides a flexible alternative to data bind these controls - model binding. Using the new model binding features you can use methods instead of data source controls to perform CRUD operations. This article explains how the new model binding features can be used. You will also learn to use strongly typed data controls.
Data Binding in ASP.NET Server Controls
In the earlier versions of ASP.NET you used data bound controls and data source controls hand in hand to display and edit data. First, you needed to configure a data source control, such as Object Data Source or Entity Data Source and then set the DataSourceID property of a data bound control to the respective data source control. The data bound control would have one or more fields of some inbuilt type (BoundField for example) or template fields. If the control was using template fields you used Eval() data binding expression for one way data binding and Bind() data binding expression for two way data binding.
The following sample markup shows a GridView control bound with an Entity Data Source.
<asp:EntityDataSource ID="EntityDataSource1" runat="server"
ConnectionString="name=NorthwindEntities"
DefaultContainerName="NorthwindEntities" EnableFlattening="False"
EntitySetName="Customers"
Select="it.[CustomerID], it.[CompanyName], it.[ContactName], it.[Country]">
</asp:EntityDataSource>
<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False"
DataKeyNames="CustomerID" DataSourceID="EntityDataSource1">
<Columns>
<asp:BoundField DataField="CustomerID" HeaderText="CustomerID" ReadOnly="True"
SortExpression="CustomerID" />
<asp:BoundField DataField="CompanyName" HeaderText="CompanyName"
ReadOnly="True" SortExpression="CompanyName" />
<asp:BoundField DataField="ContactName" HeaderText="ContactName"
ReadOnly="True" SortExpression="ContactName" />
<asp:TemplateField HeaderText="Country" SortExpression="Country">
<EditItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Eval("Country") %>'></asp:Label>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("Country") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Notice how the DataSourceID property of the GridView is set to EntityDataSource1. Also, notice the use of Eval() and Bind() to bind the Country column.
Though data source controls work well in simple situations, developers often need complete control on how the data is fetched and how it is saved back to the database. This calls for a more code centered approach than the control driven approach. That is where the new model binding features come into the picture. Using model binding features you can write methods in your code behind class that perform the CRUD operations on the underlying data store. This way you have total control over the process of fetching and editing the data. Have a look at the following markup that uses the new model binding features:
<asp:GridView ID="GridView1" runat="server"
SelectMethod="GetCustomers"
dateMethod="UpdateCustomer"
DeleteMethod="DeleteCustomer"
... >
This time the GridView control doesn't use a data source control. The SelectMethod, UpdateMethod and DeleteMethod properties are set to method names GetCustomers, UpdateCustomers and DeleteCustomers respectively. These methods reside in the code behind. You will develop a complete version of this example in the following sections.
In addition to the code centered data binding, you can also use data within the controls in strongly typed manner. This is especially handy for template fields. Instead of using Eval() and Bind() data binding expressions, you can now use Item and BindItem properties respectively. These properties work along with the ItemType property. The use of these properties will be clear from the following markup.
<asp:GridView ID="GridView1" runat="server"
SelectMethod="GetCustomers"
UpdateMethod="UpdateCustomer"
DeleteMethod="DeleteCustomer"
ItemType="NorthwindModel.Customer"
... >
<Columns>
...
<asp:TemplateField HeaderText="Country" SortExpression="Country">
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Item.Country %>'></asp:Label>
</ItemTemplate
<EditItemTemplate>
<asp:TextBox ID="TextBox2" Text='<%# BindItem.Country %>' runat="server"></asp:TextBox>
</EditItemTemplate>
</asp:TemplateField>
...
Notice the code marked in bold letters. The ItemType property is set to an Entity Framework Data Model type - Customer. Once you do so, you can access properties of Customer class in a strongly typed fashion. Instead of Eval("Country") you now use Item.Country and instead of Bind("Country") you use BindItem.Country. This way there can't be any errors while specifying column names because they are available to you in the intellisense in strongly typed manner.
Creating a Sample Website
Now that you understand the basics of model binding and strongly typed data binding, let's create a web form that illustrates the complete process in detail. You will be using Customers table from the Northwind database in this example. Begin by creating a new ASP.NET Web Site. Then add an Entity Framework Data Model to its App_Code folder and create model class for the Customers table. The following figure shows the Customers model class in the Visual Studio designer
Open the default web form and place a GridView control on it. Configure the GridView to show CustomerID, CompanyName, ContactName and Country columns. Also add Edit and Delete command buttons. The following markup shows the GridView after adding these columns:
<asp:GridView ID="GridView1" runat="server"
SelectMethod="GetCustomers"
UpdateMethod="UpdateCustomer"
DeleteMethod="DeleteCustomer"
ItemType="NorthwindModel.Customer"
AllowSorting="True"
AllowPaging="True"
DataKeyNames="CustomerID" ...>
<Columns>
<asp:BoundField DataField="CustomerID" HeaderText="Customer ID" ReadOnly="True"
SortExpression="CustomerID"></asp:BoundField>
<asp:TemplateField HeaderText="Company Name" SortExpression="CompanyName">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" Text='<%# BindItem.CompanyName %>' runat="server"></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Item.CompanyName %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="ContactName" HeaderText="Contact Name"
SortExpression="ContactName"></asp:BoundField>
<asp:TemplateField HeaderText="Country" SortExpression="Country">
<EditItemTemplate>
<asp:TextBox ID="TextBox3" runat="server" Text='<%# BindItem.Country %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label3" runat="server" Text='<%# Item.Country %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField ShowEditButton="True" ButtonType="Button">
<ControlStyle Width="75px" />
</asp:CommandField>
<asp:CommandField ShowDeleteButton="True" ButtonType="Button">
<ControlStyle Width="75px" />
</asp:CommandField>
</Columns>
...
</asp:GridView>
The following figure shows the GridView at runtime:
Notice that the ItemType property of the GridView is set to NorthwindModel.Customer and its SelectMethod, UpdateMethod and DeleteMethod properties are set to GetCustomers, UpdateCustomer and DeleteCustomer respectively. You will be creating these methods in the following sections.
Selecting Data
In order to display data in the GridView you need to write a method that fetches data from the data store and then specify it in the SelectMethod property of GridView. So, switch to the code behind and add a method named GetCustomers() as shown below:
public IQueryable<Customer> GetCustomers()
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Customers
orderby item.CustomerID
select item;
return data;
}
The GetCustomers() method returns an IQueryable collection of Customer objects. Inside it fetches all of the records from the Customers table and returns them to the caller. Note that we have enabled paging in our GridView. When you enable paging for a GridView, behind the scenes it fetches only the required number of records as specified by the PageSize property.
Updating and Deleting Data
The methods UpdateCustomer() and DeleteCustomer() do the job of updating and deleting a Customer respectively. The UpdateCustomer() method is shown next:
public void UpdateCustomer(Customer c)
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Customers
where item.CustomerID == c.CustomerID
select item;
Customer obj = data.SingleOrDefault();
obj.CompanyName = c.CompanyName;
obj.ContactName = c.ContactName;
obj.Country = c.Country;
db.SaveChanges();
}
The UpdateCustomer() method accepts a parameter of type Customer. At runtime, when you modify a GridView row and click the Update button, UpdateCustomer() method will be called and the corresponding Customer object is passed as its parameter. Inside, you find out a specific Customer based on its CustomerID, assign the modified values and then save the changes to the database by calling the SaveChanges() method.
The DeleteCustomer() method follows a similar method signature but internally deletes a Customer. The following code shows the DeleteCustomer() method:
public void DeleteCustomer(Customer c)
{
NorthwindEntities db=new NorthwindEntities();
var data = from item in db.Customers
where item.CustomerID == c.CustomerID
select item;
Customer obj = data.SingleOrDefault();
db.DeleteObject(obj);
db.SaveChanges();
}
The DeleteCustomer() method receives the Customer being deleted as a parameter. Inside it fetches a Customer matching the CustomerID and deletes it using the DeleteObject() method.
Now, run the web form and try editing and deleting a few Customer records.
Adding Validation Support
Though the GridView is able to modify Customer data, there are no validations on the data being saved. One way to enforce the validations is to use a combination of template fields and validation controls. However, there is an easy alternative - Data Annotations.
Data Annotations are special attributes that you apply on the data model properties. They specify validation criteria such as maximum length, minimum length and specific data format (Email address, URL etc.).
To use Data Annotations add a new class in the App_Code folder and name it as CustomerValidations. The CustomerValidations class will store only validation information for the Customer class. You could have added the data annotation attributes directly to the Customer data model class but to avoid accidental overwriting during model recreation it is recommended to isolate them in a separate class.
public class CustomerValidations
{
[StringLength(5,MinimumLength=3 ,ErrorMessage="CustomerID must between 3-5 characters!")]
public string CustomerID { get; set; }
[StringLength(20, ErrorMessage = "Company Name must be upto 20 characters!")]
public string CompanyName { get; set; }
[Required]
public string ContactName { get; set; }
[Required]
[StringLength(15,ErrorMessage="Invalid country length!")]
public string Country { get; set; }
}
The CustomerValidations class makes use of data annotation attributes such as [StringLength] and [Required]. The former attribute ensures that the entered data is within a specified maximum length. The later attribute enforces that the property value must be provided. You can also use many other data annotation attributes depending on your requirement.
As of now the CustomerValidations class is a stand alone class. You need to associate it with the Customer data model class using the [MetadataType] attribute. To do so, open the Entity Framework Data Model file (Model.Designer.cs) and add the following piece of code:
[MetadataType(typeof(CustomerValidations))]
public partial class Customer : EntityObject
{
...
}
The [MetadataType] attribute specifies the type of the class that contains validation information (CustomerValidations in this case).
Now, open the web form and place a ValidationSummary control below the GridView. Set its ShowModelStateErrors property to true. The ShowModelStateErrors property controls whether data model validation errors should be displayed in the ValidationSummary or not.
<asp:ValidationSummary ID="ValidationSummary1" runat="server"
ShowModelStateErrors="true" ... />
In addition to displaying validation errors to the end user, you should also ensure that the changed Customer details are saved in the database only if model validations are successful. To do so, modify the UpdateCustomer() method as shown below:
public void UpdateCustomer(Customer c)
{
NorthwindEntities db = new NorthwindEntities();
var data = from item in db.Customers
where item.CustomerID == c.CustomerID
select item;
Customer obj = data.SingleOrDefault();
obj.CompanyName = c.CompanyName;
...
if (ModelState.IsValid)
{
db.SaveChanges();
}
}
Notice the code marked in bold letters. The UpdateCustomer() method now checks the state of the model using the ModelState.IsValid property. If all the model validations are successful, the IsValid property will return true and only then will changes be saved to the database.
Now, run the web form again and try to enter some invalid data. You should see validation errors similar to the following figure:
Summary
ASP.NET 4.5 supports model binding that allows a more code centric approach for performing CRUD operations on the data. Instead of using Data Source Controls, you can now write methods in the code behind file that select, insert, update and delete data from the underlying data store. The SelectMethod, InsertMethod, UpdateMethod and DeleteMethod properties of data bound controls are used to specify the respective method. You can also make use of Data Annotations for performing validations on the data being entered. Thus the new model binding features provide a more flexible and powerful approach to data binding.