MVC: So geben Sie einen String als JSON zurück

70

Um einen Fortschrittsberichtsprozess ein wenig zuverlässiger zu gestalten und ihn von der Anforderung / Antwort zu entkoppeln, führe ich die Verarbeitung in einem Windows-Dienst durch und behalte die beabsichtigte Antwort auf eine Datei bei. Wenn der Client nach Updates fragt, beabsichtigt der Controller, den Inhalt der Datei, unabhängig davon, was sie sind, als JSON-Zeichenfolge zurückzugeben.

Der Inhalt der Datei wird in JSON vorserialisiert. Dies soll sicherstellen, dass der Antwort nichts im Wege steht. Es muss keine Verarbeitung stattfinden (es sei denn, der Dateiinhalt wird in eine Zeichenfolge eingelesen und zurückgegeben), um die Antwort zu erhalten.

Ich dachte zunächst, dies wäre ziemlich einfach, aber es stellt sich nicht heraus, dass dies der Fall ist.

Derzeit sieht meine Controller-Methode folgendermaßen aus:

Regler

Aktualisiert

[HttpPost]
public JsonResult UpdateBatchSearchMembers()
{
    string path = Properties.Settings.Default.ResponsePath;
    string returntext;
    if (!System.IO.File.Exists(path))
        returntext = Properties.Settings.Default.EmptyBatchSearchUpdate;
    else
        returntext = System.IO.File.ReadAllText(path);

    return this.Json(returntext);
}

Und Fiddler gibt dies als rohe Antwort zurück

HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Mon, 19 Mar 2012 20:30:05 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 3.0
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 81
Connection: Close

"{\"StopPolling\":false,\"BatchSearchProgressReports\":[],\"MemberStatuses\":[]}"

AJAX

Aktualisiert

Das Folgende wird wahrscheinlich später geändert, aber im Moment funktionierte dies, als ich die Antwortklasse generierte und sie wie eine normale Person als JSON zurückgab.

