20

I've written a webservice which returns JSON and I've tried to call it using jQuery like this:

$.ajax({
    contentType: "application/json; charset=utf-8",
    url: "http://examplewebsite.com/service.asmx/GetData",
    data: { projectID: 1 },
    dataType: "jsonp",
    success: function () {alert("success");}
});

However the code never calls the success function, despite the webservice call being successful when looking at the HTTP traffic using Fiddler. I think this is because my web service is returning raw JSON instead of JSONP.

How can I produce JSONP as the response from a standard .NET webservice method like this:

[WebMethod(), ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public Project GetData(int projectID)
{
    Project p = new Project();
    p.Name = "foobar";
    return p;
}

Thanks.

NickG
  • 9,315
  • 16
  • 75
  • 115
  • I've only done it with an ASP.Net Web API based on this: http://stackoverflow.com/a/9422178/585552 it may give you some ideas... – Greg Jan 08 '13 at 18:20
  • Try adding `Response.AddHeader("Access-Control-Allow-Origin","*")` in the body of the Web Method – Icarus Jan 08 '13 at 18:25

3 Answers3

44

OK, I've eventually figured it out myself. As I found it so hard to find a complete working solution on the web, I've decided to document my working solution here.

A JSONP response is just standard JSON string wrapped in a function call. ASP.NET doesn't seem to provide any way to return the reponse in this format directly, but it's very simple to do this yourself. You do though, have to override the default method of JSON encoding.

Below is an example of JSONP.

functionName({ name: 'value';});

..now this bit: { name: 'value';} is just standard JSON that any JSON serializer will give you, so all we need to do is tack on the function call wrapper. Unfortunately, doing that means we have to 'unwire' (or bypass) the existing JSON encoding which is handled transparently by the framework when you return an object from the web service function.

This is done by overriding the response from the web service function completely by writing the JSONP to the output stream (Response) using our own code. This is actually quite straightforward and I've included an example below.

You can use either the built in DataContractJsonSerializer (from the System.Runtime.Serialization.Json namespace in ASP.NET 3.5+) or the NewtonSoft JSON serializer, and both examples are shown below. I prefer to use the the NewtonSoft JSON (installed from nuget) rather than the built in JSON serializer as I find it gives you more control and also can output nicely formatted human readable JSON for debugging. It's also much faster on paper!

[WebMethod()]
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public void GetData(int projectID, string callback)
{
    List<Video> videos = null;
    // <code here to populate list on line above>

    // Method 1: use built-in serializer:
    StringBuilder sb = new StringBuilder();
    JavaScriptSerializer js = new JavaScriptSerializer();
    sb.Append(callback + "(");
    sb.Append(js.Serialize(videos));
    sb.Append(");");    

    // Method 2: NewtonSoft JSON serializer (delete as applicable)
    // StringBuilder sb = new StringBuilder();
    // sb.Append(callback + "(");
    // sb.Append(JsonConvert.SerializeObject(videos, Formatting.Indented)); // indentation is just for ease of reading while testing
    // sb.Append(");");     

    Context.Response.Clear();
    Context.Response.ContentType = "application/json";
    Context.Response.Write(sb.ToString());
    Context.Response.End();
}

This method can then be called using the following JQuery code:

$.ajax({
    crossDomain: true,
    contentType: "application/json; charset=utf-8",
    url: "http://examplewebsite.com/service.asmx/GetData",
    data: { projectID: 1 }, // example of parameter being passed
    dataType: "jsonp",
    success: onDataReceived
});

function onDataReceived(data)
{
    alert("Data received");
    // Do your client side work here.
    // 'data' is an object containing the data sent from the web service 
    // Put a JS breakpoint on this line to explore the data object
}
NickG
  • 9,315
  • 16
  • 75
  • 115
  • You didnt send `callback` in ajax request. How could you receive it in c# method? – Sami Sep 03 '15 at 12:37
  • 1
    @Sami - it's automatically added by JQuery – NickG Sep 03 '15 at 16:50
  • Hi @NickG how to send multiple data parameters, I tried ` data: { mNo: txtLoginMobile, pWord: txtLoginPassword},` and used `public void checkLogin(string callback, string mNo, string pWord)` however, it's giving '500 Internal Server Error'. Can run webservice directly. Please advice. I'm using entity serialization. – Ravimallya Oct 08 '15 at 09:28
  • @Ravimallya Use Fiddler to check exactly what is being sent and make sure it's encoded as valid JSON. Check for JS console errors in the browser. – NickG Oct 08 '15 at 13:37
  • @Ravimallya the names of the parameters will need to exactly match the names on the server-side C# function (Case Sensitive). – NickG Oct 08 '15 at 13:38
  • @Ravimallya I'm sure your server should be returning more detail than just "500 Internal Server Error" so please use Fiddler or server-side debugging to see the exact error. If you're still stuck, please post a **new** StackOverflow question with your C# and JS code. – NickG Oct 08 '15 at 13:40
  • @NickG thanks for quick reply. I was able to fix the issue. Actually I had to add HttpPost and HttpGet protocols in web.config. Thanks again. :) – Ravimallya Oct 08 '15 at 14:31
  • @NickG This really saved me. Thanks so much – Dowlers Apr 27 '16 at 15:04
  • @NickG: I have the same problem, i used your trick, now it goes in success function, but it does not gets the JSON, i tried to run the webservice independently and it says missing parameter callback, could you please help here? – Abbas Feb 22 '17 at 13:30
  • @Abbas sorry I don't think I can help with that remotely. You'd need to debug using the F12 view, or using Fiddler and watch what is being sent/received over the network. – NickG Feb 24 '17 at 16:20
  • It seems like there should be a solution for this workaround by now. Is Context.Response.Write still the best solution with VS2015? – Corey Alix Mar 27 '17 at 16:53
  • The content type of the response needs to be "application/javascript" and not "application/json". It will not work with "application/json" if you use the X-Content-Type-Options=nosniff – Alon Gingold Apr 28 '21 at 22:30
  • @AlonGingold - no "application/json" works fine. At least in Edge, Chrome and Firefox. – NickG May 02 '23 at 09:46
