ASP.net MVC restituisce JSONP

Sto cercando di restituire alcuni JSON tra domini e capisco che il modo per farlo è attraverso JSONP piuttosto che JSON puro. Sto usando ASP.net MVC quindi stavo pensando di estendere il tipo JSONResult e quindi estendere Controller in modo che anche implementasse un metodo Jsonp. È questo il modo migliore per farlo o esiste un ActionResult integrato che potrebbe essere migliore?

Edit: Sono andato avanti e l’ho fatto. Solo per riferimento ho aggiunto un nuovo risultato:

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

e anche un paio di metodi per una superclass di tutti i miei controller:

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

Funziona come un fascino.

Ecco una soluzione semplice, se non si desidera definire un filtro azione

Codice lato client usando jQuery:

  $.ajax("http://www.myserver.com/Home/JsonpCall", { dataType: "jsonp" }).done(function (result) {}); 

Azione del controller MVC. Restituisce il risultato del contenuto con il codice JavaScript che esegue la funzione di callback fornita con la stringa di query. Imposta anche il tipo MIME di JavaScript per la risposta.

  public ContentResult JsonpCall(string callback) { return Content(String.Format("{0}({1});", callback, new JavaScriptSerializer().Serialize(new { a = 1 })), "application/javascript"); } 

Piuttosto che creare sottoclassi dei miei controller con i metodi Jsonp (), sono passato al metodo di estensione perché mi sembra un pulitore di touch. La cosa bella di JsonpResult è che puoi testarlo esattamente nello stesso modo in cui faresti un JsonResult.

L’ho fatto:

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

In questo modo non devi preoccuparti di creare tutti i diversi overload di Jsonp (), basta convertire il tuo JsonResult in uno di Jsonp.

Il post sul blog di Ranju (conosciuto anche come “Questo post sul blog l’ho trovato”) è eccellente, e leggerlo ti consentirà di ampliare ulteriormente la soluzione in modo che il controller possa gestire le richieste JSON e JSONP nello stesso dominio con eleganza nello stesso controller senza codice aggiuntivo [nell’azione].

Indipendentemente da ciò, per i tipi “dammi il codice”, eccolo qui, nel caso in cui il blog scompaia di nuovo.

Nel tuo controller (questo snippet è nuovo / codice non-blog):

 [AllowCrossSiteJson] public ActionResult JsonpTime(string callback) { string msg = DateTime.UtcNow.ToString("o"); return new JsonpResult { Data = (new { time = msg }) }; } 

JsonpResult trovato su questo eccellente post sul blog :

 ///  /// 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 ///  public class JsonpResult : JsonResult { ///  /// Gets or sets the javascript callback function that is /// to be invoked in the resulting script output. ///  /// The callback function name. public string Callback { get; set; } ///  /// Enables processing of the result of an action method by a /// custom type that inherits from . ///  /// The context within which the /// result is executed. 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: seguendo i commenti all’OP di @Ranju e altri , ho pensato che valesse la pena postare il codice funzionale “minimo indispensabile” dal post del blog di Ranju come wiki della comunità. Anche se è sicuro dire che Ranju ha aggiunto il sopra e altro codice sul suo blog per essere usato liberamente, non ho intenzione di copiare le sue parole qui.

Gli articoli di riferimento di stimms e ranju v erano entrambi molto utili e chiarivano la situazione.

Tuttavia, mi è stato lasciato grattarmi l’idea di usare estensioni, sottoclass nel contesto del codice MVC che avevo trovato online.

C’erano due punti chiave che mi hanno catturato:

  1. Il codice che avevo derivato da ActionResult, ma in ExecuteResult c’era un codice per restituire XML o JSON.
  2. Avevo quindi creato un ActionResult basato su Generics, per garantire che lo stesso ExecuteResults fosse utilizzato indipendentemente dal tipo di dati che ho restituito.

Quindi, combinando i due: non avevo bisogno di ulteriori estensioni o sottoclassi per aggiungere il meccanismo per restituire JSONP, semplicemente cambia i miei ExecuteResults esistenti.

Ciò che mi aveva confuso è che davvero stavo cercando un modo per derivare o estendere JsonResult, senza ricodificare ExecuteResult. Poiché JSONP è effettivamente una stringa JSON con prefisso e suffisso, sembrava uno spreco. Tuttavia il sottotitolo ExecuteResult usa respone.write – quindi il modo più sicuro di cambiare è ricodificare ExecuteResults come fornito da vari post!

Posso postare del codice se questo sarebbe utile, ma c’è già un bel po ‘di codice in questa discussione.

  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(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(value); } } } //Example of using the Jsonp function:: // 1- public JsonResult Read() { IEnumerable result = context.All(); return this.Jsonp(result); } //2- public JsonResult Update() { var models = this.DeserializeObject>("models"); if (models != null) { Update(models); //Update properties & save change in database } return this.Jsonp(models); } 

la soluzione sopra è un buon modo di lavorare ma dovrebbe essere estesa con un nuovo tipo di risultato invece di avere un metodo che restituisce un JsonResult, dovresti scrivere metodi che restituiscono i tuoi tipi di risultato

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