this.CheckForUpdate = function () {
var parent = this;

if (this.BatchSearchId != null && WorkflowState.SelectedSearchList != "") {
    showAjaxLoader = false;
    if (progressPending != true) {
        progressPending = true;
        $.ajax({
            url: WorkflowState.UpdateBatchLink + "?SearchListID=" + WorkflowState.SelectedSearchList,
            type: 'POST',
            contentType: 'application/json; charset=utf-8',
            cache: false,
            success: function (data) {
                for (var i = 0; i < data.MemberStatuses.length; i++) {
                    var response = data.MemberStatuses[i];
                    parent.UpdateCellStatus(response);
                }
                if (data.StopPolling = true) {
                    parent.StopPullingForUpdates();
                }
                showAjaxLoader = true;
            }
        });
        progressPending = false;
    }
}
CodeWarrior
quelle
Können Sie die Zeichenfolge nicht einfach zurückgeben, müssen Sie den Rückgabetyp in Zeichenfolge ändern.
RubbleFord
Womit führen Sie den Ajax-Aufruf durch (jQuery, custom, dojo)? Könnten Sie diesen Code bereitstellen?
Paul
Ich sehe nicht, dass Sie diese pathVariable irgendwo in Ihrer Controller-Aktion verwenden. Wo lesen Sie den Inhalt der Datei? Sie geben lediglich einen JSON mit dem Inhalt der Properties.Settings.Default.EmptyBatchSearchUpdateEigenschaft zurück. Wussten Sie auch, dass Sie eine Datei nicht lesen können, während ein anderer Thread darauf schreibt? Zumindest kann dies nicht auf sichere Weise geschehen. Sie könnten sehr schnell auf Rennbedingungen stoßen. Ich denke also, dass Ihr Design von Anfang an fehlerhaft ist.
Darin Dimitrov
@ Paul Ich benutze Ajax, ich werde es in einer Minute posten. Der obige Code wird bearbeitet, ich werde ihn in einer Sekunde ändern. Der in der Fiddler-Rückgabe angezeigte Rückgabewert repräsentiert genau die ordnungsgemäß maskierte C # -String des JSON-Objekts, mit dem ich zu antworten versuche.
CodeWarrior
1
Ja, ich bin bereits auf Probleme gestoßen und habe Schritte unternommen, um dies im Windows-Dienst zu verhindern. Vielen Dank!
CodeWarrior

Antworten:

131

Ich glaube, das Problem ist, dass das Ergebnis der Json-Aktion ein Objekt (Ihr Modell) nehmen und eine HTTP-Antwort mit Inhalt als JSON-formatierten Daten aus Ihrem Modellobjekt erstellen soll.

Was Sie jedoch an die Json-Methode des Controllers übergeben, ist ein JSON-formatiertes Zeichenfolgenobjekt. Daher wird das Zeichenfolgenobjekt in JSON "serialisiert", weshalb der Inhalt der HTTP-Antwort in doppelte Anführungszeichen gesetzt ist (I '). Ich gehe davon aus, dass dies das Problem ist.

Ich denke, Sie können das Ergebnis der Inhaltsaktion als Alternative zum Ergebnis der Json-Aktion verwenden, da Sie im Wesentlichen bereits über den Rohinhalt für die HTTP-Antwort verfügen.

return this.Content(returntext, "application/json");
// not sure off-hand if you should also specify "charset=utf-8" here, 
//  or if that is done automatically

Eine andere Alternative wäre, das JSON-Ergebnis aus dem Dienst in ein Objekt zu deserialisieren und dieses Objekt dann an die Json-Methode des Controllers zu übergeben. Der Nachteil besteht jedoch darin, dass Sie die Daten de-serialisieren und dann erneut serialisieren, was möglicherweise unnötig ist für Ihre Zwecke.

Dr. Wily's Lehrling
quelle
4
+1: Sie müssen auch die ContentType-Eigenschaft des Ergebnisses auf "application / json" setzen, da dies vom JsonResult automatisch ausgeführt wird.
StriplingWarrior
@StriplingWarrior guter Punkt, ich werde mein Codebeispiel aktualisieren, um die Überladung der anderen Content-Methode zu verwenden.
Dr. Wily's Apprentice
Es ist erwähnenswert, dass Contentzurückkehrt, ContentResultwohin Jsonzurückkehrt JsonResult.
Schütze
44

Sie müssen nur das Standard-ContentResult zurückgeben und ContentType auf "application / json" setzen. Sie können ein benutzerdefiniertes ActionResult dafür erstellen:

public class JsonStringResult : ContentResult
{
    public JsonStringResult(string json)
    {
        Content = json;
        ContentType = "application/json";
    }
}

Und dann geben Sie die Instanz zurück:

[HttpPost]
public JsonResult UpdateBatchSearchMembers()
{
    string returntext;
    if (!System.IO.File.Exists(path))
        returntext = Properties.Settings.Default.EmptyBatchSearchUpdate;
    else
        returntext = Properties.Settings.Default.ResponsePath;

    return new JsonStringResult(returntext);
}
Dmitriy Startsev
quelle
Es ist, obwohl sein größter Vorteil die Wiederverwendung ist. Ich brauchte das nur für eine einzige Antwort. Wenn ich es jedoch in Zukunft an anderen Orten brauchen würde, würde ich den Weg dieser Antwort völlig gehen.
CodeWarrior
3

Ja, das ist es ohne weitere Probleme, um rohen String json zu vermeiden, das ist es.

    public ActionResult GetJson()
    {
        var json = System.IO.File.ReadAllText(
            Server.MapPath(@"~/App_Data/content.json"));

        return new ContentResult
        {
            Content = json,
            ContentType = "application/json",
            ContentEncoding = Encoding.UTF8
        };
    } 

Hinweis: Bitte beachten Sie , dass die Methode Rückgabetyp JsonResultist für mich nicht funktioniert, da JsonResultund ContentResultbeide erben , ActionResultaber es gibt keine Beziehung zwischen ihnen.

Ivan Carmenates García
quelle
2

Verwenden Sie den folgenden Code in Ihrem Controller:

return Json(new { success = string }, JsonRequestBehavior.AllowGet);

und in JavaScript:

success: function (data) {
    var response = data.success;
    ....
}
user3652935
quelle
Vielen Dank für die neue Antwort, aber das passt nicht wirklich zur ursprünglichen Frage. Ich hatte vorgenerierte JSON-Daten, die ich zurückgeben wollte, und nicht etwas, das ich als Zeichenfolgenwert eines JSON-Objekts darstellen wollte. Beachten Sie auch, dass ich mich in der Frage in einer Aktion befinde, die explizit als HTTP-Post markiert ist. AllowGet wird dafür nichts tun.
CodeWarrior
0

Alle Antworten hier liefern guten und funktionierenden Code. Aber jemand wäre unzufrieden, dass sie alle ContentTypeals Rückgabetyp verwenden und nicht JsonResult.

Leider JsonResultwird JavaScriptSerializerohne Option zum Deaktivieren verwendet. Der beste Weg, dies zu umgehen, ist zu erben JsonResult.

Ich habe den größten Teil des Codes aus dem Original kopiert JsonResultund eine JsonStringResultKlasse erstellt, die die übergebene Zeichenfolge als zurückgibt application/json. Der Code für diese Klasse ist unten

public class JsonStringResult : JsonResult
    {
        public JsonStringResult(string data)
        {
            JsonRequestBehavior = JsonRequestBehavior.DenyGet;
            Data = data;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
                String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("Get request is not allowed!");
            }

            HttpResponseBase response = context.HttpContext.Response;

            if (!String.IsNullOrEmpty(ContentType))
            {
                response.ContentType = ContentType;
            }
            else
            {
                response.ContentType = "application/json";
            }
            if (ContentEncoding != null)
            {
                response.ContentEncoding = ContentEncoding;
            }
            if (Data != null)
            {
                response.Write(Data);
            }
        }
    }

Anwendungsbeispiel:

var json = JsonConvert.SerializeObject(data);
return new JsonStringResult(json);
Schütze
quelle