Corey Coogan

Python, .Net, C#, ASP.NET MVC, Architecture and Design

  • Subscribe

  • Archives

  • Blog Stats

    • 110,056 hits
  • Meta

Calling ASP.NET Web Service (ASMX) from JQuery

Posted by coreycoogan on December 17, 2010

UPDATE: 12/17/2010 12:55 AM CST – Thanks to Dave’s comments below, I learned that the script service handles object serialization to JSON for me. This means that if your return type is that of an object, it will automatically serialize this object to JSON. If you want to create an anonymous object, such as you may do from a LINQ query, just set your return type to “object”. I thought I had to turn that into a string, but that’s not necessary. Thanks again Dave. I also removed the [ScriptMethod] attribute, which is isn’t necessary and actually causes the JSON returned to be a string and not a JSON object.

I’ve not done anything real meaningful in Web Forms in some time, so I was very surprised at how much trouble I had trying to call a simple HelloWorld service from a jQuery Post and have it return some JSON back to my screen.  It took a couple hours to finally get it right, and I know I’m not the only one.  There are Stack Overflow posts about this and many blog posts as well. Unfortunately, as helpful as those posts were, it seems that I was still missing things… the things I’m going to cover in this here post.

ASMX Web Service

First, let’s start with the old school web service that I’d like to use to communicate with the client.  Sure I could go WCF, but an ASMX is just fine for my needs and darn simple. The important things to note on the service:

  • To call a service from JavaScript, the service class must be decorated with the [ScriptService] attribute.
  • The service method should not decorated with the [ScriptMethod] attribute, only the [WebMethod] attribute. The ScriptMethod causes the return JSON to be a string that needs to be deserialized on the client. Without it, it comes down as a valid JSON object, automatically deserialized by jQuery for me.
  • If you are going to return a hardcoded JSON string for jQuery to parse, it has be formated with double quotes around the names and properties.  I was using single ticks, which I have gotten away with in some cases, but not here (return “{\”response\”:\”Hello World\”}”).
  • Mark the return type of the method to “object” if you want to return with an anonymous object from a LINQ query or something like that. 

This is an example of a working service:

    [ScriptService]
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class HelloWorld : System.Web.Services.WebService
    {
        [WebMethod]
        //[ScriptMethod(ResponseFormat = ResponseFormat.Json)] Don't need this attribute.  
        public HelloWorldResponse SayIt(string name)
        {
            //the Identity of the logged in user is available
            var userName = HttpContext.Current.User.Identity.Name;
            
           return new HelloWorldResponse() {Name = name, NickName = "Mad Dog"};
        }

        public class HelloWorldResponse
        {
            public string Name { get; set; }
            public string NickName { get; set; }
        }
    }

AJAX and jQuery

Now for the jQuery, for which I’m using version 1.4.4.  Here’s the things that tripped me up:

  • I was using a JQ $.post call in my web pages to communicate with the server.  That caused me some problems in that the data was always returned as XML, no matter what I tried.  Knowing that $.post is a shortcut against the $.ajax method, I switched to using that so I could explicitly set the contentType to “application/json”.  I do realize that I can probably set that on the $.post method, as I’m pretty sure I’ve done that in the past, but that can cause confusion, especially with new developers.
  • If you are accessing more than one service in more than place, create a utility method or use the $.ajaxSetup method to configure the defaults.
  • If you don’t set the dataType to “json”, the response data is returned as xml.
  • If you don’t pass empty data when there are no parameters needed for the service, the response data will be returned as xml.
  • The dictionary passed to the service in the data parameter must be quoted or the service will throw a 500 error.  Web Service parameters are passed with the name of the parameter as the key (see example).
  • This is discovered soon enough, but the response from the service comes wrapped in another JSON object, available at the ‘d’ key.  If the value for ‘d’ is a JSON string, it will have to be deserialized into a proper JSON object.  I use jQuery’s $.parseJSON function for that. This example will return ‘d’ as a proper JSON object.
<script>
    $(document).ready(function() {

        //this would be done in a common script file if you are going to
        //make a lot of these calls
        $.ajaxSetup({ type: 'POST', dataType: 'json', contentType: 'application/json', data: {} });

        $('#btn').click(function() { 
             //call the web service with the button is clicked
            $.ajax({ url: 'webservices/helloworld.asmx/SayIt',
                data: '{ "name": "Corey" }', //ensure the data is enclosed in a quote
                success: function(data) {
                    var responseJson = data.d; //the real json is wrapped, get it and parse it
                    $('#sayit').html(responseJson.Name + ' aka ' + responseJson.NickName); //put the response value from the return json
                }
            });

            //return false to avoid a postback
            return false;
        });

    });
</script>
<div>
	<button id='btn'>Load It</button>
	<div id='sayit'></div>
</div>

Authorization and Authentication

Any ASMX service written in our web project will be available to the world.  If you are using a ROLES and MEMBERSHIP provider and your site requires authentication, use the <location> tag in your web.config to lock down the directory where you put all your services.  In my example, I’ve put all my services in a WebServices directory and only authenticated users in the Admins role have access.

 <location path="WebServices">
    <system.web>
      <authorization>
        <deny users="?"/>
        <allow roles="Admins"/>
        <deny users="*"/>
      </authorization>
    </system.web>
  </location>

If the user is logged in, there will be an active IIdentity in HttpContext.Current.User.Identity, so your service can use that for authorization where necessary.

var userName = HttpContext.Current.User.Identity.Name;
Advertisements

