Calling Remote ASP.NET Web Services from JQuery

December 2nd, 2008

This is Jason masquerading as a guest. I really should post more often…

I spent the best part of a day banging my head against the wall with this problem and I thought that I’d share the solution that I came up with. I’m sure that there’s a better way to do this and if there is I’d love to know what it is. And of course there are other viable approaches such as calling a local Web Service that would forward the request onto the remote server. But I am extremely stubborn and I HATE losing (both a good and a bad quality and probably worthy of a post in it’s own right) so once I started down this path I was not about to give up without a fight.

What I wanted to do is trivial against when interacting with ASP.NET Web Services on a local server using JSON rather than XML. A great post by David Ward explains how to do this. But when I tried this approach against a different domain it failed. Bob Ippolito explains the problem in his original proposal for the JSONP standard. Now JQuery supports JSONP so I thought that this would be easy from here but I was wrong.

I hooked up the following Javascript to a button click event on a test page:

function test() {
    $.ajax({ url: http://remoteserver/web_service.asmx/TestMethod”,
        data: { ID: 1 },
        dataType: “jsonp”,
        success: function(json) {
            alert(json.d);
        }
    });
}

The call was successful but the returned data was XML not JSON:

<string xmlns=”http://tempuri.org/”>abc</string>

This resulted in a ‘missing ; before statement’ error when JQuery tried to parse the data. After doing some research and looking at the difference is the requests generated by the JSON and JSONP dataTypes I realized that the issue was that the correct Content-Type was not being set by JQuery. This is required by ASP.NET Web Services as as explained here by Scott Guthrie. Rather than take on the challenge in the JQuery source I added a simple HttpModule to the remote server that sets the Content-Type to ‘application/json; charset=utf-8′ for remote requests.

After doing this I was finally receiving JSON:

{“d”:”abc”}

But I was now seeing an ‘Invalid label’ error.

After some more Googling I stumbled across this article by T Bogard. The salient point is that I had to append the name of the callback function to the returned JSON so that the browser knows what code to execute when it receives the response. Basically ASP.NET Web Services are, to the best of my knowledge, not JSONP aware. I already had an HttpModule in play and so I figured that would be the best place to take care of the wrapping. A post by Phil Hack explained how to modify the HttpResponse. Now the returned JSON looks like this:

jsonp1227742793574({“d”:”abc”});

Finally the message box is displayed with the correct data…and I can finally get some sleep.

  • Pingback: Dew Drop - December 3, 2008 | Alvin Ashcraft's Morning Dew

  • Alexei

    Best article about. NET jsonp jQuery. Thank you.

  • Sundeep

    Hi,

    By seeing this article I am wondering about the security issues by exposing the url in HTML source. If anyone can query web services hosted on other websites, then how can it be tackled.

    Thanks,
    Sundeep.

  • Maciej Nejmantowicz

    Jason,

    Get post. Can you please share the code for the HttpModule?

    Maciej

  • http://elegantcode.com Jason Grundy

    Here you go Maciej:

    using System;
    using System.Web;

    namespace ABF.Reporting.Web
    {
    public class JsonHttpModule : IHttpModule
    {
    private const string JSON_CONTENT_TYPE = “application/json; charset=utf-8″;

    #region IHttpModule Members
    public void Dispose()
    {
    }

    public void Init(HttpApplication app)
    {
    app.BeginRequest = OnBeginRequest;
    app.ReleaseRequestState = OnReleaseRequestState;
    }
    #endregion

    public void OnBeginRequest(object sender, EventArgs e)
    {
    HttpApplication app = (HttpApplication)sender;
    HttpRequest resquest = app.Request;
    if (!resquest.Url.AbsolutePath.Contains(“ReportQueueService.asmx”)) return;

    if (string.IsNullOrEmpty(app.Context.Request.ContentType))
    {
    app.Context.Request.ContentType = JSON_CONTENT_TYPE;
    }
    }

    public void OnReleaseRequestState(object sender, EventArgs e)
    {
    HttpApplication app = (HttpApplication)sender;
    HttpResponse response = app.Response;
    if (app.Context.Request.ContentType != JSON_CONTENT_TYPE) return;

    response.Filter = new JsonResponseFilter(response.Filter);
    }
    }
    }

    using System.IO;
    using System.Text;
    using System.Web;

    namespace ABF.Reporting.Web
    {
    public class JsonResponseFilter : Stream
    {
    private readonly Stream _responseStream;
    private long _position;

    public JsonResponseFilter(Stream responseStream)
    {
    _responseStream = responseStream;
    }

    public override bool CanRead { get { return true; } }

    public override bool CanSeek { get { return true; } }

    public override bool CanWrite { get { return true; } }

    public override long Length { get { return 0; } }

    public override long Position { get { return _position; } set { _position = value; } }

    public override void Write(byte[] buffer, int offset, int count)
    {
    string strBuffer = Encoding.UTF8.GetString(buffer, offset, count);
    strBuffer = AppendJsonpCallback(strBuffer, HttpContext.Current.Request);
    byte[] data = Encoding.UTF8.GetBytes(strBuffer);
    _responseStream.Write(data, 0, data.Length);
    }

    private string AppendJsonpCallback(string strBuffer, HttpRequest request)
    {
    return request.Params["callback"] “(” strBuffer “);”;
    }

    public override void Close()
    {
    _responseStream.Close();
    }

    public override void Flush()
    {
    _responseStream.Flush();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
    return _responseStream.Seek(offset, origin);
    }

    public override void SetLength(long length)
    {
    _responseStream.SetLength(length);
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
    return _responseStream.Read(buffer, offset, count);
    }
    }
    }

    • http://www.facebook.com/doncisco1979 Frankie Rodriguez

      Hey guys. Just a little side note. There was a little something missing form this code that I had to change up. I changed this line of code to make the WSDL service continue to work as normal to allow both JSON requests and XML requests.

      if (!request.Url.AbsolutePath.Contains(“ServiceName.asmx”) || request.Url.Query.Contains(“?WSDL”))

      Without this the WSDL was not being properly generated.

  • http://www.runxc.com runxc1

    Let me start off by saying that I know that asp.net can be fairly picky about how you send and recieve JSON especially with dates. What you have done looks decent but you should realy give jmsajax a look it is a plugin for jQuery made for asp.net. Below is a little snippet that works wonderfully

    $.jmsajax({
    url: “Default.aspx”,
    method: “SomeMethod”,
    data: {containerIdsAssigned: containerIdsAssigned, containerIdsUnAssigned: containerIdsUnAssigned},
    success: function(jsonResponse) {
    //Do Something here
    }
    });

  • Fatih Kiymet

    great post