asp.net-mvc-3jsonpmodel-binding

ASP.NET MVC 3 JSONP: Does this work with JsonValueProviderFactory?


Phil Haack has an excellent blog post on how to use JSON, data binding, and data validation.

Enter the browser's "same origin policy security restriction." and JSONP where you use $.getJSON() to retrieve the content.

Is there a built in MVC 3 way to do this, or do I need to follow the advice of posts like this? Can you post content? I ask because my colleague implemented a JsonPfilterAttribute among other things to make this work. It's obviously preferred to avoid that if something already exists in MVC 3.

Edit:

Summary: everything works with the exception of accessing a POST variable, i.e., how do I access the POST variable in the context? (comment marking it in the last section of code)

I elected to use this format to call the server:

$.ajax({
    type: "GET",
    url: "GetMyDataJSONP",
    data: {},
    contentType: "application/json; charset=utf-8",
    dataType: "jsonp",
    jsonpCallback: "randomFunctionName"
});

Which produces this response:

randomFunctionName([{"firstField":"111","secondField":"222"}]);

And all this works very well if I use a GET. However, I still cannot get this to work as a POST. Here's the original code posted by Nathan Bridgewater here. This line doesn't find the POST data:

context.HttpContext.Request["callback"];

Either I should be accessing Form in some way, or the MVC data validators are stripping out the POST variables.

How should context.HttpContext.Request["callback"]; be written to access the POST variable or is MVC stripping out these values for some reason?

namespace System.Web.Mvc
{   public class JsonpResult : ActionResult
    {   public JsonpResult() {}

        public Encoding ContentEncoding { get; set; }
        public string ContentType { get; set; }
        public object Data { get; set; }
        public string JsonCallback { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {   if (context == null)
               throw new ArgumentNullException("context");

            this.JsonCallback = context.HttpContext.Request["jsoncallback"];

            // This is the line I need to alter to find the form variable:

            if (string.IsNullOrEmpty(this.JsonCallback))
                this.JsonCallback = context.HttpContext.Request["callback"];

            if (string.IsNullOrEmpty(this.JsonCallback))
                throw new ArgumentNullException(
                    "JsonCallback required for JSONP response.");

            HttpResponseBase response = context.HttpContext.Response;

            if (!String.IsNullOrEmpty(ContentType))
               response.ContentType = ContentType;
            else
               response.ContentType = "application/json; charset=utf-8";

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

            if (Data != null)
            {   JavaScriptSerializer serializer = new JavaScriptSerializer();
                response.Write(string.Format("{0}({1});", this.JsonCallback,
                    serializer.Serialize(Data)));
    }   }   }

    //extension methods for the controller to allow jsonp.
    public static class ContollerExtensions
    {
        public static JsonpResult Jsonp(this Controller controller, 
               object data)
        {
            JsonpResult result = new JsonpResult();
            result.Data = data;
            result.ExecuteResult(controller.ControllerContext);
            return result;
        }
    }
}

Solution

  • As far as receiving a JSON string and binding it to a model is concerned the JsonValueProviderFactory does this job out of the box in ASP.NET MVC 3. But there is nothing built-in for outputting JSONP. You could write a custom JsonpResult:

    public class JsonpResult : JsonResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            var request = context.HttpContext.Request;
            var response = context.HttpContext.Response;
            string jsoncallback = (context.RouteData.Values["jsoncallback"] as string) ?? request["jsoncallback"];
            if (!string.IsNullOrEmpty(jsoncallback))
            {
                if (string.IsNullOrEmpty(base.ContentType))
                {
                    base.ContentType = "application/x-javascript";
                }
                response.Write(string.Format("{0}(", jsoncallback));
            }
            base.ExecuteResult(context);
            if (!string.IsNullOrEmpty(jsoncallback))
            {
                response.Write(")");
            }
        }
    }
    

    And then in your controller action:

    public ActionResult Foo()
    {
        return new JsonpResult
        {
            Data = new { Prop1 = "value1", Prop2 = "value2" },
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };
    }
    

    which could be consumed from another domain with $.getJSON():

    $.getJSON('http://example.com/home/foo?jsoncallback=?', function(data) {
        alert(data.Prop1);
    });