ASP.net MVC devuelve JSONP
asp.net-mvc (6)
Estoy buscando devolver algunos JSON en todos los dominios y entiendo que la forma de hacerlo es a través de JSONP en lugar de JSON puro. Estoy utilizando ASP.net MVC, así que estaba pensando en simplemente extender el tipo JSONResult y luego extender el controlador para que también implementara un método Jsonp. ¿Es esta la mejor manera de hacerlo o hay un ActionResult integrado que podría ser mejor?
Editar: seguí adelante e hice eso. Solo como referencia, agregué un nuevo resultado:
public class JsonpResult : System.Web.Mvc.JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/javascript";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
// The JavaScriptSerializer type was marked as obsolete prior to .NET Framework 3.5 SP1
#pragma warning disable 0618
HttpRequestBase request = context.HttpContext.Request;
JavaScriptSerializer serializer = new JavaScriptSerializer();
response.Write(request.Params["jsoncallback"] + "(" + serializer.Serialize(Data) + ")");
#pragma warning restore 0618
}
}
}
y también un par de métodos para una superclase de todos mis controladores:
protected internal JsonpResult Jsonp(object data)
{
return Jsonp(data, null /* contentType */);
}
protected internal JsonpResult Jsonp(object data, string contentType)
{
return Jsonp(data, contentType, null);
}
protected internal virtual JsonpResult Jsonp(object data, string contentType, Encoding contentEncoding)
{
return new JsonpResult
{
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding
};
}
Funciona de maravilla.
Aquí hay una solución simple, si no quiere definir un filtro de acción
Código del lado del cliente usando jQuery:
$.ajax("http://www.myserver.com/Home/JsonpCall", { dataType: "jsonp" }).done(function (result) {});
Acción del controlador MVC. Devuelve el resultado del contenido con el código JavaScript que ejecuta la función de devolución de llamada proporcionada con la cadena de consulta. También establece el tipo JavaScript MIME para la respuesta.
public ContentResult JsonpCall(string callback)
{
return Content(String.Format("{0}({1});",
callback,
new JavaScriptSerializer().Serialize(new { a = 1 })),
"application/javascript");
}
En lugar de subclasificar mis controladores con métodos Jsonp (), fui por la ruta del método de extensión, ya que me parece un poco más limpio. Lo bueno de JsonpResult es que puedes probarlo exactamente de la misma manera que lo harías con un JsonResult.
Yo si:
public static class JsonResultExtensions
{
public static JsonpResult ToJsonp(this JsonResult json)
{
return new JsonpResult { ContentEncoding = json.ContentEncoding, ContentType = json.ContentType, Data = json.Data, JsonRequestBehavior = json.JsonRequestBehavior};
}
}
De esta forma, no tiene que preocuparse por crear las diferentes sobrecargas de Jsonp (), simplemente convierta su JsonResult a uno de Jsonp.
La solución anterior es una buena forma de trabajar, pero debe ser presentada con un nuevo tipo de resultado en lugar de tener un método que devuelva un JsonResult. Debe escribir métodos que devuelvan sus propios tipos de resultados.
public JsonPResult testMethod() {
// use the other guys code to write a method that returns something
}
public class JsonPResult : JsonResult
{
public FileUploadJsonResult(JsonResult data) {
this.Data = data;
}
public override void ExecuteResult(ControllerContext context)
{
this.ContentType = "text/html";
context.HttpContext.Response.Write("<textarea>");
base.ExecuteResult(context);
context.HttpContext.Response.Write("</textarea>");
}
}
Los artículos mencionados por stimms y ranju v fueron muy útiles e hicieron que la situación fuera clara.
Sin embargo, me quedé rascándome la cabeza sobre el uso de extensiones, subclasificación en el contexto del código MVC que había encontrado en línea.
Hubo dos puntos clave que me sorprendieron:
- El código que obtuve de ActionResult, pero en ExecuteResult había algún código para devolver XML o JSON.
- Luego, creé un ActionResult basado en Generics, para garantizar que se utilizara el mismo ExecuteResults independientemente del tipo de datos que devolviera.
Entonces, combinando los dos, no necesité más extensiones o subclases para agregar el mecanismo para devolver JSONP, simplemente cambio mis ExecuteResults existentes.
Lo que me confundió es que realmente estaba buscando una manera de derivar o extender JsonResult, sin volver a codificar el ExecuteResult. Como JSONP es efectivamente una cadena JSON con prefijo y sufijo, parecía un desperdicio. Sin embargo, el subordinado ExecuteResult utiliza respone.write, por lo que la forma más segura de cambiar es volver a codificar ExecuteResults como lo proporcionan varias publicaciones.
Puedo publicar un código si eso fuera útil, pero ya hay bastante código en este hilo.
http://blogorama.nerdworks.in/entry-EnablingJSONPcallsonASPNETMVC.aspx (también conocida como "Esta publicación de blog que encontré") es excelente, y leerla le permitirá ampliar la solución a continuación para que su controlador pueda manejar solicitudes JSONP JSONP y dominio JSON de mismo dominio elegantemente en la misma acción de controlador sin código adicional [en la acción].
De todos modos, para los tipos "dame el código", aquí está, en caso de que el blog desaparezca nuevamente.
En su controlador (este fragmento es código nuevo / no blog):
[AllowCrossSiteJson]
public ActionResult JsonpTime(string callback)
{
string msg = DateTime.UtcNow.ToString("o");
return new JsonpResult
{
Data = (new
{
time = msg
})
};
}
JsonpResult encontró en http://blogorama.nerdworks.in/entry-EnablingJSONPcallsonASPNETMVC.aspx :
/// <summary>
/// Renders result as JSON and also wraps the JSON in a call
/// to the callback function specified in "JsonpResult.Callback".
/// http://blogorama.nerdworks.in/entry-EnablingJSONPcallsonASPNETMVC.aspx
/// </summary>
public class JsonpResult : JsonResult
{
/// <summary>
/// Gets or sets the javascript callback function that is
/// to be invoked in the resulting script output.
/// </summary>
/// <value>The callback function name.</value>
public string Callback { get; set; }
/// <summary>
/// Enables processing of the result of an action method by a
/// custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>.
/// </summary>
/// <param name="context">The context within which the
/// result is executed.</param>
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
response.ContentType = ContentType;
else
response.ContentType = "application/javascript";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
if (Callback == null || Callback.Length == 0)
Callback = context.HttpContext.Request.QueryString["callback"];
if (Data != null)
{
// The JavaScriptSerializer type was marked as obsolete
// prior to .NET Framework 3.5 SP1
#pragma warning disable 0618
JavaScriptSerializer serializer = new JavaScriptSerializer();
string ser = serializer.Serialize(Data);
response.Write(Callback + "(" + ser + ");");
#pragma warning restore 0618
}
}
}
Nota: Siguiendo los comentarios al OP de @Ranju y otros , pensé que valía la pena publicar el código funcional "mínimo" de la publicación de blog de Ranju como wiki de la comunidad. Aunque es seguro decir que Ranju agregó el código anterior y otro en su blog para usarlo libremente, no voy a copiar sus palabras aquí.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;
namespace Template.Web.Helpers
{
public class JsonpResult : JsonResult
{
public JsonpResult(string callbackName)
{
CallbackName = callbackName;
}
public JsonpResult()
: this("jsoncallback")
{
}
public string CallbackName { get; set; }
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[CallbackName] as string) ?? request[CallbackName]) ?? CallbackName;
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(")");
}
}
}
public static class ControllerExtensions
{
public static JsonpResult Jsonp(this Controller controller, object data, string callbackName = "callback")
{
return new JsonpResult(callbackName)
{
Data = data,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
public static T DeserializeObject<T>(this Controller controller, string key) where T : class
{
var value = controller.HttpContext.Request.QueryString.Get(key);
if (string.IsNullOrEmpty(value))
{
return null;
}
JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
return javaScriptSerializer.Deserialize<T>(value);
}
}
}
//Example of using the Jsonp function::
// 1-
public JsonResult Read()
{
IEnumerable<User> result = context.All();
return this.Jsonp(result);
}
//2-
public JsonResult Update()
{
var models = this.DeserializeObject<IEnumerable<User>>("models");
if (models != null)
{
Update(models); //Update properties & save change in database
}
return this.Jsonp(models);
}