jquery jsonp asp.net-mvc-4 asp.net-web-api

jquery - web api json c#



JSONP con API web ASP.NET (15)

Estoy trabajando en la creación de un nuevo conjunto de servicios en ASP.MVC MVC 4 utilizando la API web. Hasta ahora, es genial. Creé el servicio y lo puse a funcionar, y ahora estoy tratando de consumirlo usando JQuery. Puedo recuperar la cadena JSON usando Fiddler, y parece estar bien, pero debido a que el servicio existe en un sitio separado, trato de llamarlo con los errores de JQuery con el "No permitido". Entonces, este es claramente un caso donde necesito usar JSONP.

Sé que la API web es nueva, pero espero que alguien me pueda ayudar.

¿Cómo realizo una llamada a un método de API web utilizando JSONP?


Aquí hay una versión actualizada con varias mejoras, que funciona con la versión RTM de API web.

  • Selecciona la codificación correcta, según los propios encabezados de Accept-Encoding la solicitud. El new StreamWriter() en los ejemplos anteriores simplemente usaría UTF-8. La llamada a base.WriteToStreamAsync puede usar una codificación diferente, lo que da como resultado una salida dañada.
  • Mapas JSONP solicita al encabezado Content-Type la application/javascript ; el ejemplo anterior generaría JSONP, pero con el encabezado application/json . Este trabajo se realiza en la clase de Mapping anidado (ver Mejor tipo de contenido para servir a JSONP? )
  • Evita la construcción y la sobrecarga de descarga de un StreamWriter y obtiene directamente los bytes y los escribe en la secuencia de salida.
  • En lugar de esperar en una tarea, use el mecanismo ContinueWith la Biblioteca paralela de tareas para encadenar varias tareas.

Código:

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 }

Soy consciente del "hackiness" del parámetro Func<string> en el constructor de la clase interna, pero fue la forma más rápida de resolver el problema que resuelve, ya que C # solo tiene clases internas estáticas, no puede ver la propiedad CallbackQueryParameter . Pasar el Func en vincula la propiedad en el lambda, por lo que Mapping podrá acceder más adelante en TryMatchMediaType . Si tienes una forma más elegante, ¡comenta!


Aquí hay una versión actualizada de JsonpMediaTypeFormatter para usar con 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); } }


Ciertamente, la respuesta de Brian es la correcta, sin embargo, si ya está usando el formateador Json.Net, que le da fechas bastante json y una serialización más rápida, entonces no puede simplemente agregar un segundo formateador para jsonp, debe combinar los dos. Es una buena idea usarlo de todos modos, ya que Scott Hanselman ha dicho que el lanzamiento de ASP.NET Web API va a usar el serializador Json.Net de forma predeterminada.

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); } }


Desafortunadamente, no tengo suficiente reputación para comentar, así que publicaré una respuesta. @Justin planteó el problema de ejecutar el formateador WebApiContrib.Formatting.Jsonp junto con el JsonFormatter estándar. Ese problema se resuelve en la última versión (realmente lanzada hace algún tiempo). Además, debería funcionar con la última versión de la API web.


Después de hacer esta pregunta, finalmente encontré lo que necesitaba, así que lo estoy respondiendo.

Me encontré con este JsonpMediaTypeFormatter . Agréguelo al Application_Start de su global.asax haciendo esto:

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

y está listo para usar una llamada JQuery AJAX que se ve así:

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

Parece que funciona muy bien.


En lugar de alojar su propia versión del formateador JSONP, puede instalar el paquete WebApiContrib.Formatting.Jsonp NuGet con uno ya implementado (elija la versión que funcione para su .NET Framework).

Agregue este formateador a Application_Start :

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


JSONP solo funciona con la solicitud Http GET. Existe un soporte CORS en asp.net web api que funciona bien con todos los verbos http.

This artículo puede ser útil para usted.



Para aquellos de ustedes que están usando HttpSelfHostServer, esta sección de código fallará en HttpContext.Current, ya que no existe en el servidor de host autónomo.

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); }

Sin embargo, puede interceptar el "contexto" del host del self a través de esta anulación.

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); }

El método request.the le dará "GET", "POST", etc. y GetQueryNameValuePairs puede recuperar el parámetro? Callback. Por lo tanto, mi código revisado se ve así:

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); }

Espero que esto ayude a alguno de ustedes. De esta forma, no necesariamente necesitas un ajuste HttpContext.

DO.


Podemos resolver el problema CORS (intercambio de recursos de origen cruzado) utilizando dos formas:

1) Usando Jsonp 2) Habilitando el Cors

1) Usando Jsonp: para usar el Jsonp, necesitamos instalar el paquete de WebApiContrib.Formatting.Jsonp nuget y necesitamos agregar JsonpFormmater en WebApiConfig.cs referir capturas de pantalla,

Código Jquery

2) Habilitar el Cors -

para habilitar los cors necesitamos agregar el paquete de Microsoft.AspNet.WebApi.Cors nuget y necesitamos habilitar los cors en WebApiConfig.cs referir la captura de pantalla

Para obtener más referencias, puede enviar mi repositorio de muestra en GitHub usando el siguiente enlace. https://github.com/mahesh353/Ninject.WebAPi/tree/develop


Puede usar un ActionFilterAttribute como este:

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); } }

Luego ponlo en tu acción:

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


Si el contexto es Web Api , agradeciendo y refiriéndose a la respuesta de 010227leo , debe considerar WebContext.Current value que será null .

Así que actualicé su código a esto:

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); } }


johperl, Thomas. La respuesta dada por Peter Moberg arriba debe ser correcta para la versión RC ya que el JsonMediaTypeFormatter del que hereda usa ya el serializador NewtonSoft Json, por lo que lo que tiene debe funcionar sin ningún cambio.

Sin embargo, ¿por qué las personas todavía usan parámetros, cuando puedes hacer lo siguiente?

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); }



Actualizado

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); } }