65.9K
CodeProject 正在变化。 阅读更多。
Home

在 jQuery 和 MVC 中处理 JSON 对象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (26投票s)

2010年11月12日

CPOL

10分钟阅读

viewsIcon

394645

downloadIcon

6981

本文使用一个简单的示例来回答在使用 jQuery 和 MVC 处理 JSON 对象时遇到的一些常见问题。

引言

本文使用一个简单的示例来回答在使用 jQuery 和 MVC 处理 JSON 对象时遇到的一些常见问题。

背景

"JSON" (JavaScript Object Notation) 是一种轻量级的、基于文本的开放标准,旨在实现人类可读的数据交换。在构建 Web 应用程序时,它与 "jQuery" 和 "ASP.NET MVC" 结合使用,提供了一种在 Web 浏览器和 Web 服务器之间高效交换数据的机制。

JsonSerializationBetweenServerBrowser.jpg

在浏览器端,数据被存储和操作为 "JavaScript" "JSON 对象"。在服务器端,如果使用 ASP.NET MVC,数据将被存储和操作为 ".NET 对象"。

  • 当浏览器从服务器加载数据时,.NET 对象需要被 "序列化" 成 "JSON 字符串" 并传递给浏览器。浏览器将 "反序列化" 这些 "JSON 字符串" 以便于使用 JavaScript "JSON 对象"。
  • 当浏览器将数据发送到服务器时,"JSON 对象" 需要被 "序列化" 成 "JSON 字符串",然后在服务器端被 "反序列化" 成 ".NET 对象"。

本文旨在结合一个示例 "ASP.NET MVC" 项目,在 "jQuery" 和 "ASP.NET MVC" 的上下文中回答以下问题:

  • 如何从浏览器发起一个请求到服务器,以 JavaScript "JSON 对象" 的形式请求某些数据?
  • 服务器如何序列化 ".NET 对象" 并将它们发送到浏览器?
  • 浏览器如何序列化 JavaScript "JSON 对象" 并将它们发送到服务器?
  • 服务器如何 "反序列化" JSON 字符串为 ".NET 对象"?

除了回答这些问题,这个示例项目还展示了如何在 "ASP.NET MVC" 的 "控制器" 之外使用 session。这个简单的示例项目在解决方案资源管理器中的样子如下:

SolutionExplorerFinal.JPG

应用程序的主要构建块包括:

  • 将在 "Models\StudentModel.cs" 文件中实现的 .NET 对象的类定义,它们也将作为在 "Models\StudentModel.cs" 文件中实现的 JSON 对象的模板。
  • Index.aspx 的 "ASP.NET MVC" 视图页面。这是 Web 应用程序中唯一的 视图页面。所有客户端 JavaScript 代码都实现了这个页面。
  • Controllers 文件夹中的两个 控制器类。HomeController 类是加载 Index.aspx控制器JsonOperationsController 类是服务器端处理从浏览器发布的 JSON 对象并将所有 JSON 对象发送到浏览器的中心位置。
  • StudentsRepository 类使用 Web 应用程序的 session 状态作为应用程序数据的存储库。

该示例项目是在 Visual Studio 2010 和 "MVC 2" 中开发的。 jQuery 版本是 "1.4.2"。要构建 Index.aspx 中的应用程序 UI,使用了 "jQuery treeview 插件"。这个 "插件" 用于在浏览器中以 结构显示应用程序数据。本文假设读者对 MVCjQueryJSON 有一些基本知识。如果您对这些主题不熟悉,应该可以很容易地找到参考资料。

我们将首先查看应用程序中的模型类,以便您了解在浏览器和服务器之间传递的数据。然后,我们将查看服务器端和客户端代码。最后,我们将回答我们的问题来结束本文。

.NET "模型" 类

为了使浏览器和服务器之间传递的数据结构足够复杂,在 StudentModel.cs 文件中实现了三个 .NET 类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace JsonMVC.Repositories
{
    public class Student
    {
        public string Name { get; set; }
        public IList<Class> ClassesTaken { get; set; }
    }
 
    public class Class
    {
        public string Name { get; set; }
        public IList<Exam> ExamesTaken { get; set; }
    }
 
    public class Exam
    {
        public DateTime ExamTime { get; set; }
        public string Description { get; set; }
        public int Score { get; set; }
    }
}

这三个类是 StudentClassExam。在浏览器和服务器之间传递的数据是 Student 对象的 List。每个 Student 对象可以通过公共 "属性" ClassesTaken 引用多个 Class 对象,并且每个 Class 对象可以通过公共 属性 ExamesTaken 引用多个 Exam 对象。

StudentsRepository 类

为了演示 Web session 可以在 MVC "控制器" 之外使用,应用程序服务器端的数据由 StudentsRepository 类保存在 session 中。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using JsonMVC.Models;
 
namespace JsonMVC.Repositories
{
    public class StudentsRepository
    {
        public void Initialize(int NoOfStudents,
            int NoOfClasses, int NoOfExams)
        {
            List<Student> students = new List<Student>();
            Random rd = new Random();
            for (int iStudent = 0; iStudent < NoOfStudents; iStudent++)
            {
                Student aStudent = new Student();
                aStudent.Name = "Student Name No. " +
                    iStudent.ToString();
                aStudent.ClassesTaken = new List<Class>();
                for (int iClass = 0; iClass < NoOfClasses; iClass++)
                {
                    Class aClass = new Class();
                    aClass.Name = "Class No. " + iClass.ToString();
                    aClass.ExamesTaken = new List<Exam>();
                    for (int iExam = 0; iExam < NoOfExams; iExam++)
                    {
                        Exam aExam = new Exam();
                        aExam.ExamTime = System.DateTime.Now;
                        aExam.Description = "Exam No. " +
                            iExam.ToString();
                        aExam.Score
                            = Convert.ToInt16(60 + rd.NextDouble() * 40);
 
                        aClass.ExamesTaken.Add(aExam);
                    }
 
                    aStudent.ClassesTaken.Add(aClass);
                }
 
                students.Add(aStudent);
            }
 
            HttpContext.Current.Session["Students"] = students;
        }
 
        public IList<Student> GetStudents()
        {
            return (List<Student>)
                HttpContext.Current.Session["Students"];
        }
 
        public void SetStudents(IList<Student> students)
        {
            HttpContext.Current.Session["Students"] = students;
        }
    }
}

"StudentsRepository" 类中实现了三个公共方法:

  • Initialize 方法用于根据输入参数,通过一些随机生成的数据初始化一个 Student 对象 List。然后,随机生成的 Student "List" 被保存在 session 状态中。
  • GetStudents 方法返回从 session 中检索到的 Student "List"。
  • SetStudents 方法接受一个 Student "List" 作为输入参数,并将其保存在 session 中。

"HomeController"

HomeController 是用于加载应用程序唯一 视图页面 Index.aspxMVC "控制器"。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Collections.Specialized;
using System.Configuration;
 
namespace JsonMVC.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            NameValueCollection appSettings
                = ConfigurationManager.AppSettings;
 
            ViewData["ApplicationTitle"] = 
                  appSettings["ApplicationTitle"];
            return View();
        }
    }
}

它获取在 Web.config 中配置的应用程序标题,并通过 ViewData 的形式将其传递给 Index.aspx

JsonOperationsController

JsonOperationsController "控制器" 是本文的重点之一。所有与 Web 浏览器使用 JSON 进行通信的服务器端操作都在这里实现。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using JsonMVC.Models;
using JsonMVC.Repositories;
 
