使用注解在 MVC 应用程序的 jQuery AJAX 调用中进行数据验证
本文介绍了一个示例,说明如何使用数据注释来验证从 jQuery AJAX 调用接收到的数据,以及如何将验证结果以及来自部分视图的 HTML 内容作为 JSON 对象发送到客户端,这一切都在 MVC 中完成。
引言
本文介绍了一个示例,说明如何使用数据注释来验证从 jQuery AJAX 调用接收到的数据,以及如何将验证结果以及来自部分视图的 HTML 内容作为 JSON 对象发送到客户端,这一切都在 MVC 中完成。
背景
在编写需要录入数据的 Web 应用程序时,验证始终是一个重要的话题。有两种方法可以验证用户输入:客户端验证和服务器端验证。
- 使用 JavaScript 在客户端验证数据可以立即向用户提供反馈,但我们可能无法始终在客户端拥有所有信息来完全验证数据。
- 在服务器端验证数据可以利用所有可用信息,这通常可以提供最可信的结果。但我们需要将数据发送到服务器,有时还需要刷新页面。
为了利用这两种验证方法的优势,一些 Web 应用程序同时具备客户端和服务器端验证。但是,将验证逻辑放在两个不同的位置会使代码难以维护。此外,如果管理不善,在两个不同位置拥有两段验证逻辑很容易导致重复工作。
本文代表了一项尝试弥合这两种验证方法的努力。该示例使用了服务器端验证,但通过使用 jQuery AJAX 调用来发送数据到服务器并检索结果和其他有用信息,从而尽量减少了网络流量。在服务器端,它利用 Data Annotation 和 MVC Model Binding 来最大程度地减少我们需要编写的验证数据代码。客户端和服务器使用 JSON 对象进行信息交换。
本文假定您对 jQuery、AJAX、MVC、Data Annotation 和 MVC Model Binding 有一些基本了解。如果您对这些主题不熟悉,可以在网上找到很多参考资料。
Visual Studio 解决方案
随附的 Visual Studio 2010 解决方案是一个简单的 MVC 2 Web 应用程序。
- 应用程序的“模型”实现在“Models/Student.cs”文件中。
- 应用程序的“视图”实现在“Views/Home/Index.aspx”文件中。“Student.ascx”文件实现了一个 Partial View(部分视图),用于渲染“Index.aspx”页面。
- 应用程序的“控制器”实现在“Controllers/HomeController.cs”文件中。
我将在本文中详细介绍每个构建块。让我们从“Models/Student.cs”文件开始。
应用程序模型
应用程序的模型实现在“Models/Student.cs”文件中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace DataAnnotationAjax.Models
{
public class Student
{
public int Id { get; set; }
[Required(ErrorMessage = "Last name is required")]
public string LastName { get; set; }
[Required(ErrorMessage = "First name is required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Score is required to add the student"),
Range(60, 100, ErrorMessage="Score should be between 60 - 100")]
public int Score { get; set; }
public DateTime Enrollment { get; set; }
}
public static class StudentRepository
{
private static int IdSeed = 1;
private static readonly List<Student> Students = new List<Student>();
// Initiate a student list in the static constructor
static StudentRepository()
{
Random rand = new Random();
for (int i = 0; i < 3; i++)
{
var student = new Student();
int id = IdSeed++;
student.Id = id;
student.LastName = "Last Name " + id.ToString();
student.FirstName = "First Name " + id.ToString();
student.Score = (60 + Convert.ToInt16(rand.NextDouble() * 40));
student.Enrollment = DateTime.Now;
Students.Add(student);
}
}
// Add a single student
public static void AddStudent(Student student)
{
student.Id = IdSeed++;
student.Enrollment = DateTime.Now;
Students.Add(student);
}
// Get the list of students
public static List<Student> GetStudent()
{
return Students;
}
}
}
出于简化的原因,我在“Models/Student.cs”文件中放置了两个类。
Student
类是应用程序的数据模型。我在LastName
、FirstName
和Score
属性上放置了一些 Data Annotations(数据注释)。如您稍后在应用程序的控制器中看到的,这些注释将用于通过 MVC Model Binding 进行数据验证。StudentRepository
类将作为应用程序的数据存储库。在 Static Constructor(静态构造函数)中,向存储库添加了三个随机生成的学生。当应用程序启动时,这三个学生将显示给用户。您可以使用AddStudent
方法向存储库添加新学生,并可以使用 GetStudent 方法检索学生列表。
此 Web 应用程序的目的是演示如何基于 Student
类属性上的 Data Annotations(数据注释)来验证用户输入,以及如何在 jQuery AJAX 调用中进行此操作。
视图页面
此 MVC 应用程序的视图页面实现在“Views/Home/Index.aspx”文件中。
<%@ Page Language="C#"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<DataAnnotationAjax.Models.Student>>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Data Annotation & Ajax</title>
<link rel="stylesheet"
href="<%: Url.Content("~/Content/Site.css") %>"
type="text/css" />
<script src='<%: Url.Content("~/Scripts/jquery-1.6.1.min.js") %>'
type="text/javascript">
</script>
<script src='<%: Url.Content("~/Scripts/Home.Index.js") %>'
type="text/javascript">
</script>
<script type="text/javascript">
var addStudentUrl = '<%: Url.Action("AddStudent", "Home") %>';
</script>
</head>
<body>
<div>
<table cellpadding="0px">
<tr>
<td>Last Name</td>
<td><input type="text" id="LastName" /></td>
</tr>
<tr>
<td></td><td colspan="2"
id="Err_LastName" class="errormsg"></td>
</tr>
<tr>
<td>First Name</td>
<td><input type="text" id="FirstName" /></td>
</tr>
<tr>
<td></td><td colspan="2"
id="Err_FirstName" class="errormsg"></td>
</tr>
<tr>
<td>Score</td>
<td><input type="text" id="Score" /></td>
<td>
<button id="btnAddStudent">Add Student</button>
<button id="btnClear">Clear</button>
</td>
</tr>
<tr>
<td></td><td colspan="2"
id="Err_Score" class="errormsg"></td>
</tr>
</table>
</div>
<div id="divStudent"><%Html.RenderPartial("Students"); %></div>
</body>
</html>
此“ASPX”页面具有以下视觉元素:
- 三个文本框用于接收用户输入。每个文本框对应
Student
类中的一个带注释的属性。 - 由部分视图 Students.ascx 渲染的数据存储库中现有学生的列表。
- “添加学生”按钮将启动一个“jQuery”AJAX 调用,将用户输入发送到服务器以开始验证。我将在介绍“Scripts/"Home.Index.js”文件时详细讨论这一点。
- “清除”按钮将清除用户输入。
在查看“Scripts/Home.Index.js”文件中的 JavaScript 代码之前,让我们先看看 Partial View(部分视图)“Views/Home/Students.ascx”。
<%@ Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<DataAnnotationAjax.Models.Student>>" %>
<table cellpadding="8px" rules="all">
<tr>
<th>Id</th><th>Last Name</th><th>First Name</th>
<th>Score</th><th>Enrollment Time</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td><%: item.Id %></td>
<td><%: item.LastName %></td>
<td><%: item.FirstName %></td>
<td><%: item.Score %></td>
<td><%: item.Enrollment.ToString("MM/dd/yyyy") %></td>
</tr>
<% } %>
</table>
此 Partial View(部分视图)接收学生列表并在 HTML 表中显示。它在此应用程序中使用了两个地方:
- 通过
RenderPartial
方法嵌入到“Views/Home/Index.aspx”页面中。 - 它将在应用程序的控制器中使用,以便将学生列表的 HTML 内容以“JSON”对象的形式发送到 Web 浏览器。我们将在介绍应用程序的控制器时,了解如何在 MVC 控制器中从“Partial View”(部分视图)生成 HTML 字符串。
“Views/Home/Index.aspx”页面的客户端 JavaScript 代码实现在“Scripts/"Home.Index.js”文件中。
$(document).ready(function () {
// Set up the click event on the button to make
// the ajax call to add the student.
$("#btnAddStudent").click(function () {
// collect the data to a Json object.
var data = {
LastName: $.trim($("#LastName").val()),
FirstName: $.trim($("#FirstName").val()),
Score: $.trim($("#Score").val())
};
$.ajax({
cache: false,
type: "POST",
url: addStudentUrl,
data: data,
dataType: "json",
success: function (data) {
// There is no problem with the validation
if (data.Valid) {
$("#divStudent").html(data.StudentsPartial);
$("input").val("");
return;
}
// Problem happend during the validation, display
// the messages. The following script will display the last
// message related to the field.
$.each(data.Errors, function (key, value) {
if (value != null) {
$("#Err_" + key).html(value[value.length - 1].ErrorMessage);
}
});
},
error: function (xhr) {
alert(xhr.responseText);
alert("Critical Error!. Failed to call the server.");
}
});
});
$("#btnClear").click(function () {
$(".errormsg").html("");
$("input").val("");
});
// Set up the change event to the textboxes, so when user
// makes changes, clear the error messages associated to the textbox.
$("input").keyup(function () {
var $errorDiv = $("#Err_" + this.id);
if ($errorDiv.html() != "") {
$errorDiv.html("");
}
});
});
在 $(document).ready
事件中,JavaScript 代码执行以下操作:
- 注册“添加学生”按钮的单击事件,以将用户输入发送到服务器。当响应返回时,UI 将相应地进行调整。
- 注册“清除”按钮的单击事件,以清除用户输入以及任何可能的错误消息(如果存在)。
- 注册文本框的“keyup”事件。当用户更改文本框内容时,如果该文本框有相关的错误消息,则会将其清除。
“添加学生”按钮单击事件中的 jQuery AJAX 调用是相当标准的。它将用户输入 POST 到服务器,并期望一个 JSON 对象。从服务器接收到的 JSON 对象包含三个变量:
Valid
变量,指示用户输入的验证是否成功。- 如果在验证过程中出现任何问题,
Errors
变量将包含详细的错误消息。 - 如果验证成功,
StudentsPartial
变量是控制器中 Partial View(部分视图)“Views/Home/Students.ascx”生成的 HTML 字符串,其中包含新添加的学生。
现在让我们看一下控制器。我们将看到它是如何验证数据的,以及它如何生成客户端期望的 JSON 对象。
控制器
此应用程序的控制器实现在“Controllers/HomeController.cs”文件中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DataAnnotationAjax.Models;
using DataAnnotationAjax.Controllers.ControllerBase;
namespace DataAnnotationAjax.Controllers
{
public class HomeController : BaseController
{
[HttpGet]
public ActionResult Index()
{
return View(StudentRepository.GetStudent());
}
[HttpPost]
public ActionResult AddStudent()
{
var student = new Student();
// The TryUpdateModel will use data annotation to validate
// the data passed over from the client. If any validation
// fails, the error will be written to the "ModelState".
var valid = TryUpdateModel(student);
string studentPartialViewHtml = null;
if (valid)
{
// Add the student to the repository.
// In any pratical application, exception handling should be used
// when making data access. In this small example, I will just cross
// my finger to say "there is no chance to encounter exeption" when
// adding the student. Indeed, the chance of exception is very minimal
// when adding students to my small repositoty. If the validation is
// successful, I will simply tell the client that the student is added.
StudentRepository.AddStudent(student);
// Obtain the html string from the partial view "Students.ascx"
var students = StudentRepository.GetStudent();
studentPartialViewHtml = RenderPartialViewToString("Students", students);
}
return Json(new {Valid = valid,
Errors = GetErrorsFromModelState(),
StudentsPartial = studentPartialViewHtml
});
}
}
}
此类控制器实现了两个 Action(操作)方法:
Index
方法用于在应用程序启动时加载 Index.aspx 页面。AddStudent
方法在用户单击“添加学生”按钮时由客户端 JavaScript 代码调用。它接收用户的输入数据并对其进行验证。如果验证成功,则将学生添加到存储库。将按照预期的格式正确地构造一个 JSON 对象并发送到客户端。
如果您不熟悉 MVC Model Binding,您可能会对为什么我没有编写一行代码来验证数据感到惊讶。此类数据验证的秘密在于 TryUpdateModel
方法。
TryUpdateModel
方法接受一个 student 对象。它从Request
对象读取发送到服务器的数据,并通过匹配属性名称和已发布数据的名称来尝试更新 student 对象中的属性。- 在 student 对象更新后,它将检查
Student
类中的 Data Annotations(数据注释)来验证数据。如果发现问题,它会将错误消息添加到控制器的ModelState
对象中。TryUpdateModel
方法的返回类型是布尔值,指示模型是否成功验证。 - 如果您对数据验证的实现方式仍有疑问,可以参考此链接,其中包含更详细的说明。
为了构造客户端期望的 JSON 对象,我调用了两个方法:RenderPartialViewToString
和 GetErrorsFromModelState
。这两个方法实现在 BaseController
类中。
using System.Web.Mvc;
using System.IO;
using System.Collections.Generic;
namespace DataAnnotationAjax.Controllers.ControllerBase
{
public class BaseController : Controller
{
// This method helps to render a partial view into html string.
// http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/
// Credit: Kevin Craft
public string RenderPartialViewToString(string viewName, object model)
{
ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult =
ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
var viewContext = new ViewContext(ControllerContext,
viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
// This method helps to get the error information from the MVC "ModelState".
// We can not directly send the ModelState to the client in Json. The "ModelState"
// object has some circular reference that prevents it to be serialized to Json.
public Dictionary<string, object> GetErrorsFromModelState()
{
var errors = new Dictionary<string, object>();
foreach (var key in ModelState.Keys)
{
// Only send the errors to the client.
if (ModelState[key].Errors.Count > 0)
{
errors[key] = ModelState[key].Errors;
}
}
return errors;
}
}
}
RenderPartialViewToString
方法用于生成 Partial View(部分视图)的字符串表示形式,以便我们可以将 HTML 内容作为 JSON 对象发送到客户端。MVC 本身不提供此功能。此方法是从“Kevin Craft”那里借鉴的。如果您感兴趣,可以查看他的帖子。GetErrorsFromModelState
方法用于构造一个字典来保存ModelState
对象中的验证错误,因为我们在 MVC 中无法直接将ModelState
对象序列化为 JSON 格式。将验证错误放入更简单的数据对象也有助于我们减小 JSON 对象中的有效负载。
运行应用程序
我们已经完成了应用程序,可以进行测试运行。我们可以按 F5 键以调试模式启动此应用程序,或按“Ctrl + F5”键在不调试的情况下启动它。
应用程序启动后,我们可以看到三个预生成的学生和三个文本框。
不输入任何内容就单击“添加学生”按钮,验证会失败,相应的消息将显示在文本框旁边。
如果我们输入了姓和名,但给出了错误的分数,验证会针对分数失败,消息将显示在分数旁边。
如果我们为所有三个文本框提供了适当的信息,学生将被添加到存储库,并且学生列表将在浏览器中刷新。
关注点
- 本文介绍了一个示例,说明如何使用数据注释来验证从 jQuery AJAX 调用接收到的数据,以及如何将验证结果以及来自部分视图的 HTML 内容作为 JSON 对象发送到客户端,这一切都在 MVC 中完成。
- 本文代表了一项通过利用服务器上的可用信息来最大化验证结果的真实性,并通过使用 jQuery AJAX 调用来最小化网络流量的努力。对于大多数 Web 应用程序,此方法可以提供足够的性能。但是,如果您确实有大量数据需要验证,您仍然可以考虑同时保留客户端和服务器端验证。
- 从技术角度来看,本文应该回答了以下问题:
- 如何在 MVC 中将 Web 浏览器中的数据绑定到数据模型对象
- 如何在 MVC 中使用 Data Annotation(数据注释)来验证数据
- 如何从 Partial View(部分视图)生成 HTML 内容的字符串表示形式,以及如何将其作为 JSON 对象发送到 Web 浏览器
- 如何在 MVC
ModelState
对象中将信息以 JSON 对象的形式发送到浏览器 - 虽然使用 Data Annotations(数据注释)进行数据验证可以节省我们大量打字工作,但其功能有限。对于复杂的数据验证,我们可能仍需要编写自己的验证代码。如果 Data Annotations(数据注释)本身不支持此类数据验证,我们可以实现我们自己的自定义数据注释。
- 如果需要客户端验证,您可以参考我之前的帖子“使用 jQuery Validation Plugin 的示例”。
- 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
- 首次修订 - 2011 年 6 月 26 日。