JsonP support

Topics: Web Api
Oct 5, 2011 at 2:15 AM

Hi guys,

Are there plans to WCF Web API to support JsonP natively (something like the WebHttpBinding.CrossDomainScriptAccessEnabled property)?

Coordinator
Oct 6, 2011 at 6:51 AM

JSONP support is in the works. Please check out the JSONP prototype implementation and sample that we just added on the CodePlex site.

Daniel Roth

From: israelaece [email removed]
Sent: Tuesday, October 04, 2011 7:16 PM
To: Daniel Roth
Subject: JsonP support [wcf:274789]

From: israelaece

Hi guys,

Are there plans to WCF Web API to support JsonP natively (something like the WebHttpBinding.CrossDomainScriptAccessEnabled property)?

Oct 6, 2011 at 4:59 PM
Edited Oct 6, 2011 at 5:00 PM

Can you point to the newly added sample for JSONP, I couldn't find it?

Thanks

Coordinator
Oct 6, 2011 at 5:08 PM

http://wcf.codeplex.com/SourceControl/changeset/view/2a521ec9f398#WCFWebApi%2fJsonpSample%2fJsonpSample.csproj

Daniel Roth

From: mhoward2467 [email removed]
Sent: Thursday, October 06, 2011 10:00 AM
To: Daniel Roth
Subject: Re: JsonP support [wcf:274789]

From: mhoward2467

Can you point o the newly added sample for JSONP, I couldn't find it?

Thanks

Read the full discussion online.

To add a post to this discussion, reply to this email (wcf@discussions.codeplex.com)

To start a new discussion for this project, email wcf@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com

Oct 6, 2011 at 5:23 PM

Great thanks!

Oct 6, 2011 at 5:39 PM

Mr Roth, thanks again for the quick repsonse. Will there be an update to the Web Api download with the JSONP addition? If so do youknow when? I would like to use it in a current project, but I'm not sure what I need to reference to get it working. Since this is still a great work in progess is it wise to try to use this functionality at this time.

Thank you,

 

Coordinator
Oct 7, 2011 at 5:42 AM

The JSONP support will show up in the next NuGet update as part of the Web API Enhancements package, but the exact date is still to be determined. The Web API Enhancements are a set of early prototypes that we are looking to get feedback on from the community before we add them to the core product. For now you can build the enhancements assembly from the prototype branch and just use that. Feel free to use any of the enhancements for your projects, but just be aware that they are not as far along in the development process.

Daniel Roth

From: mhoward2467 [email removed]
Sent: Thursday, October 06, 2011 10:39 AM
To: Daniel Roth
Subject: Re: JsonP support [wcf:274789]

From: mhoward2467

Mr Roth, thanks again for the quick repsonse. Will there be an update to the Web Api download with the JSONP addition? If so do youknow when? I would like to use it in a current project, but I'm not sure what I need to reference to get it working. Since this is still a great work in progess is it wise to try to use this functionality at this time.

Thank you,

Coordinator
Oct 7, 2011 at 4:01 PM

The JSONP sample was actually in the wrong location. I've moved it under the samples folder with the rest of the samples:

http://wcf.codeplex.com/SourceControl/changeset/view/556aed02b9a0#WCFWebApi%2fHttp%2fsamples%2fJsonpSample%2fJsonpSample.csproj

Daniel Roth

Oct 27, 2011 at 6:14 PM

Daniel,

Thanks for the sample.

Can you provide an example of a MediaTypeFormatter where the application/javascript accept header returns json and not xml?

I'm trying to do jsonp and actually have the json returned to my client side jquery app, but I always get xml.

Thanks,

Adam

Coordinator
Oct 27, 2011 at 7:17 PM

The JsonpMediaTypeFormatter in the prototype branch is configured to respond to request for application/javascript.