namespace JsonMVC.Controllers
{
    public class JsonOperationsController : Controller
    {
        // Utility function to add a exam to each class for a list of students
        private void AddAnExamToStudents(IList<Student> students, string updateSource)
        {
            Random rd = new Random();
            foreach (Student aStudent in students)
            {
                foreach (Class aClass in aStudent.ClassesTaken)
                {
                    IList<Exam> exames = aClass.ExamesTaken;
                    Exam aExam = new Exam();
                    aExam.ExamTime = System.DateTime.Now;
                    aExam.Description = "Exam No. " +
                        exames.Count.ToString()
                        + " by " + updateSource;
                    aExam.Score
                        = Convert.ToInt16(60 + rd.NextDouble() * 40);
 
                    exames.Add(aExam);
                }
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult GetStudents(int NoOfStudents,
            int NoOfClasses, int NoOfExams)
        {
            StudentsRepository repository = new StudentsRepository();
            repository.Initialize(NoOfStudents, NoOfClasses, NoOfExams);
 
            return Json(repository.GetStudents());
        }
 
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult AddAnExamByJson(IList<Student> students)
        {
            StudentsRepository repository = new StudentsRepository();
            repository.SetStudents(students);
 
            AddAnExamToStudents(students, "json");
            return Json(students);
        }
 
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult AddAnExamBySession()
        {
            StudentsRepository repository = new StudentsRepository();
            IList<Student> students = repository.GetStudents();
 
            AddAnExamToStudents(students, "session");
            return Json(students);
        }
    }
}

三个公共 ActionResult 方法是:

  • GetStudents 方法接受一个整数输入,并向浏览器返回一个 Student 对象 List
  • AddAnExamByJson 方法接受来自浏览器的 Student 对象 List,并向学生所修的每个 "Class" 对象添加一个 Exam 对象。它将 List 保存到 session 中,然后将 Student 对象 List 返回给浏览器。
  • AddAnExamBySession 方法不接受任何参数。它使用 StudentsRepository 类从 Web session 中获取 Student 对象 List。它向学生所修的每个 "Class" 对象添加一个 Exam 对象,然后将 Student 对象 List 返回给浏览器。

所有这三个方法都使用 JsonResultJSON 对象发送到浏览器。它们各自展示了接收来自浏览器数据的三种情况之一。

  • AddAnExamBySession 不接受来自浏览器的数据。
  • GetStudents 接受一个标量输入数据。
  • AddAnExamByJson 接受一个复杂对象的 List

MVC 视图 Index.aspx

在客户端与 JsonOperationsController 中的方法进行通信的代码实现在 "Index.aspx" 中。

<%@ Page Language="C#" 
         Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
 
<!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 id="Head1" runat="server">
    <title>Working on Json Arrays in MVC</title>
    <link rel="SHORTCUT ICON"
        href="<%= Url.Content("~/Content/Images/rubik.ico") %>" />
    <link rel="stylesheet"
        href="<%= Url.Content("~/Content/Styles/Site.css") %>"
        type="text/css" />
    <link rel="stylesheet"
        href="<%= Url.Content("~/Content/Styles/jquery.treeview.css") %>"
        type="text/css" />
     
    <script src="<%= Url.Content("~/Scripts/jquery-1.4.2.min.js") %>"
        type="text/javascript">
    </script>
    <script src="<%= Url.Content("~/Scripts/jquery.treeview.js") %>"
        type="text/javascript">
    </script>
 
    <script type="text/javascript" language="javascript">
        var GetStudentsURL
            = '<%: Url.Action("GetStudents", "JsonOperations") %>';
        var AddAnExamBySessionURL
            = '<%: Url.Action("AddAnExamBySession", "JsonOperations") %>';
        var AddAnExamByJsonURL
            = '<%: Url.Action("AddAnExamByJson", "JsonOperations") %>';
        var StudentJson = null;
        var NoOfStudents = 3, NoOfClasses = 1, NoOfExams = 0;
 
        var prependZero = function (v) {
            v = v.toString();
            return (v.length == 1) ? "0" + v : v;
        };
 
        var dateDeserialize = function (dateStr) {
            var dt = new Date(parseInt(dateStr.substr(6)));
            return prependZero((dt.getMonth() + 1))
                + "/" + prependZero(dt.getDate())
                + "/" + dt.getFullYear()
                + " " + prependZero(dt.getHours())
                + ":" + prependZero(dt.getMinutes())
                + ":" + prependZero(dt.getSeconds());
        };
 
        var FixDateinJson = function (JsonStudents) {
            $.each(JsonStudents, function (i, JsonStudent) {
                $.each(JsonStudent.ClassesTaken, function (i, JsonClass) {
                    $.each(JsonClass.ExamesTaken, function (i, JsonExam) {
                        JsonExam.ExamTime = dateDeserialize(JsonExam.ExamTime);
                    });
                });
            });
 
            return JsonStudents;
        }
 
        var buildStudentTree = function (students) {
 
            var createTextNode = function (text) {
                var span = document.createElement("span");
                span.setAttribute("style", "margin-left: 2px");
                var tx = document.createTextNode(text);
                span.appendChild(tx);
 
                return span;
            };
 
            var root = document.createElement("ul");
            root.id = "StudentTreeRoot";
            root.setAttribute("style", "margin: 15px");
            root.className = "filetree";
            $.each(students, function (i, student) {
                var studentNode = document.createElement("li");
                //studentNode.className = "closed";
                var span = document.createElement("span");
                span.className = "folder";
                span.appendChild(createTextNode(student.Name));
                studentNode.appendChild(span);
 
                var classesNode = document.createElement("ul");
                $.each(student.ClassesTaken, function (i, aClass) {
                    var classNode = document.createElement("li");
                    //classNode.className = "closed";
                    span = document.createElement("span");
                    span.className = "folder";
                    span.appendChild(createTextNode(aClass.Name))
                    classNode.appendChild(span);
 
                    var examesNode = document.createElement("ul");
                    examesNode.className = "folder";
 
                    $.each(aClass.ExamesTaken, function (i, aExam) {
                        var examNode = document.createElement("li");
                        //examNode.className = "closed";
                        span = document.createElement("span");
                        span.className = "folder";
                        span.appendChild(createTextNode(aExam.Description));
                        examNode.appendChild(span);
 
                        var detailNode = document.createElement("ul");
                        var examTime = document.createElement("li");
                        span = document.createElement("span");
                        span.className = "file";
                        span.appendChild(createTextNode(aExam.ExamTime));
                        examTime.appendChild(span);
                        detailNode.appendChild(examTime);
 
                        var score = document.createElement("li");
                        span = document.createElement("span");
                        span.className = "file";
                        span.appendChild(createTextNode(aExam.Score));
                        score.appendChild(span);
                        detailNode.appendChild(score);
 
                        examNode.appendChild(detailNode);
 
                        examesNode.appendChild(examNode);
                    }); 
 
                    classNode.appendChild(examesNode)
 
                    classesNode.appendChild(classNode);
                });
 
                studentNode.appendChild(classesNode);
                root.appendChild(studentNode);
            });
 
            $("#StudentTree").html("").append(root);
            $("#StudentTreeRoot").treeview();
        };
 
