Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Use jQuery.extend() and Ajax to construct your JavaScript Objects from Server Objects

4.33/5 (5 votes)
14 Aug 2011CPOL7 min read 30.5K   243  
Using WebMethod to construct your JavaScript objects

Introduction

So you start playing around with jQuery and Ajax and you discover that it is pretty cool stuff. Soon you discover that by using AJAX and web methods, you can send a .NET object to the client and it will automatically decode into a nice JavaScript object for you. This is all fine and good, but when you get right down to it, most of your objects actually begin their life on the client. Our applications aren't just about displaying data that already exists. The real power in the application is the ability to get the data into the application. What would be nice is to have our client objects and the server objects be one and the same, or at least as close as we can get to that using two different languages.

Background

In the past, my strategy to achieve this was to, simply by brute force, recreate my .NET objects in JavaScript on the client-side. This works, but it is a lot of work! And if you misspell a variable name or accidentally delete a required variable, the server will not be able to decode the object back to its native .NET type. I once spent more time than I care to admit to debugging an issue where I typed a zero instead of an O. Also, if you extend the object on the server, you then have to go to the client and manually keep everything in synch. Even on a small project, you end up with a code management nightmare.

Then it hit me! What if I were able to "construct" the object on the server? I should be able to take advantage of the servers ability to pass me back an object. The only issue I would have is only the data members of the .NET class come across. Which is fine because the implementation of the objects functions would have to change anyway. If I could just code the functions on the client and have the Data Members populated from the server, that would be better.

Then I read an article about how to use jquery.extend(). (Unfortunately, I have not been able to locate the article, to give credit where it is due.) I knew that jquery.extend() was used to create plugins for jQuery, where you basically build your plugin and use extend to add it to the jQuery API. What I didn't know was that extend can do the same thing for your objects. Finally, I have all the pieces I need to "construct" my client objects on the server.

Using the Code

The sample project is a simple interface that will allow you to add Person objects to a table. All this is done using Web Methods on the server side and JQuery on the client-side!

Business Layer Objects

I derive all my business layer objects off of PersistableObject. At least all the objects in the Business Layer that I will store to the database. This PersistableObject class gives me two Data Members Id and InSync. Id is used to store an Id for the object. Most of my database tables are built to include an auto incremented id for each row. This auto incremented id is what will be stored here. The InSync flag is not an homage to 80’s boy bands, it simply allows me to keep track of whether or not I'm synchronized with the database. If I modify a Data Member on the derived object, I will set this flag to false.

I also have two virtual functions that will allow me to either Persist the object or Drop the object from the database. Notice that there are no separate Insert and Update commands. I plan on the data layer being smart enough to figure that out for me. Which is very easy since if the data layer sees an Id it needs to do an update. No Id means this is a new object and needs to be inserted.

C#
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!");
        }
    }
}

So for the sample project, we will need a Person object. So I derived that object from my PersistableObject class. I have overridden the base class’s Persist and Drop functions. For this demonstration, I simulate storing the object into a database. I didn't want the code to get lost in a sea of database code. This Person object simply gives me a few Data Members that will allow me to store the First name, Last name, and birthdate of the person entered from the client.

C#
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 Helper Functions

Before continuing on to the actual JavaScript object, I need to mention a couple of helper functions that I use for my Ajax calls. The first thing to point out is I put all my Web Methods in one file instead of peppering them all over my project. This allows me to create a single function for my Ajax calls. In this function, I send in the Method Name(m), the Data(d), and the Success Callback(c). I do not pass in a Failure callback because I handle all my Ajax failures using the same function. This error handler will pop up a dialog to tell me there is an error. Not the best way to handle an error, but it works for this demonstration.

One thing to point out is I have two variations of the Method helper. I have the plain Method helper, which does a "true" Ajax call and keeps everything asynchronous. But, I also have a SerialMethod helper that allows me to wait for the return. This will be very important when I create my JavaScript object and the constructor will need to call a Server side Web Method to build the object.

JavaScript
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); }
    });
};

Client Side Objects

So here is my JavaScript variation of the Person object. Notice that I again have the Persist and Drop function. But on the client side, these functions call Web Methods on the server side using the helper functions and Ajax. Both Persist and Drop require callback functions which get called when the Web Methods have completed.

Next you will notice that there is a Create function. This will also call a Web Method on the server; but this time we will use the SerialMethod to wait for a return. Once we get the object returned from the server, we then call our Clone function. Clone's main purpose is to call jQuery's extend method to copy all the data members of the server object into our new object. Another benefit of the Clone function is that we can also clean up some of our variables that come back. For instance, we know dates will come back in a weird notation which will need to be converted into a JavaScript date. We take care of that conversion here in the Clone function.

One last thing to note here is that we can create this object from an already existing object on the client. If we already have a person object and we want to copy that object we don't really have to go back to the server for the constructor.

Creating a new object from the Server:

C#
var p = new cd.Objects.Person();

Creating a new object from an existing Person:

C#
var p = new cd.Objects.Person(aPersonObject);
JavaScript
    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();
    }
};

The WebMethods

One of the benefits of putting together the Business Layer and a Client Layer to handle these objects is that your Web Methods turn out to be very simple and easy to read. One thing to note here is that the UpdatePerson Web Method returns the person object that was just persisted. This will return us the Id that was created for this object. It will also return any calculated fields that we may have added to our object.

C#
[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;
}

Extending the Person Object

Now that we have everything hooked up, we can more easily extend the Person Object. For example, if we want to add a Read-Only accessor for age, we only have to add the accessor to the server and it will be available for us to use on the client the next time we create a new object.

C#
[DataMember]
public TimeSpan Age
{
    get
        {
            var t = new TimeSpan();
            if (_birthdate != null)
            {
                t =  System.DateTime.Now - (DateTime) _birthdate;
            }
            return t;
        }
   }
}

Points of Interest

I have a tendency to reinvent the wheel and to do things the hard way. So I have no doubt that I may get suggestions on how I could have done this easier or that there is already an established way of handling this issue. I welcome creative criticism. If you are interested in looking through the code, I would recommend that you use Firefox with the Firebug add-on installed. That is by far the easiest way to view what is actually going on with this code. Examine the code using firebug, then go into the Person class on the server side, and check out the results. Then add some new accessors to the Person objects, and check out the result again.

History

  • 14th August, 2011: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)