Wednesday, March 25, 2009

jQuery, JSON, and ASMX 2.0 Services

A few weeks ago, I had a project that was grounded in the .Net Framework v2.0 and Visual Studio 2005.  The requirements were very focused on usability and speed so AJAX and jQuery were high on my list to use.  Through the project, I learned that there isn't a large amount of information in one place that tells you how to setup a ASP.Net solution that uses jQuery and ASMX services to effectively transmit json data back and forth from the client.  Because of this, I'll attempt to fill this void since there are still many developers and companies out there that have not been able to upgrade to Visual Studio 2008.

In this post, I'll discuss the process of building a plain ASP.Net 2.0 web application project (not web site project) , setting up the necessary entries in the web.config file to utilize the ASP.Net 2.0 AJAX Extensions v1.0, and use jQuery at the client side of the transfers.    In addition, I'll also show to to use the JavaScriptSerializer class and how to write your own custom converter for your objects.

 

A Note About This Post:

This post is focused on Visual Studio 2005 with the ASP.Net 2.0 AJAX Extensions v1.0.  The v3.5 of the extensions that came with ASP.Net v3.5 and Visual Studio 2008 have a few changes.  While I will try to point out the differences, the core of this post is going to be focused on Visual Studio 2005 web application projects and v1.0 of the AJAX extensions.  While we all love focusing on the latest and greatest, I'm aware that there are a large number of companies and professionals out there that are locked into using the older version of the software for a number of reasons.

 

Additions to VS2005 Used in This Post:

This post will be using the following additions to VS2005.  Below are the items and links to their respective installers:

 

Code Downloads for This Post:

 

Creating and Configuring a New WAP:

Now that we have the required additions established, we're ready to create a new ASP.Net 2.0 Web Application Project.  I'm not going to get into the details of how to do this; however, I want to stress that I'm NOT choosing an ASP.Net AJAX Enabled Web Application.  I'm just choosing to create a new, basic ASP.Net Web Application Project.  Now that the project has been created, we need to add a few references and add some information into the Web.Config file.

In the Solution Explorer, we'll need to add a reference to the AJAX Extensions v1.0 library, System.Web.Extensions.dll.  By default, this library is located at C:\Program Files\Microsoft ASP.Net\ASP.Net 2.0 AJAX Extensions\v1.0.61025\ directory.  After adding this reference, we can update our Web.Config file by including a reference to the ScriptHandlerFactory HttpHandler using the following snippet inside of the <System.Web> config section:

   1: <remove verb="*" path="*.asmx"/>
   2: <add verb="*" 
   3:      path="*.asmx" 
   4:      validate="false" 
   5:      type="System.Web.Script.Services.ScriptHandlerFactory, 
   6:            System.Web.Extensions, 
   7:            Version=1.0.61025.0, 
   8:            Culture=neutral, 
   9:            PublicKeyToken=31bf3856ad364e35"/>

By adding the AJAX Extensions reference and updating the web.config file, we're now ready to enable our ASP.Net Web Services (ASMX) to be called from JavaScript.

 

Setting Up an ASMX Web Service:

We have our new WAP setup and configured, it's time to write a simple ASMX web service and configure it so that it will be able to return JSON.  To do this, let's add a web service to our project called JsonService.asmx and add the following code snippet to replace some of the defaults Visual Studio gives you:

   1: using System.Collections.Generic;
   2: using System.Web.Script.Services;
   3: using System.Web.Script.Serialization;
   4:  
   5: [WebService(Namespace = "http://YourNamespaceHere.com")]
   6: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   7: [ScriptService()]
   8: public class JsonService : System.Web.Services.WebService
   9: {
  10:     [WebMethod]
  11:     [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
  12:     public string GetCustomer()
  13:     {
  14:         // Method Body
  15:     }
  16: }

Lines 1-3, I imported 3 additional namespaces to use in the code.  System.Collections.Generics will be used in the next section.  System.Web.Script.Services allow us to decorate the service and it's methods as Script methods for the ScriptHandlerFactory to use when making AJAX calls to and from the client.

Line 7 decorates the web service with the [ScriptService()] attribute.

Line 11 decorates the GetCustomer() web method with the [ScriptMethod()] attribute.  This attribute tells the ScriptHandlerFactory that this method is allowed to be called from an Ajax Client.  The properties inside of the attribute, ResponseFormat = ResponseFormat.Json, tells the ScriptHandlerFactory to send the response stream as a json string and not XML or Soap.  If a response to the web service that is not formatted as json, the response will be returned as XML.

At this point, we can create the body of our web method in any fashion as long as it returns a string.  If you are only passing base types, you can skip down to the Talking to the Server Using jQuery section; however, if you want to pass something a bit more complex, I recommend you continue to the next section.

 

Using the JavaScriptSerializer and a Custom Converter:

While passing base types is easy enough, it can be important to pass objects back and forth from the client.  In order to assist with this, the ASP.Net AJAX Extensions v1.0 comes with the JavaScriptSerializer object.  This object has the ability to serialize certain objects into strings representing json objects.  This sounds great and the answer to all of our problem!  Too bad it is very limiting in it's natural state.  It CAN convert Arrays of base types and (from what I can tell) any framework classes that implements IEnumerable<T>.  I haven't experimented with some of the more obscure generic collections; however, I do know that it does concern List<T> and Dictionary<S,V> just fine. 

In order to use the JavaScriptSerializer, you simply instantiate it and call its Serialize() method, passing the object that you wish to serialize.  If this is a string array or an object of type Dictionary<string,string>, it will do all of the heavy conversion for you and give you a nice little string to return to the client as shown below:

   1: // Method Body
   2: Dictionary<string, string> customerInfo = new Dictionary<string, string>();
   3: customerInfo.Add("FirstName", "John");
   4: customerInfo.Add("LastName", "Doe");
   5: customerInfo.Add("EmailAddress", "JohnDoe@Domain.Com");
   6: customerInfo.Add("PhoneNumber", "555-555-1212");
   7:  
   8: return new JavaScriptSerializer().Serialize(customerInfo);

In this code snippet, I have instantiated a new Dictionary<string,string> generic object and populated with the property information for a customer.  Lastly, I instantiate a new JavaScriptSerializer object and call it's Serialize method, passing our customer information into it.  The JavaScriptSerializer will create following string from our dictionary:

   1: {"FirstName":"John","LastName":"Doe","EmailAddress":"JohnDoe@Domain.Com","PhoneNumber":"555-555-1212"}

This is just a simple string that, technically, we could have concatenated ourselves; however, you see how it converts the name-value pairs of the Dictionary object and turns them into a json Object with properties and string values.

Seems pretty simple.  Now, let's turn our customer Dictionary into a CustomerInfo object with the same four properties.  Below is the class definition:

   1: public class CustomerInfo
   2: {
   3:     public string FirstName { get; set; }
   4:     public string LastName { get; set; }
   5:     public string EmailAddress { get; set; }
   6:     public string PhoneNumber { get; set;}
   7: }

Now, if we replace our Dictionary object with our CustomerInfo object we get code that looks similar to the following, easier to read, snippet:

   1: // Method body
   2: CustomerInfo custInfo = new CustomerInfo();
   3: custInfo.FirstName = "John";
   4: custInfo.LastName = "Doe";
   5: custInfo.EmailAddress = "JohnDoe@Domain.Com";
   6: custInfo.PhoneNumber = "555-555-1212";
   7:  
   8: return new JavaScriptSerializer().Serialize(custInfo);

Sadly, running the above code will give you the following error when you attempt to run it:

CircularReferenceError

Since the JavaScriptSerializer doesn't know the definition of our CustomerInfo object, we get this Circular Reference error.  This causes us to go down one of two roads.  We can either turn the object manually back into our Dictionary object, or we can write a custom JavaScriptConverter for our CustomerInfo Class.  The nice thing about a custom converter is that once it's setup to our JavaScriptSerializer, we can then have it convert any number of CustomerInfo classes (or collections of CustomerInfo objects) we may need.

 

Writing A Custom JavaScriptConverter

Writing our own custom JavaScriptConverter is not as difficult as one may first assume.  Inside the System.Web.Script.Serialization namespace, we are provided with an abstract base class that helps us outline the definition and gets us started quickly.  In our project, let's add another class called CustomerInfoConverter.  Have the class inherit from the JavaScriptConverter class and right-click on the class name and select "Implement Abstract Class".  What you get is the following code snippet:

   1: public class CustomerInfoConverter : JavaScriptConverter
   2: {
   3:     public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
   4:     {
   5:         throw new Exception("The method or operation is not implemented.");
   6:     }
   7:  
   8:     public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
   9:     {
  10:         throw new Exception("The method or operation is not implemented.");
  11:     }
  12:  
  13:     public override IEnumerable<Type> SupportedTypes
  14:     {
  15:         get { throw new Exception("The method or operation is not implemented."); }
  16:     }
  17: }

 

The first method the JavaScriptConverter makes us define is the Deserialize method.  This method is used by the JavaScriptSerializer to convert a json object from a client into the expected type of the web method uses as a parameter.  The JavaScriptSerializer automatically converts the json object into a Dictionary<string, object> collection.  Inside this method, you could add our mappings between the Dictionary keys to a new CustomerInfo object's properties and finally return that new instance of the CustomerInfo object.  Below is the method body:

   1: CustomerInfo cust = new CustInfo();
   2: cust.FirstName = dictionary["FirstName"].ToString();
   3: cust.LastName = dictionary["LastName"].ToString();
   4: cust.EmailAddress = dictionary["EmailAddress"].ToString();
   5: cust.PhoneNumber = dictionary["PhoneNumber"].ToString();
   6:  
   7: return cust;

 

The second method the JavaScriptConverter makes us define is the Serialize method.  This method is used during the actual serialize process.  Here, we're creating a new Dictionary<string, object> collection for our CustomerInfo class.  Since our CustomerInfo class will be boxed through the method's parameter, we'll need to properly cast it before we can start adding the values to the Dictionary object.  Below is the method body:

   1: // Cast the obj parameter
   2: CustomerInfo cust = obj as CustomerInfo;
   3:  
   4: if (cust != null)
   5: {
   6:     Dictionary<string, object> result = new Dictionary<string, object>();
   7:     result.Add("FirstName", cust.FirstName);
   8:     result.Add("LastName", cust.LastName);
   9:     result.Add("EmailAddress", cust.EmailAddress);
  10:     result.Add("PhoneNumber", cust.PhoneNumber);
  11:  
  12:     return result;
  13: }
  14:  
  15: // If the obj doesn't convert for some reason, return an empty dictionary.
  16: return new Dictionary<string, object>();

 

The last item the JavaScriptConverter makes us define is the SupportedTypes property of type IEnumerable<Type>.  This property should be a collection of types that this converter supports.  Since we are using this converter only for our CustomerInfo class, the property can be simplified to the following line of code:

   1: get { return new Type[] { typeof(CustomerInfo) }; }

 

Now that we have our converter built, we can attach it to our serializer to get our json string as shown below:

   1: CustomerInfo cust = new CustomerInfo();
   2: cust.FirstName = "John";
   3: cust.LastName = "Doe";
   4: cust.EmailAddress = "JohnDoe@Domain.com";
   5: cust.PhoneNumber = "555-555-1212";
   6:  
   7: JavaScriptSerializer jss = new JavaScriptSerializer();
   8: jss.RegisterConverters(new CustomerInfoConverter[] { new CustomerInfoConverter() });
   9: return jss.Serialize(cust);

 

Talking to the Server using jQuery:

Now that we have our server-side infrastructure setup, we can begin to write our AJAX client-side code using jQuery.  Compared to all of the code we've written, this is the easier part.  Below is the JavaScript/jQuery code that can be used to call our web service:

   1: function GetCustomerFromServer()
   2: {
   3:     $.ajax({
   4:         type: "POST",
   5:         url: "/JsonService.asmx/GetCustomer",
   6:         dataType: "json",
   7:         data: "{}",
   8:         contentType: "application/json; charset=utf-8",
   9:         success: function(msg){
  10:             var custInfo = eval("(" + msg + ")");
  11:             alert(custInfo.FirstName);
  12:         }
  13:     });
  14: }

In this client-side code, we're making a HTTP Post call to our web service by calling the web method of it directly.  We are stating that we are sending and receiving data in json format.  Despite the fact that our GetCustomer() web method does not take any parameters, we still need to send an empty json object in order to have json returned. 

Lastly, we write an anonymous method to handle the successful message returned to us.  The message is evaluated in order to be turned into a json object on the client side.  We then validate the object by echoing the FirstName property in an alert box.

One thing to remind, this is for ASP.Net 2.0 Web Services.  The response from ASP.Net 3.5 Web Services IS different in that the response message (msg) has it's content in a property only called "d".  So instead of eval("(" + msg + ")"), it would be eval("(" + msg.d + ")").

Summary:

This post has covered a large amount of steps to get a json-based web service infrastructure setup using jQuery and ASP.Net 2.0 web services.  After a lot of low level research and asking questions to people, I realized that there was not a single location for this information. Hopefully, this post will help fill that gap.


kick it on DotNetKicks.comShout it

25 comments:

  1. Instead of creating a custom Converter Class, you could just make the CustomerInfo class [Serializable]

    ReplyDelete
  2. @Julian

    That is true. I was looking for a simple example for complex types that weren't serializable and ended up with a poor example. Probably the most common example for creating the custom Converter Class would be if you wanted to pass a DataTable over JSON. You are correct though in that just decorating the CustomerInfo class in the example as Serializable would have worked as well.

    Thanks,

    ReplyDelete
  3. Hi,

    Great post! There's a dirth of good pages on combining JSON with ASMX.

    When I tried your example, however, I had to change the jQuery dataType to "String". Alternatively, and more concise is to keep the dataType as "json" and simply change one line to
    var custInfo = msg, eliminating the eval expression.

    Thanks again for your excellent example!

    ReplyDelete
  4. What about if your webservice is in a different domain? How could you modify this code to work with the getJSON method in jquery?

    ReplyDelete
  5. @Corey

    In terms of using getJSON instead of using POST, the reason why I didn't use such is because I don't like using the HTTP GET verb unless I'm using RESTful APIs. The other obstacle is that ASP.Net AJAX (which is what I'm using here for my webservices) is very strict on JSON. It has to have the content type correct and forces the HTTP verb to be POST only. Because of this, there isn't a way that I know of to transmit true JSON via a ASMX service over HTTP GET. I'll do some digging to see what I can find though.

    As far as hitting a web service that's in a different domain, this cannot be done due to the JavaScript security model preventing such. The recommended way, at this time, to do this is to proxy the other domain's web service by creating a web service in your domain that calls it. The JSON would call your web service instead of that in the other domain. It's not the cleanest of solutions; however, it does work. I've heard arguments from both sides on whether or not the next version of JavaScript will continue to have this security feature or not, but I prefer having it in there for at least a little while longer until businesses have their services setup properly.

    ReplyDelete
  6. Very nice post. Succinct and to the point. Clears up a lot of things for me.

    Question though. I've got a JSON web service that returns a delimited string--delimited by pipe and by asterisk--which I return to the client. Once returned to the client, I take the string and parse it into a 2D array. Then i read the array once from beginning to end -- i don't have to search through it or anything.

    Would it be more efficient to do what I'm doing (passing a base type) or to create a JSON object that I can use natively with javascript? Any ideas would be appreciated.

    ReplyDelete
  7. @Trevor I used to do what you are doing with your 2D array. This practice is very common; however, I noticed that after I changed a few instances in my code to use JSON objects instead of delimited arrays that the code felt more maintainable; cleaner. Taking it out to a large chunk of data, JSON objects provided me a better performance boost also since I didn't have to do all of the index lookups and splits. I would recommend to see about refactoring to JSON objects (or an array of JSON objects) instead of multi-dimensional arrays.

    ReplyDelete
  8. I can call my webservice using jQuery IF the contentType = "application/x-www-form-urlencoded; charset=utf-8"

    This will, however, return the xml: `string [myjson] /string`

    If I try to POST to the service using "application/json; charset=utf-8" I receive a 500 error with an empty StackTrace and ExceptionType. My webservice function is never hit so I'm not quite sure how to debug this situation.

    My methods and classes are decorated with the appropriate attributes and are set to use JSON as their response type (as do my wsdl and disco files). I have the Ajax extensions installed and the required entries in web.config.

    This is on a SharePoint farm, but I'm not sure that makes too much of a difference. I deployed the web.config changes on all WFE's as well as installed the ajax extensions. Again the service works, it just will not accept anything but the default content type.

    Not sure what I'm missing here, fellas...

    my ajax call:

    $.ajax({
    type: "POST",
    url: "/_vti_bin/calendar.asmx/Test",
    dataType: "json",
    data: "{}",
    contentType: "application/json; charset=UTF-8",
    success: function(msg){
    alert(msg);
    },
    error: function(xhr, msg){ alert(msg + '\n' + xhr.responseText); }
    });

    My webservice class:

    [WebService(Namespace = "http://namespace")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ScriptService()]
    public class CalendarService : WebService
    {
    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public string Test()
    {
    return "Hello World";
    }
    }

    ReplyDelete
  9. @Steve Ruiz

    All of the times that I've seen the 500 error is usually concerning the HTTP Handler for the MS AJAX not being setup properly or the AJAX extensions not installed correctly.

    What I would do in order to try and trouble shoot this a bit more would be the following:
    1) See if you can get a response back by browsing to your web service and the method in a web browser.
    2) Drop the System.Web.extensions.dll into the bin directory. This will tell you if the installation or references aren't setup properly.
    3) Double check your web.config file to ensure that all *.asmx calls are going through the proper http hanlder.

    One of those three should provide some more information to hopefully troubleshoot this better. Lastly, I noticed your AJAX call was using the "/_vti_bin/calendar.asmx/Test" path. JavaScript uses the "/" character to represent the root path so the path you are specifying there is "http://www.yourdomain.com/_vti_bin/calendar.asmx/Test". If the "_vti_bin" directory is not from your root site, then you may want to remove the leading "/" from your path.

    Hopefully this helped. If not, let me know and I'll be more than happy to try to help you outside of these comments.

    ReplyDelete
  10. Hi there! Thanx for the excellent article!

    With regards to your response to Corey above, I am also in the same dilemma. Could you perhaps explain how to create a proxy class (that can be called as JSON with jQuery) to a webservice that is located on a different domain?

    Thank u very much!

    ReplyDelete
  11. @Shalan
    What I meant by a proxy class should have been really a proxy "service" instead. Currently, in order to do an AJAX call to some service or API not on your domain, you will need to make a call to a service on your domain first. That service that the JavaScript talks to needs to then call the 3rd party service for the information and then pass it back to the calling JavaScript.

    For Example:
    If I wanted to create a personal website and supply a Twitter feed of my latest Tweets; I would need to have my JavaScript call a web service on my own domain and then that service would need to query the Twitter API for the information.

    Hope this clarifies things a little.

    ReplyDelete
  12. nice post!!!
    I met another problem recently. I use jquery to call pagemethod without arguments. It works OK under IIS5 + WindowsXP + VS2008. But after deployed to server (IIS7 + Windows7 + VS2008), jquery cannot hit the pagemethod at all and be directed to error function with server error 500 for length required. while debugging with firebug, I noticed it seemed that something wrong with httprequest sending data

    error: function(xhr, status, error) {
    alert("AJAX Error!");
    }

    Any ideas about it? Much appreciate.

    ReplyDelete
  13. Great post. This really saves a lot of mine time on sending the JSON data for complex objects. Specially custom implementation of converter.

    ReplyDelete
  14. Really liked your article, but couldn't get it to run in my environment and wonder if you might possibly be able to steer me in the right direction.

    The little debugging I've done so far shows that the web service will return parameters and from the client side I've use the alert function to show that data is going to the data string, but then I changed it to not take any parameters at all and it's still not working,ie; nothing happens, nothing moves. I wonder if could be some type of syntax error on the 'url:' or 'data:' entries since I've seen them in a variety of ways looking at examples.

    Set up is VS2005, and I did set reference to ajax dll as you said.

    THANK YOU!

    ReplyDelete
  15. @wayne

    I pulled down the code and tried it using VS2005 and ASP.Net AJAX Extensions 1.0 (linked in the post) and was able to have it work correctly.

    In the example, the GetCustomer() web method doesn't take a parameter and thus the data parameter in the JavaScript needs to resemble:

    data: "{}",

    Can you give me any more information on your environment? I do know that in VS2008, the returned message will place the object into a msg.d property instead of just using the initial parameter like the example code shows.

    If you are trying to pass parameters to the web method, the data parameter has to match the signature of the webmethod; including parameter casing. For example if we were to add a firstName and lastName parameter to our GetCustomer() web method, the calling JavaScript code would look like this:

    data: "{firstName:'John', lastName:'Doe'}"

    If this doesn't help, please feel free to send me an email (in my profile information) and I'll try to help you out more.

    - James

    ReplyDelete
  16. James,

    Thanks for getting back. I have VS2005 (8.0.50727.42) .NET Framework Version 2.0.50727, and as I mentioned earlier I did install the AJAX extension. Don't know if it makes any difference, but your VS project link has a corrupted zip file so I set up the project using a regular web site page, adding an import namespace for system web services in the default page, adding a reference to the system dll as you instructed and otherwise tried to follow everything you showed on the screen and can't think of anything else to go over to account for why it ran in your set up and not mine.

    Would the web service name space have any bearing? I named mine http://localhost/website.

    Is there way you could recommend to check what the function is sending to the web service, if anything?

    Thanks again,

    Wayne




    I

    ReplyDelete
  17. This comment has been removed by the author.

    ReplyDelete
  18. Thanks so much for this post. This has been a lifesaver for the project I'm working on. Great job using easy to follow examples!

    ReplyDelete
  19. If I passed in an array using JSON like this:

    { "person" : [ { "FirstName" : "Peter", "LastName" : "Billings"}, { "FirstName" : "Lottie", "LastName" : "Dah"}],
    "pets" : [ { "Type" : "Dog", "Weight" : 192 } ]

    What would I need on the asmx side to receive this information as objects? If I had a person class and a pets class, would it look like this? (vb)

    Public Function SubmitInfo(ByVal person As CollectedInfo.person, ByVal unit As CollectedInfo.pets) As String

    Or, do I have to receive as arrays and then convert the arrays to the objects?

    ReplyDelete
  20. @RS

    In order to send collections of objects up to the service like you are wondering, you'll need to have your web service accept Arrays of serialize-able objects. I updated the project and added your scenario as a VB project to the solution. I'll write a blog post about some of the differences in the near future; however, until then, check out the newly uploaded 3.5 solution here.

    Hope this helps.

    ReplyDelete
  21. Thank you SO much! I was stuck on that, and now I'm moving again.

    I was missing the _ on my classes and the () to pass the objects in as an array of my object type.

    ReplyDelete
  22. @RS

    I'm glad I could help. I'll be writing the post about that new solution here shortly as well as another focusing on using WCF.

    ReplyDelete
  23. How to reverse of it..
    Means How to convert json data to Custom Class object???

    ReplyDelete
  24. @Atul Singhal

    While I haven't confirmed this recently, The System.Web.Script.JavaScriptSerializer class that I used in this post to serialize an object into json can be used to convert a json object back to a .net object.

    Code like below should work:

    var cust = new JavaScriptSerializer().deserialize(jsonstring);

    For more information, I'd check out this MSDN article.

    http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx

    ReplyDelete
  25. Thank you very much for this tutorial. Keep it up in future.

    ReplyDelete