        $(document).ready(function () {
            $("#StudentTree").html("");
            $.ajax({
                cache: false,
                type: "POST",
                async: false,
                url: GetStudentsURL
                    + "/?NoOfStudents=" + NoOfStudents
                    + "&NoOfClasses=" + NoOfClasses
                    + "&NoOfExams=" + NoOfExams,
                dataType: "json",
                success: function (students) {
                    StudentJson = FixDateinJson(students);
                    buildStudentTree(StudentJson);
                }
            });
 
            $("#btnAddAnExamJson").click(function () {
                $("#StudentTree").html("Loading ...");
 
                $.ajax({
                    cache: false,
                    type: "POST",
                    url: AddAnExamByJsonURL,
                    contentType: 'application/json',
                    dataType: "json",
                    data: JSON.stringify(StudentJson),
                    success: function (students) {
                        StudentJson = FixDateinJson(students);
                        buildStudentTree(StudentJson);
                    }
                });
            });
 
            $("#btnAddAnExamSession").click(function () {
                $("#StudentTree").html("Loading ...");
 
                $.ajax({
                    cache: false,
                    type: "POST",
                    url: AddAnExamBySessionURL,
                    dataType: "json",
                    success: function (students) {
                        StudentJson = FixDateinJson(students);
                        buildStudentTree(StudentJson);
                    }
                });
            });
        });
    </script>
</head>
 
<body>
    <div id="TitleContainer"><%= ViewData["ApplicationTitle"]%></div>
    <div id="MainContainer">
        <div id="StudentTree"></div>
        <div id="ButtonContainer">
            <button id="btnAddAnExamSession" class="ButtonStyle">
                Add an exam to students using session</button>
            <button id="btnAddAnExamJson" class="ButtonStyle">
                Add an exam to students by posting Json</button>
        </div>
    </div>
</body>
</html>

Index.aspxHTML 部分非常简单,但您应该注意以下组件:

  • 按钮控件 btnAddAnExamSession。此 按钮 触发一个 "jQuery" AJAX 调用到 JsonOperationsController 中的 AddAnExamBySession 方法。
  • 按钮控件 "btnAddAnExamJson"。此 按钮 触发一个 "jQuery" AJAX 调用到 JsonOperationsController 中的 AddAnExamByJson 方法。
  • StudentTree div 是一个占位符,我们将使用 jQuery treeview 插件 在其中显示从服务器接收到的 Student 对象 List

Index.aspx 中的大部分逻辑是用 JavaScript 和 jQuery 实现的。

  • $(document).ready() 事件中,使用 jQuery 调用 JsonOperationsController 中的 GetStudents 方法,执行一次同步 AJAX 调用。返回的 Student 对象 List 已经被 jQuery "反序列化" 成 JSON 对象数组。这个 JSON 数组然后被保存在全局 JavaScript 变量 StudentJson 中以供稍后处理。它也用于 buildStudentTree 函数来构建一个树形视图。这个树形视图是基于 StudentTree div 使用 jQuery treeview 插件 构建的。
  • btnAddAnExamSession 按钮的点击事件中,会发起一个异步 AJAX 调用到 JsonOperationsController 中的 AddAnExamBySession 方法。然后,树形视图将使用新接收的学生列表进行刷新。
  • btnAddAnExamJson 按钮的点击事件中,会发起一个异步 AJAX 调用到 JsonOperationsController 中的 AddAnExamByJson 方法。然后,树形视图将使用新接收的学生列表进行刷新。

Index.aspx 中的 JavaScript 代码演示了以下内容:

  • jQuery "AJAX" 调用可以是 "同步" 和 "异步" 的。默认是异步调用,但您可以通过指定 "async: false" 来使其同步。进行同步 jQuery "AJAX" 调用不是本文的重点,但在某些情况下可能有用。
  • 三个 jQuery "AJAX" 调用中的每一个都演示了三种情况之一,即向服务器传递零数据、传递一个标量数据项,以及传递一个 JSON 对象数组到服务器。

对默认 MVC "Model Binder" 的补充

现在我们已经完成了客户端和服务器端的编码,展示了如何使用 jQueryMVC 处理 JSON 对象。在大多数情况下,这将运行良好。但是,默认的 MVC "ModelBinder" 不会 反序列化 JSON 对象数组。这意味着我们将无法将 Student JSON 对象数组发送到 AddAnExamByJson 方法。要解决此问题,我们需要对 "Global.asax.cs" 文件进行更改。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
 
namespace JsonMVC
{
    public class JsonModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext,
            ModelBindingContext bindingContext)
        {
            // If the request is not of content type "application/json"
            // then use the default model binder.
            if (!IsJSONRequest(controllerContext))
            {
                return base.BindModel(controllerContext, bindingContext);
            }
 
            // Get the JSON data posted
            var request = controllerContext.HttpContext.Request;
            var jsonStringData =
                new System.IO.StreamReader(request.InputStream).ReadToEnd();
 
            return new System.Web.Script.Serialization.JavaScriptSerializer()
                .Deserialize(jsonStringData, bindingContext.ModelMetadata.ModelType);
        }
 
