ASP.NET 4.5 web forms support model binding features. Additionally they can make use of model validation using data annotation validators. The validation errors thrown by data annotation validators can be easily displayed in a web form using ValiationSummary control. While this arrangement works great, at times you want something customized. For example, consider the following two scenarios:
- You may want to display validation error messages thrown by data annotation validators in front of the respective data entry fields instead ( or in addition to ) of ValidationSummary control.
- Whenever there is any model error the ValidationSummary shows the error message but there is no indication (such as * mark) about the field causing the error. You may want to provide such an indication.
This article shows how model errors can be displayed inside a GridView column. You can use similar technique for other databound controls such as DetailsView and FormView.
To understand how validation errors can be displayed beside the data entry controls let's develop a web form as shown below:
Have a look at the above figure carefully. It shows validation error for the Country field beside the corresponding textbox. The above form uses Employee data model class. To add this class a new ADO.NET Entity Framework Data Model to your web application and configure it to select Employees table from the Northwind database. The following figure shows how the Employee data model class looks like:
Although the Employee class has many properties this example uses only four of them viz. EmployeeID, FirstName, LastName and Country.
Now add a web form to the project and place a GridView control on it. Design the GridView as per the following markup:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="10"
DataKeyNames="EmployeeID" ItemType="DataAnnotationInWebForms.Employee"
SelectMethod="GridView1_GetData" UpdateMethod="GridView1_UpdateItem">
<Columns>
<asp:BoundField DataField="EmployeeID" HeaderText="Employee ID" ReadOnly="True" />
<asp:TemplateField HeaderText="Full Name">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text="<%# BindItem.FirstName %>" Columns="10"></asp:TextBox>
<asp:TextBox ID="TextBox3" runat="server" Text="<%# BindItem.LastName %>" Columns="10"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox2"
ErrorMessage="First Name is required!" Font-Bold="True" ForeColor="Red"></asp:RequiredFieldValidator>
<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="TextBox3"
ErrorMessage="Last Name is required!" Font-Bold="True" ForeColor="Red"></asp:RequiredFieldValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text="<%# Item.FirstName %>"></asp:Label>
<asp:Label ID="Label3" runat="server" Text="<%# Item.LastName %>"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Country">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text="<%# BindItem.Country %>"></asp:TextBox>
<br />
<asp:Label ID="lblErr" runat="server" Font-Bold="True" ForeColor="Red" Text=""></asp:Label>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text="<%# Item.Country %>"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField ButtonType="Button" ShowEditButton="True" />
</Columns>
</asp:GridView>
The GridView consists of three columns - one BoundField and two TemplateFields. The BoundField is bound with EmployeeID column. The first TemplateField shows FirstName and LastName columns whereas the second TemplateField shows Country column.
The FirstName and LastName are validated using two RequiredFieldValidator controls. The Country textbox has a Label (lblErr) placed beside it and the country will be validated using data annotation validators. The validation erros (if any) will be displayed in lblErr.
The ItemType property of the GridView is set to the fully qualified name of the Employee data model class. This enables you to use Item and BindItem objects inside the ItemTemplate and EditItemTemplate respectively. Also, SelectMethod and UpdateMethod properties are set to GridView1_GetData and GridView1_UpdateItem respectively. You will write these two methods shortly.
Below the GridView drag-n-drop a ValidationSummary control and ensure that its ShowModelStateErrors property is set to True. This property causes the ValidationSummary control to show all the model errors in addition to errors thrown by validation controls.
Next step is to create a metadata class for the Employee model class so that data annotation validators can be attached to the properties of the model class. The EmployeeMetadata class that does this is shown below:
public class EmployeeMetadata
{
[Required]
[StringLength(20,ErrorMessage="First Name must be less than 20 characters.")]
public string FirstName { get; set; }
[Required]
[StringLength(20, ErrorMessage = "Last Name must be less than 20 characters.")]
public string LastName { get; set; }
[Required]
[StringLength(10, ErrorMessage = "Country must be less than 10 characters.")]
public string Country { get; set; }
}
As you can see the EmployeeMetadata class defines three properties FirstName, LastName and Country. All the three are decorated with [Required] and [StringLength] data annotation validators. Notice that the Country property. The [StringLength] attribute specifies its maximum length to 10 and also specifies an error message that will be passed on to the UI in case model validation fails.
The EmployeeMetadata class is currently not associated with the Employee model class in any way. To associate EmployeeMetadata with the Employee class create a partial class named Employee and use [MetadataType] attribute as shown below:
[MetadataType(typeof(EmployeeMetadata))]
public partial class Employee
{
}
The [MetadataType] attribute specifies the type of a class that is supplying metadata to the model class under consideration.
Next, write the GridView1_GetData method in the code behind file as shown below:
NorthwindEntities db = new NorthwindEntities();
public IQueryable<DataAnnotationInWebForms.Employee> GridView1_GetData()
{
var data = from emp in db.Employees
orderby emp.EmployeeID
select emp;
return data;
}
The GridView1_GetData() method returns IQueryable of Employee objects by executing a LINQ to Entities query against the Employees DbSet.
Now add GridView1_UpdateItem method as shown below:
public void GridView1_UpdateItem(int employeeid)
{
DataAnnotationInWebForms.Employee item = db.Employees.Find(employeeid);
if (item == null)
{
ModelState.AddModelError("", String.Format("Item with id {0} was not found", employeeid));
}
else
{
TryUpdateModel(item);
if (ModelState.IsValid)
{
//db.SaveChanges();
}
else
{
if (ModelState["Country"].Errors.Count > 0)
{
Label l = (Label)GridView1.Rows[GridView1.EditIndex].FindControl("lblErr");
l.Text = "";
foreach (ModelError err in ModelState["Country"].Errors)
{
l.Text += err.ErrorMessage + "<br/>";
}
}
}
}
}
Notice the code marked in bold letters. The code essentially checks the ModelState.IsValid property to determine whether there are any model errors. If IsValid returns false it accesses the ModelState dictionary for the Country key. The Errors collection contains all the validation errors for that field. If Errors.Count is greater than zero it means there are errors for Country model property. It then grabs the lblErr placed inside the EditItemTemplate of Country column using FindControl() method. It then iterates through the Errors collection and for each ModelError found there adds the respective error message in the Text property of the Label.
That's it! Run the web form and enter some Country value longer than 10 characters. You should see error message in the ValidationSummary as well as lblErr. If required you can set lblErr to a * (or some other short phrase) and use ValidationSummary to display the full error message.