在MVC操作输出

时间:2016-12-08 23:01:26

标签: c# json asp.net-mvc json-deserialization jsonresult

很久以前,我为我的应用设置了一个编码标准,即返回JSON的所有操作都会将其结果放入顶级包装器对象中:

var result = {
    success: false,
    message: 'Something went wrong',
    data: {} // or []
}

效果很好,为我提供了良好的代码标准化快乐。

然而,今天,我意识到我的服务器端代码假定它始终可以对返回的内容进行完整的序列化。现在我想序列化其中一个"数据" payload已经是一个结构良好的JSON字符串。

这是一直有效的一般模式:

bool success = false;
string message = "Something went wrong";
object jsonData = "[{\"id\":\"0\",\"value\":\"1234\"}]";  // Broken

dynamic finalData = new { success = success, message = message, data = jsonData };

JsonResult output = new JsonResult
{
    Data = finalData,
    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
    MaxJsonLength = int.MaxValue
};
return output;

它破坏的地方是"数据"当元素到达浏览器时,它将作为字符串接收,而不是它应该是正确的JSON对象(或上例中的数组)。

我是否有某种方法可以使用一个属性来装饰属性,该属性说"序列化为原始",或者我是在编写自定义JSON序列化程序以使其工作的领域?

4 个答案:

答案 0 :(得分:1)

你将它序列化两次(jsonData + output)。你不能这样做,并期望只将其反序列化一次(输出)。

您可以设置"数据"你的动态对象是真正的c#对象,那会起作用。或者您可以将您的财产重命名为" jsonData":

dynamic finalData = new { success = success, message = message, jsonData = jsonData };

...所以它反映了你真正在做的事情:)。

答案 1 :(得分:0)

您可以使用Newtonsoft的JsonWriter类自己构建JSON包来实现此目的。它看起来像这样:

using(var textWriter = new StringWriter())
using(var jsonWriter = new JsonTextWriter(textWriter))
{
   jsonWriter.WriteStartObject();

   jsonWriter.WritePropertyName("success");
   jsonWriter.WriteValue(success);

   jsonWriter.WritePropertyName("message");
   jsonWriter.WriteValue(message);

   jsonWriter.WritePropertyName("data");
   jsonWriter.WriteRaw(jsonData);

   jsonWriter.WriteEndObject();

   var result = new ContentResult();
   result.Content = textWriter.ToString();
   result.ContentType = "application/json";
   return result;
}

答案 2 :(得分:0)

我认为您只需要使用JSON序列化程序(如NewtonSoft)将从SQL表返回的字符串序列化为对象。

bool success = false;
string message = "Something went wrong";
string rawData = "[{\"id\":\"0\",\"value\":\"1234\"}]";  // Broken
object jsonData = JsonConvert.DeserializeObject<dynamic>(rawData);

dynamic finalData = new { success = success, message = message, data = jsonData };

JsonResult output = new JsonResult
{
    Data = finalData,
    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
    MaxJsonLength = int.MaxValue
};
return output;

答案 3 :(得分:0)

这是我最终得到的......

// Wrap "String" in a container class
public class JsonStringWrapper
{
    // Hey Microsoft - This is where it would be nice if "String" wasn't marked "sealed"
    public string theString { get; set; }
    public JsonStringWrapper() { }
    public JsonStringWrapper(string stringToWrap) { theString = stringToWrap; }
}

// Custom JsonConverter that will just dump the raw string into
// the serialization process.  Loosely based on:
//   http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
public class JsonStringWrapperConverter : JsonConverter
{
    private readonly Type _type = typeof(JsonStringWrapper);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JToken t = JToken.FromObject(value);

        if (t.Type != JTokenType.Object)
        {
            t.WriteTo(writer);
        }
        else
        {
            string rawValue = ((JsonStringWrapper)value).theString;
            writer.WriteRawValue((rawValue == null) ? "null" : rawValue);
        }
    }

    public override bool CanWrite
    {
        get { return true; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override bool CanConvert(Type objectType)
    {
        return _type == objectType;
    }
}

// Custom JsonResult that will use the converter above, largely based on:
//   http://stackoverflow.com/questions/17244774/proper-json-serialization-in-mvc-4
public class PreSerializedJsonResult : JsonResult
{
    private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
    {
        Converters = new List<JsonConverter> { new JsonStringWrapperConverter() }
    };

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

        var response = context.HttpContext.Response;

        response.ContentType = !string.IsNullOrEmpty(this.ContentType) ? this.ContentType : "application/json";

        if (this.ContentEncoding != null)
        {
            response.ContentEncoding = this.ContentEncoding;
        }

        if (this.Data == null)
        {
            return;
        }

        response.Write(JsonConvert.SerializeObject(this.Data, Settings));
    }
}

// My base controller method that overrides Json()...
protected JsonResult Json(string message, object data)
{
    PreSerializedJsonResult output = new PreSerializedJsonResult();

    object finalData = (data is string && (new char[] { '[', '{' }.Contains(((string)data).First())))
        ? new JsonStringWrapper(data as string)
        : data;

    output.Data = new
    {
        success = string.IsNullOrEmpty(message),
        message = message,
        data = finalData
    };
    output.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
    output.MaxJsonLength = int.MaxValue;
    return output;
}

// Aaaand finally, here's how it might get called from an Action method:
...
return Json("This was a failure", null);
...
return Json(null, yourJsonStringVariableHere);

有了这个,我没有在服务器上进行任何Json解析。我的字符串来自数据库并直接进入客户端而没有MVC触摸它。

编辑:现在更新版本还支持序列化在其层次结构中某些类型为JsonStringWrapper的具有单独属性的对象。这在我的场景中非常有用,可以支持&#34; hybrid&#34;模型。如果对象A的属性B是我预先烘焙的JSON字符串之一,则上面的代码将正确处理它。