However, I’m hearing from some folks that browsers frequently just send */* for the Accept header when doing jsonp requests. See http://wcf.codeplex.com/discussions/276947 for the discussion and a possible workaround (I haven’t tried it yet).

Daniel Roth

Oct 28, 2011 at 4:26 PM
Edited Nov 2, 2011 at 5:37 AM

To anyone interested, I got the JsonP support working utilizing the following custom formatter and delegating handler:

The delegating handler defaults the Accept header to 'custom/json' if it comes across as */*. If the url contains a ?callback, it changes the Accept header to 'application/javascript' which is the proper header for JsonP requests. This is needed because Google Chrome and Firefox send an Accept header of */* for JsonP requests, so this has to be handled server-side. So, basically I'm defaulting to Json as my preferred content-type and handling JsonP cases based on the querystring.

I looked at using the JsonpMediaTypeFormatter that Daniel mentioned in the prototype branch, but ended up going with one using Json.NET because it worked better with EntityFramework types and ExpandoObjects. It's basically the same customer formatter in this NuGet package by Clarius Consuling: http://nuget.org/List/Packages/netfx-Microsoft.ApplicationServer.Http.JsonNetMediaTypeFormatter I just extended it to handle JsonP using the same technique in Daniel's JsonpMediaTypeFormatter.

public class JsonNetMediaTypeFormatter : MediaTypeFormatter
{
	private JsonSerializerSettings serializerSettings;

	/// <summary>
	/// Initializes a new instance of the <see cref="JsonNetMediaTypeFormatter"/> class with 
	/// <see cref="ReferenceLoopHandling.Ignore "/> for the Json serializer.
	/// </summary>
	public JsonNetMediaTypeFormatter()
		: this(new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore })
	{
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="JsonNetMediaTypeFormatter"/> class with 
	/// the specified Json serializer settings.
	/// </summary>
	public JsonNetMediaTypeFormatter(JsonSerializerSettings serializerSettings)
	{
		this.serializerSettings = serializerSettings;

		this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json"));
		this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
		this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("custom/json"));
		this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/bson"));
		this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/javascript"));
	}

	/// <summary>
	/// Serializes the given value to the stream using Json.
	/// </summary>
	protected override void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext context)
	{
		var serializer = JsonSerializer.Create(this.serializerSettings);
		// NOTE: we don't dispose or close the writer as that would 
		// close the stream, which is used by the rest of the pipeline.
		var writer = GetWriter(contentHeaders, stream);

		IEnumerable<string> callbackValues = null;
		if (contentHeaders.ContentType.MediaType == "application/javascript")
		{
			contentHeaders.Add("jsonp-callback", HttpContext.Current.Request.QueryString["callback"]);
		}
		if (contentHeaders.TryGetValues("jsonp-callback", out callbackValues))
		{
			var callback = callbackValues.First<string>();
			writer.WriteRaw(callback + "(");
			writer.Flush();
		}
		if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IQueryable<>)))
		{
			serializer.Serialize(writer, ((IEnumerable)value).OfType<object>().ToList());
		}
		else
		{
			serializer.Serialize(writer, value);
		}
		if (contentHeaders.TryGetValues("jsonp-callback", out callbackValues))
		{
			writer.WriteRaw(")");
			writer.Flush();
		}

		writer.Flush();
	}

	/// <summary>
	/// Deserializes the stream as Json.
	/// </summary>
	protected override object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders)
	{
		var serializer = JsonSerializer.Create(this.serializerSettings);
		var reader = GetReader(contentHeaders, stream);

		var result = serializer.Deserialize(reader, type);

		return result;
	}

	private static JsonReader GetReader(HttpContentHeaders contentHeaders, Stream stream)
	{
		if (contentHeaders.ContentType.MediaType.EndsWith("json") || contentHeaders.ContentType.MediaType.EndsWith("javascript"))
			return new JsonTextReader(new StreamReader(stream));
		else
			return new BsonReader(stream);
	}

	private JsonWriter GetWriter(HttpContentHeaders contentHeaders, Stream stream)
	{
		if (contentHeaders.ContentType.MediaType.EndsWith("json") || contentHeaders.ContentType.MediaType.EndsWith("javascript"))
			return new JsonTextWriter(new StreamWriter(stream));
		else
			return new BsonWriter(stream);
	}
}

DefaultToJsonHandler:

public class DefaultToJsonHandler : DelegatingHandler
{
	protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
	{
		if (ShouldForceJsonP(request))
		{
			request.Headers.Accept.Clear(); //clear the accept and replace it to use JSON.
			request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/javascript"));
		} else if (ShouldForceJson(request))
		{
			request.Headers.Accept.Clear(); 
			//clear the accept and replace it to use custom/json until bug is fixed in Preview 5 where custom json formatters must inherit from JsonMediaTypeFormatter.
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("custom/json"));
		}
		return base.SendAsync(request, cancellationToken);
	}

	public bool ShouldForceJson(HttpRequestMessage request)
	{
		//replace with custom logic such as looking at the accept headers
		if (request.Headers.Accept.Contains(new MediaTypeWithQualityHeaderValue("*/*")))
		{
			return true;
		}
		return false;
	}

	public bool ShouldForceJsonP(HttpRequestMessage request)
	{
		//replace with custom logic such as looking at the accept headers
		if (request.RequestUri.Query.Contains("callback"))
		{
			return true;
		}
		return false;
	}
}
This is all registered in Global.asax.cs like so:
var config = new NinjectWebApiConfiguration()
{
	EnableTestClient = true, 
	MessageHandlerFactory = () => new[] { new DefaultToJsonHandler() }
};
config.Formatters.JsonFormatter.MediaTypeMappings.Clear();
config.Formatters.Clear();
config.Formatters.Insert(0, new JsonNetMediaTypeFormatter());
Dec 9, 2011 at 1:46 PM

Ahemm, actually, the above code doesn't work because HttpContext.Current is null. (I've got the WCF side running under IIS Express, and the client side is serverless.)

Dec 9, 2011 at 2:03 PM

JSONP is supported OOTB http://wcf.codeplex.com/discussions/281318