Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.net MVC returning JSONP

I am looking to return some JSON across domains and I understand that the way to do this is through JSONP rather than pure JSON.
I am using ASP.net MVC so I was thinking about just extending the JsonResult type and then extending the Controller so that it also implemented a Jsonp method.
Is this the best way to go about it or is there a built-in ActionResult that might be better?


Solution: I went ahead and did that. Just for reference sake I added a new result:

public class JsonpResult : System.Web.Mvc.JsonResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        HttpResponseBase response = context.HttpContext.Response;

        if (!String.IsNullOrEmpty(ContentType))
        {
            response.ContentType = ContentType;
        }
        else
        {
            response.ContentType = "application/javascript";
        }
        if (ContentEncoding != null)
        {
            response.ContentEncoding = ContentEncoding;
        }
        if (Data != null)
        {
            // The JavaScriptSerializer type was marked as obsolete prior to .NET Framework 3.5 SP1
        #pragma warning disable 0618
            HttpRequestBase request = context.HttpContext.Request;

            JavaScriptSerializer serializer = new JavaScriptSerializer();
            response.Write(request.Params["jsoncallback"] + "(" + serializer.Serialize(Data) + ")");
        #pragma warning restore 0618
        }
    }
}

and also a couple of methods to a superclass of all my controllers:

protected internal JsonpResult Jsonp(object data)
{
    return Jsonp(data, null /* contentType */);
}

protected internal JsonpResult Jsonp(object data, string contentType)
{
    return Jsonp(data, contentType, null);
}

protected internal virtual JsonpResult Jsonp(object data, string contentType, Encoding contentEncoding)
{
    return new JsonpResult
    {
        Data = data,
        ContentType = contentType,
        ContentEncoding = contentEncoding
    };
}

Works like a charm.

like image 355
stimms Avatar asked Apr 17 '09 03:04

stimms


People also ask

How do I get JSONP data?

JSONP is a technique by which you put your request into a script tag URL (which is allowed to any domain) and you pass in that URL a parameter which indicates the name of a function that you want the resulting script that is returned to call and pass it your data.

Why is JSONP avoided?

JSONP is not actually JSON with padding, it's Javascript code that's executed. JSON is not a real subset of Javascript and the way it is not is important to us: via UTFGrid, we are all UTF-8 masters. JSONP is not safe: it's Javascript that's executed. It's trivial to XSS with JSONP, because JSONP is XSS.

What is JSONP callback?

JSONP is an informal protocol that allows you to make cross-domain calls by producing script tags on the current page and awaiting a response to a callback handler you provide.

What is the correct way to return JSON objects from action methods in ASP NET MVC?

In your action method, return Json(object) to return JSON to your page. SomeMethod would be a javascript method that then evaluates the Json object returned. ContentResult by default returns a text/plain as its contentType.


4 Answers

Here is a simple solution, if you don't want to define an action filter

Client side code using jQuery:

  $.ajax("http://www.myserver.com/Home/JsonpCall", { dataType: "jsonp" }).done(function (result) {});

MVC controller action. Returns content result with JavaScript code executing callback function provided with query string. Also sets JavaScript MIME type for response.

 public ContentResult JsonpCall(string callback)
 {
      return Content(String.Format("{0}({1});",
          callback, 
          new JavaScriptSerializer().Serialize(new { a = 1 })),    
          "application/javascript");
 }
like image 123
Maksym Kozlenko Avatar answered Sep 30 '22 02:09

Maksym Kozlenko


Rather than subclassing my controllers with Jsonp() methods, I went the extension method route as it feels a touch cleaner to me. The nice thing about the JsonpResult is that you can test it exactly the same way you would a JsonResult.

I did:

public static class JsonResultExtensions
{
    public static JsonpResult ToJsonp(this JsonResult json)
    {
        return new JsonpResult { ContentEncoding = json.ContentEncoding, ContentType = json.ContentType, Data = json.Data, JsonRequestBehavior = json.JsonRequestBehavior};
    }
}

This way you don't have to worry about creating all the different Jsonp() overloads, just convert your JsonResult to a Jsonp one.

like image 30
mendicant Avatar answered Sep 30 '22 03:09

mendicant


Ranju's blog post (aka "This blog post I found") is excellent, and reading it will allow you to further the solution below so that your controller can handle same-domain JSON and cross-domain JSONP requests elegantly in the same controller action without additional code [in the action].

Regardless, for the "give me the code" types, here it is, in case the blog disappears again.

In your controller (this snippet is new/non-blog code):

[AllowCrossSiteJson]
public ActionResult JsonpTime(string callback)
{
    string msg = DateTime.UtcNow.ToString("o");
    return new JsonpResult
    {
        Data = (new
        {
            time = msg
        })
    };
}

JsonpResult found on this excellent blog post:

/// <summary>
/// Renders result as JSON and also wraps the JSON in a call
/// to the callback function specified in "JsonpResult.Callback".
/// http://blogorama.nerdworks.in/entry-EnablingJSONPcallsonASPNETMVC.aspx
/// </summary>
public class JsonpResult : JsonResult
{
    /// <summary>
    /// Gets or sets the javascript callback function that is
    /// to be invoked in the resulting script output.
    /// </summary>
    /// <value>The callback function name.</value>
    public string Callback { get; set; }

    /// <summary>
    /// Enables processing of the result of an action method by a
    /// custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>.
    /// </summary>
    /// <param name="context">The context within which the
    /// result is executed.</param>
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        HttpResponseBase response = context.HttpContext.Response;
        if (!String.IsNullOrEmpty(ContentType))
            response.ContentType = ContentType;
        else
            response.ContentType = "application/javascript";

        if (ContentEncoding != null)
            response.ContentEncoding = ContentEncoding;

        if (Callback == null || Callback.Length == 0)
            Callback = context.HttpContext.Request.QueryString["callback"];

        if (Data != null)
        {
            // The JavaScriptSerializer type was marked as obsolete
            // prior to .NET Framework 3.5 SP1 
#pragma warning disable 0618
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            string ser = serializer.Serialize(Data);
            response.Write(Callback + "(" + ser + ");");
#pragma warning restore 0618
        }
    }
}

Note: Following up on the comments to the OP by @Ranju and others, I figured it was worth posting the "bare minimum" functional code from Ranju's blog post as a community wiki. Though it's safe to say that Ranju added the above and other code on his blog to be used freely, I'm not going to copy his words here.

like image 33
2 revs Avatar answered Sep 30 '22 02:09

2 revs


For ASP.NET Core ,NOT ASP.NET MVC
This is a tailored version for ASP.NET CORE of the solution which exists in the answer

public class JsonpResult : JsonResult
{
    public JsonpResult(object value) : base(value)
    {
    }

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        HttpResponse response = context.HttpContext.Response;

        if (!String.IsNullOrEmpty(ContentType))
            response.ContentType = ContentType;
        else
            response.ContentType = "application/javascript";

        if (Value != null)
        {
            HttpRequest request = context.HttpContext.Request;
            string serializedJson = JsonConvert.SerializeObject(Value);
            string result = $"{request.Query["callback"]}({serializedJson})";
            await response.WriteAsync(result);
        }
    }
}

like image 31
Hakan Fıstık Avatar answered Sep 30 '22 01:09

Hakan Fıstık