使用 jQuery.extend() 和 Ajax 从服务器对象“构建”你的 JavaScript 对象






4.33/5 (5投票s)
使用 WebMethod 构建您的 JavaScript 对象。
引言
当你开始使用 jQuery 和 Ajax 时,你会发现它们非常酷。很快你就会发现,通过使用 AJAX 和 Web 方法,你可以将一个 .NET 对象发送到客户端,它会自动为你解码成一个漂亮的 JavaScript 对象。这都很好,但当你深入研究时,你会发现大多数对象实际上是在客户端开始其生命周期的。我们的应用程序不仅仅是显示已经存在的数据。应用程序真正的强大之处在于能够将数据获取到应用程序中。如果我们的客户端对象和服务器对象能够合二为一,或者至少在使用两种不同语言的情况下尽可能接近,那将是极好的。
背景
过去,我实现这一目标的策略是简单地通过蛮力,在客户端用 JavaScript 重建我的 .NET 对象。这确实有效,但工作量很大!如果你拼错了一个变量名或者不小心删除了一个必需的变量,服务器将无法将该对象解码回其原始的 .NET 类型。我曾经花费了比我愿意承认的更多时间来调试一个问题,其中我输入了一个零而不是一个 `O`。此外,如果你在服务器上扩展了对象,那么你必须去客户端手动保持所有内容的同步。即使在一个小型项目中,你也会陷入代码管理噩梦。
然后我突然想到!如果我能够在服务器上“构建”对象呢?我应该能够利用服务器将对象返回给我的能力。我唯一的问题是只有 .NET 类的数据成员会过来。这很好,因为对象函数的实现无论如何都必须改变。如果我只是在客户端编写函数,并让数据成员从服务器填充,那会更好。
然后我读了一篇关于如何使用 `jquery.extend()` 的文章。(不幸的是,我一直未能找到那篇文章,无法致谢。)我知道 `jquery.extend()` 用于为 jQuery 创建插件,你基本上构建你的插件并使用 extend 将其添加到 `jQuery` API 中。我不知道的是 `extend` 也可以为你的对象做同样的事情。最后,我拥有了在服务器上“构建”客户端对象所需的所有部分。
Using the Code
示例项目是一个简单的界面,允许你将 `Person` 对象添加到表中。所有这些都是在服务器端使用 Web 方法和客户端使用 `JQuery` 完成的!
业务层对象
我所有的业务层对象都派生自 `PersistableObject`。至少所有我将存储到数据库中的业务层对象都如此。这个 `PersistableObject` 类给了我两个数据成员 `Id` 和 `InSync`。`Id` 用于存储对象的 `Id`。我的大多数数据库表都包含一个自动递增的 `id`,用于每一行。这个自动递增的 `id` 将存储在这里。`InSync` 标志并非为了致敬 80 年代的男孩乐队,它只是允许我跟踪是否与数据库同步。如果我修改派生对象上的数据成员,我会将此标志设置为 `false`。
我还有两个虚函数,允许我将对象 `Persist`(持久化)到数据库或 `Drop`(从数据库中删除)对象。请注意,没有单独的 `Insert` 和 `Update` 命令。我计划让数据层足够智能,能够为我解决这个问题。这非常容易,因为如果数据层看到 `Id`,它就需要执行更新。没有 `Id` 意味着这是一个新对象,需要插入。
namespace BLL
{
[DataContract]
public class PersistableObject
{
protected int? _id = null;
[DataMember]
public int? Id
{
get { return _id; }
set { _id = value; }
}
protected bool _inSync = false;
[DataMember]
public bool InSync
{
get { return _inSync; }
set { _inSync = value; }
}
/// <summary>
/// This will store the object into your Persistence Mechanism
/// (probably a database).
/// </summary>
/// <returns></returns>
public virtual int Persist()
{
throw new Exception
("PersistableObject derived class has not implemented the Persist function!");
}
/// <summary>
/// This will remove the object from your Persistence Mechanism
/// (probably a database).
/// </summary>
/// <returns></returns>
public virtual int Drop()
{
throw new Exception
("PersistableObject derived class has not implemented the Delete function!");
}
}
}
因此,对于示例项目,我们需要一个 `Person` 对象。所以我从我的 `PersistableObject` 类派生了该对象。我重写了基类的 `Persist` 和 `Drop` 函数。为了演示,我模拟了将对象存储到数据库中。我不想让代码迷失在数据库代码的海洋中。这个 `Person` 对象只是给了我一些数据成员,允许我存储从客户端输入的姓名、姓氏和出生日期。
namespace BLL
{
[DataContract]
public class Person : PersistableObject
{
#region Accessors
private string _firstName = null;
[DataMember]
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName != value)
{
InSync = false;
_firstName = value;
}
}
}
private string _lastName = null;
[DataMember]
public string LastName
{
get { return _lastName; }
set
{
if (_lastName != value)
{
InSync = false;
_lastName = value;
}
}
}
private DateTime? _birthdate = null;
[DataMember]
public DateTime? Birthdate
{
get { return _birthdate; }
set
{
if (_birthdate != value)
{
InSync = false;
_birthdate = value;
}
}
}
#endregion
public override int Persist()
{
if (!InSync)
{
// Here is were we would call the data layer and
// store our object into the database.
// Doing so would give and auto-generated Id field
// that we would pass back.
// Generate a random id to simulate a save into the database.
var random = new Random();
this.Id = random.Next(100, 10000);
this.InSync = true;
}
return Convert.ToInt32(this.Id);
}
public override int Drop()
{
// Here is were we would call the datalayer in order to
// remove our object from the database.
// Just set the Id to null and pretend we deleted it.
this.Id = null;
this.InSync = false;
return 0;
}
}
}
jQuery / JavaScript 辅助函数
在继续讲解实际的 JavaScript 对象之前,我需要提一下我用于 Ajax 调用的几个辅助函数。首先要指出的是,我将所有的 Web 方法都放在一个文件中,而不是分散在我的项目各处。这允许我为 Ajax 调用创建一个单一的函数。在这个函数中,我传入方法名 (`m`)、数据 (`d`) 和成功回调 (`c`)。我没有传入失败回调,因为我使用相同的函数处理所有 Ajax 失败。这个错误处理程序会弹出一个对话框来告诉我有一个错误。这不是处理错误的最佳方式,但它适用于本次演示。
需要指出的是,我有两种 `Method` 辅助函数的变体。我有普通的 `Method` 辅助函数,它执行“真正的”Ajax 调用并保持一切异步。但是,我还有一个 `SerialMethod` 辅助函数,它允许我等待返回。这在创建我的 JavaScript 对象时将非常重要,因为构造函数需要调用服务器端 Web 方法来构建对象。
cd.Ajax.Method = function (m, d, c) {
$.ajax({
type: "POST"
, url: "/WebServices/WebMethods.aspx/" + m
, contentType: "application/json; charset=utf-8"
, data: d
, dataType: "json"
, success: function (msg) { c(msg.d); }
, error: function (msg) { cd.Ajax.AjaxFailed(msg); }
});
};
cd.Ajax.SerialMethod = function (m, d, c) {
$.ajax({
type: "POST"
, url: "/WebServices/WebMethods.aspx/" + m
, contentType: "application/json; charset=utf-8"
, data: d
, async: false
, dataType: "json"
, success: function (msg) { c(msg.d); }
, error: function (msg) { cd.Ajax.AjaxFailed(msg); }
});
};
客户端对象
这就是我的 `Person` 对象的 JavaScript 变体。请注意,我再次拥有 `Persist` 和 `Drop` 函数。但在客户端,这些函数使用辅助函数和 Ajax 调用服务器端的 Web 方法。`Persist` 和 `Drop` 都需要回调函数,这些函数在 Web 方法完成时被调用。
接下来你会注意到有一个 `Create` 函数。这也会调用服务器上的 Web 方法;但这次我们将使用 `SerialMethod` 等待返回。一旦我们从服务器返回了对象,我们就会调用我们的 `Clone` 函数。`Clone` 的主要目的是调用 jQuery 的 extend 方法,将服务器对象的所有数据成员复制到我们的新对象中。`Clone` 函数的另一个好处是,我们还可以清理一些返回的变量。例如,我们知道日期会以奇怪的表示法返回,需要将其转换为 JavaScript 日期。我们会在 `Clone` 函数中处理这种转换。
最后一点需要注意的是,我们可以从客户端已有的对象创建此对象。如果我们已经有一个 `person` 对象,并且我们想复制该对象,我们实际上不需要返回服务器来获取构造函数。
从 `Server` 创建一个新对象
var p = new cd.Objects.Person();
从现有的 `Person` 创建一个新对象
var p = new cd.Objects.Person(aPersonObject);
cd.Objects.Person = function (info) {
this.Persist = function (callback) {
cd.Ajax.Method("UpdatePerson"
, "{'person': " + JSON.stringify(this) + "}"
, function (person) {
var p = new cd.Objects.Person(person);
callback(p);
});
};
this.Drop = function (callback) {
cd.Ajax.Method("DeletePerson"
, "{'person': " + JSON.stringify(this) + "}"
, callback);
};
this.Create = function () {
var p;
cd.Ajax.SerialMethod("CreatePerson", undefined, function (person) { p = person });
this.Clone(p);
}
this.Clone = function (info) {
if (typeof (info.Birthdate) !== "undefined") {
info.Birthdate = cd.Ajax.convertJSONDate(info.Birthdate);
}
jQuery.extend(this, info);
}
if (info !== undefined) {
this.Clone(info);
}
else {
this.Create();
}
};
Web 方法
将业务层和客户端层结合起来处理这些对象的好处之一是,你的 Web 方法会变得非常简单易读。这里需要注意的一点是,`UpdatePerson` Web 方法返回刚刚持久化的 `person` 对象。这将返回为此对象创建的 `Id`。它还将返回我们可能已添加到对象中的任何计算字段。
[WebMethod]
public static Person CreatePerson()
{
var p = new Person();
return p;
}
[WebMethod]
public static Person UpdatePerson(Person person)
{
person.Persist();
return person;
}
[WebMethod]
public static bool DeletePerson(Person person)
{
person.Drop();
return true;
}
扩展 Person 对象
现在我们已经把所有东西都连接起来了,我们可以更容易地扩展 `Person` 对象。例如,如果我们要为年龄添加一个只读访问器,我们只需将访问器添加到服务器端,下次我们创建新对象时,它就可以在客户端使用了。
[DataMember]
public TimeSpan Age
{
get
{
var t = new TimeSpan();
if (_birthdate != null)
{
t = System.DateTime.Now - (DateTime) _birthdate;
}
return t;
}
}
}
关注点
我倾向于重新发明轮子,用困难的方式做事。所以我毫不怀疑我可能会收到关于如何更轻松地完成此操作或已经存在处理此问题的既定方法的建议。我欢迎有创意的批评。如果你有兴趣查看代码,我建议你使用安装了 Firebug 附加组件的 Firefox。这是查看此代码实际运行情况的最简单方法。使用 Firebug 检查代码,然后进入服务器端的 `Person` 类,查看结果。然后为 `Person` 对象添加一些新的访问器,再次查看结果。
历史
- 2011年8月14日:初始版本