9 Responses to “Calling ASP.NET Web Service (ASMX) from JQuery”

  1. Dave Ward said

    There’s no need to manually serialize your web method’s response and then manually deserialize it in the success callback. As long as your request is a POST and you’ve set your content-type to application/json, a “ScriptService” will automatically JSON serialize its response.

    All you need for your service to be JSON-enabled is:

    [ScriptService]
    public class HelloWorld : WebService
    {
      [WebMethod]
      public string SayIt(string name)
      {
        return "Hello World and " + name;
      }
    }

    If I called that with “Dave” as a parameter, that would return:

    {"d":"Hello World and Dave"}

    Calling it from the client side:

    $.ajax({
      type: 'POST',
      dataType: 'json',
      contentType: 'application/json',
      url: 'WebServices/HelloWorld.asmx/SayIt',
      data: '{"name":"Dave"}',
      success: function(response) {
        // The response is wrapped in the ".d" to protect 
        //  against an attack against the array constructor.
        $('#sayit').text(response.d);
      }
    });

    When you serialize and deserialize manually on both ends, you’re doing that in addition to what ASP.NET and jQuery are already doing transparently. You’re sending a JSON string, which is serialized as JSON itself. One more level and you’ve got inception! 🙂 So, it’s not only more manual work, but it’s slower on both ends.

  2. Dave Ward said

    I’m not here to point out any err’ing. This stuff is so vaguely documented, it’s tricky to get it working at all at first. I just saw the link float by on Twitter and thought you might want to know about the more direct approach.

    Give the object serialization a try like this:

    public class Person
    {
      public string Name { get; set; }
      public string Website { get; set; }
    }
    
    [WebMethod]
    public Person GetDave()
    {
      Person Dave = new Person();
    
      Dave.Name = "Dave";
      Dave.Website = "http://encosia.com";
    
      return Dave;
    }

    That’ll come back like this (it’ll actually have an extra property describing the object’s type too):

    {"d":{"Name":"Dave","Website":"http://encosia.com"}}

    You can work with it on the client-side like this:

    $.ajax({
      // All the standard stuff here.
      success: function(response) {
        console.log("Dave's name: " + response.d.Name);
        console.log("Dave's website: " + response.d.Website); 
      }
    });

    You can return an anonymous object the same way, by declaring a return type of object. That has the advantage of not including the extra type property included in the JSON.

    • coreycoogan said

      You’re too fast Dave. I realized almost everything you just said and trashed my last comment. You must have replied just before I made the update and deleted my code. I never thought of just returning an object to handle anonymous types, so thanks for that tip. Much easier and now I will go make the third update.

  3. Oliver said

    But I have one question…

    How can I prevent a property from being serialized, ultimatley excluding it from the JSON representation of my object??

  4. What’s up everyone, it’s my first pay a visit at this
    website, and post is in fact fruitful designed for
    me, keep up posting such posts.

  5. Can you help me this is my script but I am getting an internal server error, this works fine in asp.net page but in MVC I am getting this

    var emptyUserNameMessage = ‘Please enter the username’;
    var progressUserNameMessage = ‘Checking…’;
    var availableUserNameMessage = ‘Username is available’;
    var usedUserNameMessage = ‘Username has been taken’;

    $(function () {
    var userNameAvailabilityLabel = $(“#UserNameAvailabilityLabel”);

    $(“#username”).blur(function () {
    var userNameTextBox = $(“#username”);
    var userName = userNameTextBox.val();
    if ($.trim(userName) == ”) {
    userNameAvailabilityLabel
    .removeClass()
    .addClass(‘required1’)
    .html(emptyUserNameMessage);
    }
    else {
    userNameAvailabilityLabel.html(”);
    $(“#ProgressDiv”).show();

    $.ajax({
    type: “POST”,
    url: “forumApp/Checkavailability.asmx/CheckUserNameAvailability”,
    data: “{userName: \”” + userNameTextBox.val() + “\”}”,
    contentType: “application/json; charset=utf-8”,
    dataType: “json”,
    success: OnCheckUserNameAvailabilitySuccess,
    error: OnCheckUserNameAvailabilityError
    });
    }
    return false; //Prevent postback
    });

    function OnCheckUserNameAvailabilitySuccess(response) {
    $(“#ProgressDiv”).hide();
    if (response != null && response.d != null) {
    var data = response.d;
    switch (data) {
    case 0:
    userNameAvailabilityLabel
    .removeClass()
    .addClass(‘available’)
    .html(availableUserNameMessage);
    $(“#btnSubmit”).removeAttr(‘disabled’);
    break;
    case 1:
    userNameAvailabilityLabel
    .removeClass()
    .addClass(‘used’)
    .html(usedUserNameMessage);
    $(“#btnSubmit”).attr(‘disabled’, ‘disabled’);
    break;
    }
    }
    }
    function OnCheckUserNameAvailabilityError(xhr, ajaxOptions, thrownError) {
    alert(xhr.statusText);
    }
    });

    • coreycoogan said

      If you are getting a 500 back from the server, then something is wrong from within the service. If you are controlling the service, try to add a breakpoint and debug the call to see where it is bombing. You could also log an error using log4net or NLog, or even Debug.Write.

      If you want to make sure your jQuery code is solid, you can make a dummy service and test against that as well, which might help you isolate your problem. The jQuery you posted looks just fine.

Sorry, the comment form is closed at this time.

 
%d bloggers like this: