JSONP with ASP.NET Web API

JqueryJsonpasp.net Mvc-4asp.net Web-Api

Jquery Problem Overview


I am working on creating a new set of services in ASP.MVC MVC 4 using the Web API. So far, it's great. I have created the service and gotten it to work, and now I am trying to consume it using JQuery. I can get back the JSON string using Fiddler, and it seems to be ok, but because the service exists on a separate site, trying to call it with JQuery errors with the "Not Allowed". So, this is clearly a case where I need to use JSONP.

I know that the Web API is new, but I'm hoping someone out there can help me.

How do I make a call to a Web API method using JSONP?

Jquery Solutions


Solution 1 - Jquery

After asking this question, I finally found what I needed, so I am answering it.

I ran across this JsonpMediaTypeFormatter. Add it into the Application_Start of your global.asax by doing this:

var config = GlobalConfiguration.Configuration;
config.Formatters.Insert(0, new JsonpMediaTypeFormatter());

and you are good to go with an JQuery AJAX call that looks like this:

$.ajax({
    url: 'http://myurl.com',
    type: 'GET',
    dataType: 'jsonp',
    success: function (data) {
        alert(data.MyProperty);
    }
})

It seems to work very well.

Solution 2 - Jquery

Here is an updated version of the JsonpMediaTypeFormatter for use with WebAPI RC:

public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
    private string callbackQueryParameter;

    public JsonpMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(DefaultMediaType);
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));

        MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
    }

    public string CallbackQueryParameter
    {
        get { return callbackQueryParameter ?? "callback"; }
        set { callbackQueryParameter = value; }
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
    {
        string callback;

        if (IsJsonpRequest(out callback))
        {
            return Task.Factory.StartNew(() =>
            {
                var writer = new StreamWriter(stream);
                writer.Write(callback + "(");
                writer.Flush();

                base.WriteToStreamAsync(type, value, stream, content, transportContext).Wait();

                writer.Write(")");
                writer.Flush();
            });
        }
        else
        {
            return base.WriteToStreamAsync(type, value, stream, content, transportContext);
        }
    }


    private bool IsJsonpRequest(out string callback)
    {
        callback = null;

        if (HttpContext.Current.Request.HttpMethod != "GET")
            return false;

        callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

        return !string.IsNullOrEmpty(callback);
    }
}

Solution 3 - Jquery

You can use an ActionFilterAttribute like this:

public class JsonCallbackAttribute : ActionFilterAttribute
{
	private const string CallbackQueryParameter = "callback";

	public override void OnActionExecuted(HttpActionExecutedContext context)
	{
		var callback = string.Empty;

		if (IsJsonp(out callback))
		{
			var jsonBuilder = new StringBuilder(callback);

			jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);

			context.Response.Content = new StringContent(jsonBuilder.ToString());
		}

		base.OnActionExecuted(context);
	}

	private bool IsJsonp(out string callback)
	{
		callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

		return !string.IsNullOrEmpty(callback);
	}
}

Then put it on your action:

[JsonCallback]
public IEnumerable<User> User()
{
	return _user;
}

Solution 4 - Jquery

Certainly Brian's answer is the correct one, however if you already are using the Json.Net formatter, which gives you pretty json dates and faster serialization, then you can't just add a second formatter for jsonp, you have to combine the two. It is a good idea to use it anyway, as Scott Hanselman has said that the release of ASP.NET Web API is going to use the Json.Net serializer by default.

public class JsonNetFormatter : MediaTypeFormatter
    {
        private JsonSerializerSettings _jsonSerializerSettings;
        private string callbackQueryParameter;

        public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
        {
            _jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();

            // Fill out the mediatype and encoding we support
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            Encoding = new UTF8Encoding(false, true);

            //we also support jsonp.
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));
            MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", "application/json"));
        }

        public string CallbackQueryParameter
        {
            get { return callbackQueryParameter ?? "jsoncallback"; }
            set { callbackQueryParameter = value; }
        }

        protected override bool CanReadType(Type type)
        {
            if (type == typeof(IKeyValueModel))
                return false;

            return true;
        }

        protected override bool CanWriteType(Type type)
        {
            return true;
        }

        protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders,
            FormatterContext formatterContext)
        {
            // Create a serializer
            JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

            // Create task reading the content
            return Task.Factory.StartNew(() =>
            {
                using (StreamReader streamReader = new StreamReader(stream, Encoding))
                {
                    using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
                    {
                        return serializer.Deserialize(jsonTextReader, type);
                    }
                }
            });
        }

        protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders,
            FormatterContext formatterContext, TransportContext transportContext)
        {
            string callback;
            var isJsonp = IsJsonpRequest(formatterContext.Response.RequestMessage, out callback);

            // Create a serializer
            JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);

            // Create task writing the serialized content
            return Task.Factory.StartNew(() =>
            {
                using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false })
                {
                    if (isJsonp)
                    {
                        jsonTextWriter.WriteRaw(callback + "(");
                        jsonTextWriter.Flush();
                    }

                    serializer.Serialize(jsonTextWriter, value);
                    jsonTextWriter.Flush();

                    if (isJsonp)
                    {
                        jsonTextWriter.WriteRaw(")");
                        jsonTextWriter.Flush();
                    }
                }
            });
        }

        private bool IsJsonpRequest(HttpRequestMessage request, out string callback)
        {
            callback = null;

            if (request.Method != HttpMethod.Get)
                return false;

            var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
            callback = query[CallbackQueryParameter];

            return !string.IsNullOrEmpty(callback);
        }
    }

Solution 5 - Jquery

Rick Strahl's implementation worked best for me with RC.

Solution 6 - Jquery

JSONP only works with Http GET request. There is a CORS support in asp.net web api which works well with all http verbs.

This article may be helpful to you.

Solution 7 - Jquery

Updated

public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
    {
        private string callbackQueryParameter;

        public JsonpMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(DefaultMediaType);
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));

            MediaTypeMappings.Add(new UriPathExtensionMapping("jsonp", DefaultMediaType));
        }

        public string CallbackQueryParameter
        {
            get { return callbackQueryParameter ?? "callback"; }
            set { callbackQueryParameter = value; }
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
        {
            string callback;

            if (IsJsonpRequest(out callback))
            {
                return Task.Factory.StartNew(() =>
                {
                    var writer = new StreamWriter(writeStream);
                    writer.Write(callback + "(");
                    writer.Flush();

                    base.WriteToStreamAsync(type, value, writeStream, content, transportContext).Wait();

                    writer.Write(")");
                    writer.Flush();
                });
            }
            else
            {
                return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
            }
        }

        private bool IsJsonpRequest(out string callback)
        {
            callback = null;

            if (HttpContext.Current.Request.HttpMethod != "GET")
                return false;

            callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

            return !string.IsNullOrEmpty(callback);
        }
    }

Solution 8 - Jquery

Here's an updated version with several improvements, which works with the RTM version of Web APIs.

  • Selects the correct encoding, based on the request's own Accept-Encoding headers. The new StreamWriter() in the previous examples would simply use UTF-8. The call to base.WriteToStreamAsync may use a different encoding, resulting in corrupted output.
  • Maps JSONP requests to the application/javascript Content-Type header; the previous example would output JSONP, but with the application/json header. This work is done in the nested Mapping class (cf. https://stackoverflow.com/questions/111302/best-content-type-to-serve-jsonp)
  • Foregoes the construction and flushing overhead of a StreamWriter and directly gets the bytes and writes them to the output stream.
  • Instead of waiting on a task, use the Task Parallel Library's ContinueWith mechanism to chain several tasks together.

Code:

public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
  private string _callbackQueryParameter;

  public JsonpMediaTypeFormatter()
  {
    SupportedMediaTypes.Add(DefaultMediaType);
    SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/javascript"));

    // need a lambda here so that it'll always get the 'live' value of CallbackQueryParameter.
    MediaTypeMappings.Add(new Mapping(() => CallbackQueryParameter, "application/javascript"));
  }

  public string CallbackQueryParameter
  {
    get { return _callbackQueryParameter ?? "callback"; }
    set { _callbackQueryParameter = value; }
  }

  public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content,
                                          TransportContext transportContext)
  {
    var callback = GetCallbackName();

    if (!String.IsNullOrEmpty(callback))
    {
      // select the correct encoding to use.
      Encoding encoding = SelectCharacterEncoding(content.Headers);

      // write the callback and opening paren.
      return Task.Factory.StartNew(() =>
        {
          var bytes = encoding.GetBytes(callback + "(");
          writeStream.Write(bytes, 0, bytes.Length);
        })
      // then we do the actual JSON serialization...
      .ContinueWith(t => base.WriteToStreamAsync(type, value, writeStream, content, transportContext))

      // finally, we close the parens.
      .ContinueWith(t =>
        {
          var bytes = encoding.GetBytes(")");
          writeStream.Write(bytes, 0, bytes.Length);
        });
    }
    return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
  }

  private string GetCallbackName()
  {
    if (HttpContext.Current.Request.HttpMethod != "GET")
      return null;
    return HttpContext.Current.Request.QueryString[CallbackQueryParameter];
  }

  #region Nested type: Mapping

  private class Mapping : MediaTypeMapping
  {
    private readonly Func<string> _param; 

    public Mapping(Func<string> discriminator, string mediaType)
      : base(mediaType)
    {
      _param = discriminator;
    }

    public override double TryMatchMediaType(HttpRequestMessage request)
    {
      if (request.RequestUri.Query.Contains(_param() + "="))
        return 1.0;
      return 0.0;
    }
  }

  #endregion
}

I'm aware of the "hackiness" of the Func<string> parameter in the inner class constructor, but it was the fastest way to get around the problem it solves -- since C# only has static inner classes, it can't see the CallbackQueryParameter property. Passing the Func in binds the property in the lambda, so Mapping will be able to access it later on in TryMatchMediaType. If you have a more elegant way, please comment!

Solution 9 - Jquery

Unfortunately, I don't have enough reputation to comment, so I'll post an answer. @Justin raised the issue of running the WebApiContrib.Formatting.Jsonp formatter alongside the standard JsonFormatter. That issue is resolved in the latest release (actually released some time ago). Also, it should work with the latest Web API release.

Solution 10 - Jquery

johperl, Thomas. The answer given by Peter Moberg above should be correct for the RC version as the JsonMediaTypeFormatter that he inherits from uses the NewtonSoft Json serializer already, and so what he has should work with out any changes.

However, why on earth are people still using out parameters, when you could just do the following

public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, TransportContext transportContext)
        {
            var isJsonpRequest = IsJsonpRequest();

            if(isJsonpRequest.Item1)
            {
                return Task.Factory.StartNew(() =>
                {
                    var writer = new StreamWriter(stream);
                    writer.Write(isJsonpRequest.Item2 + "(");
                    writer.Flush();
                    base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext).Wait();
                    writer.Write(")");
                    writer.Flush();
                });
            }

            return base.WriteToStreamAsync(type, value, stream, contentHeaders, transportContext);
        }
        
        private Tuple<bool, string> IsJsonpRequest()
        {
            if(HttpContext.Current.Request.HttpMethod != "GET")
                return new Tuple<bool, string>(false, null);

            var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];

            return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback);
        }

Solution 11 - Jquery

Instead of hosting your own JSONP formatter version you can install WebApiContrib.Formatting.Jsonp NuGet package with already implemented one (choose the version that works for your .NET Framework).

Add this formatter into Application_Start:

GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonpMediaTypeFormatter(new JsonMediaTypeFormatter()));

Solution 12 - Jquery

For those of you who are using the HttpSelfHostServer this section of code will fail on HttpContext.Current, since it doesn't exist on the self host server.

