JSONP support for complex types, why not?

Topics: Web Api
Jan 19, 2012 at 10:12 PM

Hello,

I've had this discussion with almost everyone I can think of at the company I work at, explaining why complex types do not currently work in JSONP requests.
We all get it, but still I think that some headway could be made.

I understand that there is merit to waiting for people to make standards, so we can follow them. Like the JSONP standard, the SOAP standards, things produced by w3c. But I do not see anywhere in sight (I could be near sighted) for complex types in URLs for JSONP.

I also understand that HTML5 is "attempting" to relieve the issue of cross domain ajax requests, by allowing the security to be disabled if both servers handshake on it. However, to my disapproval, this does not work in modern browsers even when they claim it does.

Would it be possible to ask for this team to come up with something to allow these complex types to be passed through the URL?
The reason I ask is because to offer JSONP and over JSON at the same time is headache-intensive because you end up writing JSONP methods that just pass-thru.

I also understand that you cannot make RESTful APIs using JSONP because of the lack of access to HTTP headers.
This is what I implemented in WCF before moving to WebApi. (Not the complex type part, but the URL format, to fake REST)

Instead what I am imagining is something along the lines of:

[WebInvoke(UriTemplate = "{id}")]
[WebGet(UriTemplate = "{id}/post?{foo}{foo2}")]
public Bar Post(int id, Bar foo, Bar foo2)
{
  foo.id = id;
  return foo;
}

Example Urls:

http://tempuri.com/bar/5/post?{ id : 0, otherField: 12 }{ id : 0, otherField: 12 }
http://tempuri.com/bar/5/post?foo.id=0&foo.otherField=12&foo2.id=1&foo2.otherField=15

Anyone have any thoughts on this? I am open to any solutions really, because I hate my current strategy of "make separate services for JSONP that forward to the normal services.".

Jan 19, 2012 at 10:21 PM
Edited Jan 20, 2012 at 4:51 PM

Addition:

I forgot that WebApi is going the one for one route with input models... so this would also be something I'd look for:

 

[WebInvoke(UriTemplate = "{id}")]
[WebGet(UriTemplate = "{id}/post?{foo}")]
public Bar Post(int id, BarPostRequest foo)
{
  foo.Foo1.id = id;
  return foo.Foo1;
}

 

Example Urls:
http://tempuri.com/bar/5/post?{ Foo1 : { id : 0, otherField : 12 }, Foo2 : { id : 0, otherField: 12 }}
http://tempuri.com/bar/5/post?Foo1.id=0&Foo1.otherField=12&Foo2.id=1&Foo2.otherField=15

Jan 20, 2012 at 10:20 PM
Edited Jan 20, 2012 at 10:22 PM

For anyone who cares, here is the code I made to make the above work.

My configuration (inside RequestHandlers action):

 

                if (JsonpSerializerRequestHandler<object>.Applies(operation))
                {
                    var parameter = operation.InputParameters.FirstOrDefault(p => p.Name == "request");
                    if (parameter != null)
                    {
                        var requestType = typeof(JsonpSerializerRequestHandler<>).MakeGenericType(parameter.ParameterType);
                        var instance = Activator.CreateInstance(requestType);
                        handlers.Add((HttpOperationHandler)instance);
                    }
                }

 

JsonpSerializerRequestHandler class:

 

    public class JsonpSerializerRequestHandler<T> : HttpOperationHandler<HttpRequestMessage, T>
        where T : class, new()
    {
        public JsonpSerializerRequestHandler()
            : base("request")
        {
        }

        protected JsonpSerializerRequestHandler(string outputParameterName)
            : base(outputParameterName)
        {

        }

        public static bool Applies(HttpOperationDescription operation)
        {
            var attribute = operation.Attributes.OfType<JsonpInputAttribute>().FirstOrDefault();
            if (attribute == null)
                return false;

            return true;
        }

        protected override T OnHandle(HttpRequestMessage request)
        {
            var input = request.QueryString("request");
            if (string.IsNullOrEmpty(input))
                return null;
            
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            T instance = serializer.Deserialize<T>(HttpUtility.UrlDecode(input));
            return instance;
        }
    }

 


The JsonpInputAttribute (nothing important, its just for marking methods):

 

    public class JsonpInputAttribute : Attribute
    {
        public JsonpInputAttribute()
        {
        }
    var theData = {
        Id: 5,
        Listing: {
            Title: "Jsonp"
        }
    };

    $.getJSON("http://tempuri.com/listing/post?callback=?&username=temp&password=temp",
                  "request=" + JSON.minify(JSON.stringify(theData), true), mycallback);

    function mycallback(data) {
        $("#output").html(JSON.stringify(data));
    }

    mycallback();
}
And finally the service. I make it partial so that it shares the normal service handlers/code/etc. 
Notice the schema of "post" to mimic a post method:
        [WebGet(UriTemplate = "post")]
        [Authorize]
        [JsonpInput]
        public PostListingRequest PostJsonp(PostListingRequest request)
        {
            return request;
        }
The client code is fairly simple:

    var theData = {
        Id: 5,
        Listing: {
            Title: "Jsonp"
        }
    };

    $.getJSON("http://tempuri.org/listing/post?callback=?&username=temp&password=temp",
                  "request=" + JSON.minify(JSON.stringify(theData), true), mycallback);

    function mycallback(data) {
        $("#output").html(JSON.stringify(data));
    }

    mycallback();