3

Thanks Nick, that was an excellent answer to a problem that I too had a hard time finding at first online. Worked great for me as well.

Wanted to make sure this this line of post got the attention it deserves.

Just wanted to add that I used the built in serializer (System.Runtime.Serialization.Json) and it worked like a charm as well.

        List<orderHistory> orderHistory = null;

        StringBuilder sb = new StringBuilder();
        JavaScriptSerializer js = new JavaScriptSerializer();
        sb.Append(callback + "(");
        sb.Append(js.Serialize(orderHistory));
        sb.Append(");");

        Context.Response.Clear();
        Context.Response.ContentType = "application/json";
        Context.Response.Write(sb.ToString());
        Context.Response.End();
Joe Ramos
  • 31
  • 1
  • The content type of the response needs to be "application/javascript" and not "application/json". It will not work with "application/json" if you use the X-Content-Type-Options=nosniff – Alon Gingold Apr 28 '21 at 22:30
2

In case someone is looking for sample how to return JSONP from ASP.NET Web API action:

// GET api/values
public JsonpResult Get()
{
    var values = new string[] { "value1", "value2" };
    return new JsonpResult(values);
}

JsonpResult helper class encapsulating the JSONP wrapping.

public class JsonpResult : JsonResult
{
    object _data = null;

    public JsonpResult(object data)
    {
        _data = data;
    }

    public override void ExecuteResult(ControllerContext controllerContext)
    {
        if (controllerContext != null)
        {
            var response = controllerContext.HttpContext.Response;
            var request = controllerContext.HttpContext.Request;

            var callBackFunction = request["callback"];
            if (string.IsNullOrEmpty(callBackFunction))
            {
                throw new Exception("Callback function name must be provided in the request!");
            }
            response.ContentType = "application/x-javascript";
            if (_data != null)
            {
                var serializer = new JavaScriptSerializer();
                response.Write(string.Format("{0}({1});", callBackFunction, serializer.Serialize(_data)));
            }
        }
    }
}
Dmitry Pavlov
  • 30,789
  • 8
  • 97
  • 121