private Tuple<bool, string> IsJsonpRequest()
{
if(HttpContext.Current.Request.HttpMethod != "GET")
 return new Tuple<bool, string>(false, null);
 var callback = HttpContext.Current.Request.QueryString[CallbackQueryParameter];
 return new Tuple<bool, string>(!string.IsNullOrEmpty(callback), callback);
 }

However you can intercept the self host "context" via this override.

public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
        {
            _method = request.Method;
            _callbackMethodName =
                request.GetQueryNameValuePairs()
                       .Where(x => x.Key == CallbackQueryParameter)
                       .Select(x => x.Value)
                       .FirstOrDefault();

            return base.GetPerRequestFormatterInstance(type, request, mediaType);
        }

The request.Method will give you "GET", "POST", etc. and the GetQueryNameValuePairs can retrieve the ?callback parameter. Thus my revised code looks like:

private Tuple<bool, string> IsJsonpRequest()
 {
     if (_method.Method != "GET")
     return new Tuple<bool, string>(false, null);

     return new Tuple<bool, string>(!string.IsNullOrEmpty(_callbackMethodName), _callbackMethodName);
}

Hope this helps some of you. This way you don't necessarily need a HttpContext shim.

C.

Solution 13 - Jquery

Check this one out. See if it helps.

JSONP with Web API

Solution 14 - Jquery

If the context is Web Api, thanking and referring to 010227leo's answer, you must consider WebContext.Current value which is going to be null.

So I updated his code to this:

public class JsonCallbackAttribute
	: ActionFilterAttribute
{
	private const string CallbackQueryParameter = "callback";

	public override void OnActionExecuted(HttpActionExecutedContext context)
	{
		var callback = context.Request.GetQueryNameValuePairs().Where(item => item.Key == CallbackQueryParameter).Select(item => item.Value).SingleOrDefault();

		if (!string.IsNullOrEmpty(callback))
		{
			var jsonBuilder = new StringBuilder(callback);

			jsonBuilder.AppendFormat("({0})", context.Response.Content.ReadAsStringAsync().Result);

			context.Response.Content = new StringContent(jsonBuilder.ToString());
		}

		base.OnActionExecuted(context);
	}
}

Solution 15 - Jquery

We can solve CORS(Cross-origin resource sharing)issue using two ways,

1)Using Jsonp 2)Enabling the Cors

1)Using Jsonp- to use the Jsonp we need to install WebApiContrib.Formatting.Jsonp nuget package and need to add JsonpFormmater in WebApiConfig.cs refer screenshots,enter image description here

Jquery code enter image description here

2)Enabling the Cors -

to enable the cors we need to add Microsoft.AspNet.WebApi.Cors nuget package and need to enable cors in WebApiConfig.cs refer screenshot

enter image description here

For more reference, you can refer my sample repo on GitHub using the following link. https://github.com/mahesh353/Ninject.WebAPi/tree/develop

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBrian McCordView Question on Stackoverflow
Solution 1 - JqueryBrian McCordView Answer on Stackoverflow
Solution 2 - JqueryPeter MobergView Answer on Stackoverflow
Solution 3 - Jquery010227leoView Answer on Stackoverflow
Solution 4 - JqueryJustinView Answer on Stackoverflow
Solution 5 - JqueryPaul GView Answer on Stackoverflow
Solution 6 - Jqueryuser1186065View Answer on Stackoverflow
Solution 7 - JqueryITXGENView Answer on Stackoverflow
Solution 8 - JqueryatanamirView Answer on Stackoverflow
Solution 9 - JquerypanesofglassView Answer on Stackoverflow
Solution 10 - JquerystevethethreadView Answer on Stackoverflow
Solution 11 - JqueryMr. PumpkinView Answer on Stackoverflow
Solution 12 - JqueryCoyoteView Answer on Stackoverflow
Solution 13 - JqueryChorinatorView Answer on Stackoverflow
Solution 14 - JqueryRikkiView Answer on Stackoverflow
Solution 15 - JqueryMendaxView Answer on Stackoverflow