        private bool IsJSONRequest(ControllerContext controllerContext)
        {
            var contentType = controllerContext.HttpContext.Request.ContentType;
            return contentType.Contains("application/json");
        }
    }
 
    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
            routes.MapRoute("Default", "{controller}/{action}/{id}",
                new
                {
                    controller = "Home",
                    action = "Index",
                    id = UrlParameter.Optional
                });
        }
 
        protected void Application_Start()
        {
            // Set the model binder
            ModelBinders.Binders.DefaultBinder = new JsonModelBinder();
            AreaRegistration.RegisterAllAreas();
            RegisterRoutes(RouteTable.Routes);
        }
 
        protected void Application_BeginRequest(object sender, EventArgs e)
        {
            // It is OK to cache the jQuery library, images, etc.
            string FilePath = HttpContext.Current.Request.FilePath;
            if (FilePath.Contains("jquery-1.4.2.min.js") ||
                FilePath.Contains("rubik.ico") ||
                FilePath.Contains(".jpg") ||
                FilePath.Contains(".css"))
                return;
 
            HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            HttpContext.Current.Response.Cache.SetNoStore();
        }
    }
}

Global.asax.cs 中,实现了一个名为 JsonModelBinder 的类。该类中的 BindModel 方法首先检查 HTTP 请求的 "内容类型"。如果它是 "application/json",则使用 JavaScriptSerializer反序列化 发布的 内容。否则,将使用默认的 "序列化器",因此它不会改变正常的 MVC 行为。我从 这里 学到了这个技巧。如果您有兴趣,可以进一步了解。

运行应用程序

现在我们可以测试运行该应用程序了。当 Index.aspx 加载时,将通过调用 JsonOperationsController 中的 GetStudents 方法,发起一个同步 jQuery "AJAX" 调用来检索学生 JSON 对象列表。然后,该学生列表将由 jQuery treeview 插件 在树形视图中显示。

ApplicationStart.JPG

然后,我们可以点击 "Add an exam to students by posting JSON" 和 "Add an exam to students using session" 按钮。每次按钮点击都会发出一个异步 jQuery "AJAX" 调用,该调用会指向 JsonOperationsController 中的 AddAnExamByJson 方法或 AddAnExamBySession 方法。树形视图将使用新内容进行刷新。

ApplicationRun.JPG

从上面的屏幕截图可以看出, JSON 对象已成功发布到 JsonOperationsController,并且由 StudentsRepository 类管理的 session 状态也正常工作。

结论

为了总结本文,我将回答开头列出的问题:

关注点

  • 本文使用了一个简单的示例来回答在使用 jQuery 和 MVC 处理 JSON 对象时遇到的一些常见问题。
  • 除了回答问题之外,本文还演示了如何在 MVC "控制器" 之外使用 session 状态,并提供了一个示例来使用 "jQuery treeview 插件" 从 JSON 对象中的数据动态生成树形视图。
  • 在浏览器和服务器之间交换 JSON 数据有很多方法。本文介绍的方法只是其中一种。在使用 jQueryMVC 进行开发时,我认为这里介绍的方法是其中一个简单的选择。
  • 希望您喜欢我的博文,并希望本文能以某种方式帮助到您。

历史

这是本文的第一个修订版。

© . All rights reserved.