ASP.net MVC gibt JSONP zurück

72

Ich möchte einige JSONs domänenübergreifend zurückgeben, und ich verstehe, dass dies eher über JSONP als über reines JSON erfolgt.
Ich verwende ASP.net MVC, also habe ich darüber nachgedacht, nur den JsonResultTyp und dann den Controller so zu erweitern, dass auch eine Jsonp-Methode implementiert wird.
Ist dies der beste Weg, oder gibt es eine integrierte Funktion ActionResult, die möglicherweise besser ist?


Lösung : Ich habe das gemacht. Nur als Referenz habe ich ein neues Ergebnis hinzugefügt:

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

und auch ein paar Methoden zu einer Superklasse aller meiner 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
    };
}

Klappt wunderbar.

stimms
quelle
2
Ich habe gerade einen Blog-Beitrag über genau diese Sache verfasst und im Wesentlichen den gleichen Ansatz wie oben beschrieben verwendet, außer dass oben ein kleiner Aktionsfilter hinzugefügt wurde, um die Aktivierung von JSONP auf vorhandenen Controller-Implementierungen etwas weniger schmerzhaft zu machen. Sie können alles darüber hier lesen:> http://blogorama.nerdworks.in/entry-EnablingJSONPcallsonASPNETMVC.aspx
Raj
Vielen Dank! Einfach in unser Projekt umgesetzt! :)
Adam Kahtava
3
Nett! Aber JSONP sollte als Anwendung / Javascript stackoverflow.com/questions/111302/…
Mauricio Scheffer
1
+1 Zitierter Blog ist ein epischer Gewinn.
Chris Marisic
1
Dies zeigt das Problem, dass nur ein Link als Antwort veröffentlicht wird. Wenn Sie mindestens das Nötigste im Textkörper der Antwort veröffentlichen, erhöht sich der Wert des StackOverflow, wenn Blogs verschoben und aufgegeben werden.
Robert Kaucher

Antworten:

17

Hier ist eine einfache Lösung, wenn Sie keinen Aktionsfilter definieren möchten

Clientseitiger Code mit jQuery:

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

MVC-Controller-Aktion. Gibt das Inhaltsergebnis mit JavaScript-Code zurück, der die mit der Abfragezeichenfolge bereitgestellte Rückruffunktion ausführt. Legt auch den JavaScript-MIME-Typ für die Antwort fest.

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

Anstatt meine Controller mit Jsonp () -Methoden zu unterklassifizieren, habe ich mich für die Erweiterungsmethode entschieden, da sie sich für mich etwas sauberer anfühlt. Das Schöne am JsonpResult ist, dass Sie es genauso testen können wie ein JsonResult.

Ich tat:

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

Auf diese Weise müssen Sie sich nicht um die Erstellung der verschiedenen Jsonp () - Überladungen kümmern. Konvertieren Sie einfach Ihr JsonResult in ein Jsonp-Ergebnis.

Bettler
quelle
3
Was ist die JsonpResult-Klasse?
Können Sie ein Beispiel für einen Aufruf dazu geben?
Josh Noe
3
Für die anderen Kommentatoren baut der Bettler, um klar zu sein, auf dem Code aus dem OP auf.
Ruffin
10

Ranjus Blog-Post (auch bekannt als "Dieser Blog-Post, den ich gefunden habe") ist ausgezeichnet. Wenn Sie ihn lesen, können Sie die folgende Lösung weiterentwickeln, sodass Ihr Controller JSONP-Anforderungen für dieselbe Domäne und domänenübergreifende JSONP-Anforderungen elegant in derselben Controller-Aktion ohne verarbeiten kann zusätzlicher Code [in der Aktion].

Unabhängig davon, für die Typen "Gib mir den Code", hier ist es, falls der Blog wieder verschwindet.

In Ihrem Controller (dieses Snippet ist neuer / kein Blog-Code):

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

JsonpResult in diesem ausgezeichneten Blog-Beitrag gefunden :

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

Hinweis: Nach den Kommentaren von @Ranju und anderen zum OP fand ich, dass es sich lohnt, den Funktionscode "Ran Minimum" aus Ranjus Blog-Post als Community-Wiki zu veröffentlichen. Obwohl man mit Sicherheit sagen kann, dass Ranju den obigen und anderen Code in seinem Blog hinzugefügt hat, um ihn frei zu verwenden, werde ich seine Worte hier nicht kopieren.

Ruffin
quelle
1
Danke @ruffin! Wollte dies eines Tages tun. Danke, dass du es geschafft hast! :)
Raj
1

Für ASP.NET Core , NICHT ASP.NET MVC
Dies ist eine maßgeschneiderte Version für ASP.NET CORE der in der Antwort enthaltenen Lösung

public class JsonpResult : JsonResult
{
    public JsonpResult(object value) : base(value)
    {
    }

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        HttpResponse response = context.HttpContext.Response;

        if (!String.IsNullOrEmpty(ContentType))
            response.ContentType = ContentType;
        else
            response.ContentType = "application/javascript";

        if (Value != null)
        {
            HttpRequest request = context.HttpContext.Request;
            string serializedJson = JsonConvert.SerializeObject(Value);
            string result = $"{request.Query["callback"]}({serializedJson})";
            await response.WriteAsync(result);
        }
    }
}

Hakan Fıstık
quelle
1
Danke Mann, du bist ein Live-Retter!
Anton Malmygin
0

Die Artikel von stimms und ranju v, auf die verwiesen wurde, waren beide sehr nützlich und machten die Situation klar.

Ich kratzte mir jedoch am Kopf, weil ich Erweiterungen verwendet hatte, die im Kontext des MVC-Codes, den ich online gefunden hatte, unterklassifiziert wurden.

Es gab zwei wichtige Punkte, die mich aufgefallen sind:

  1. Der Code, den ich von ActionResult abgeleitet hatte, aber in ExecuteResult gab es einen Code, der entweder XML oder JSON zurückgab.
  2. Ich hatte dann ein generisches ActionResult erstellt, um sicherzustellen, dass unabhängig von der Art der zurückgegebenen Daten dieselben ExecuteResults verwendet wurden.

Wenn Sie also beide kombinieren, brauchte ich keine weiteren Erweiterungen oder Unterklassen, um den Mechanismus für die Rückgabe von JSONP hinzuzufügen. Ändern Sie einfach meine vorhandenen ExecuteResults.

Was mich verwirrt hatte, war, dass ich wirklich nach einer Möglichkeit suchte, JsonResult abzuleiten oder zu erweitern, ohne das ExecuteResult neu zu codieren. Da JSONP effektiv eine JSON-Zeichenfolge mit Präfix und Suffix ist, schien dies eine Verschwendung zu sein. Der Underling ExecuteResult verwendet jedoch respone.write. Die sicherste Möglichkeit zum Ändern besteht darin, ExecuteResults neu zu codieren, wie dies in verschiedenen Postings problemlos möglich ist.

Ich kann Code posten, wenn das nützlich wäre, aber in diesem Thread befindet sich bereits ziemlich viel Code.

Aus Orbonia
quelle
0
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);
}
K.Hicham
quelle
2
Können Sie bitte weitere Details angeben, nicht nur eine Antwort nur mit Code?
Thomas
-2

Die obige Lösung ist eine gute Arbeitsweise, sollte jedoch mit einem neuen Ergebnistyp erweitert werden, anstatt über eine Methode zu verfügen, die ein JsonResult zurückgibt. Sie sollten Methoden schreiben, die Ihre eigenen Ergebnistypen zurückgeben

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