如何在ASP.NET MVC中将View Model转换为JSON对象?

163

我是一名Java开发者,对.NET还不熟悉。我正在一个.NET MVC2项目中工作,想要有一个部分视图来包装一个小组件。每个JavaScript小组件对象都有一个JSON数据对象,该对象将被模型数据填充。然后,当小部件中的数据更改或在另一个小部件中更改数据时,绑定更新此数据的方法到事件。

代码类似于:

MyController

virtual public ActionResult DisplaySomeWidget(int id) {
  SomeModelView returnData = someDataMapper.getbyid(1);

  return View(myview, returnData);
}

myview.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SomeModelView>" %>

<script type="text/javascript">
  //creates base widget object;
  var thisWidgetName = new Widget();

  thisWidgetName.updateTable = function() {
    //  UpdatesData
  };
  $(document).ready(function () {
    thisWidgetName.data = <% converttoJSON(model) %>
    $(document).bind('DATA_CHANGED', thisWidgetName.updateTable());
  });
</script>

<div><%:model.name%></div>

我不知道如何将数据发送为SomeModelView,并能够使用它来填充小部件以及将其转换为 JSON。我在控制器中看到了一些非常简单的方法,但是在视图中没有看到。我想这是一个基本的问题,但我已经试了几个小时了,尝试使它更加完美。


2
我知道这是一个老问题。但是今天有更好的方法来解决它。不要将JSON内联到您的视图结果中。JSON可以通过AJAX轻松序列化,并且可以像对象一样处理。JavaScript中的任何内容都应与视图分开。您可以通过控制器轻松返回模型,而无需任何努力。 - Piotr Kula
@PiotrKula 有时候初始化的顺序表明了JavaScript被包含和分配的偏好。总是需要一定程度的努力,但它有时取决于它被放置的位置而有所不同。在视图中使用内联JavaScript声明和初始化是可以接受的,以避免不便和更大的努力。 - Suncat2000
9个回答

360
在使用Razor的MVC3中,@Html.Raw(Json.Encode(object))似乎可以解决问题。

1
+1 我使用了 Html.Raw,但从未找到 Json.Encode,只是在控制器中使用 JavaScriptSerializer 将字符串添加到视图模型中。 - AaronLS
7
即使要将生成的JSON传递给Javascript,这种方法仍然有效。 如果您将@Html.Raw(...)代码作为函数参数放在<script>标记内部时,Razor会出现绿色波浪线的错误提示,但JSON确实会传递到调用的JS中。非常方便和流畅。+1 - Carl Heinrich Hancke
1
这是自MVC3以来的最佳实践,应该从控制器返回。不要将JSON嵌入到Viewresult中。相反,创建一个控制器单独处理JSON,并通过AJAX进行JSON请求。如果您需要在视图中使用JSON,则可能存在问题。请使用ViewModel或其他方法。 - Piotr Kula
3
Json.Encode 将我的二维数组编码为一个一维数组的 JSON。 Newtonsoft.Json.JsonConvert.SerializeObject 可以正确地将这两个维度序列化为 JSON。因此,我建议使用后者。 - mono68
16
在使用 MVC 6(或者可能是5)时,你需要使用 Json.Serialize 而不是 Encode。 - KoalaBear
显示剩余8条评论

32

做得好,你刚刚开始使用MVC,也发现了它的第一个主要缺陷。

你不想在视图中将其转换为JSON,也不想在控制器中进行转换,因为这两个位置都没有意义。不幸的是,你却陷入了这种情况。

我发现最好的做法是将JSON通过ViewModel发送到视图中,就像这样:

var data = somedata;
var viewModel = new ViewModel();
var serializer = new JavaScriptSerializer();
viewModel.JsonData = serializer.Serialize(data);

return View("viewname", viewModel);

然后使用

<%= Model.JsonData %>

在你看来,请注意标准的.NET JavaScriptSerializer非常差。

至少在控制器中执行它可以进行测试(虽然不完全像上面那样 - 你可能想要将ISerializer作为依赖项,以便可以模拟它)

更新 另外,关于你的JavaScript,最好将上面所有的widget JS都包装起来,像这样:

(
    // all js here
)();

如果您在页面上放置多个小部件,使用这种方式就不会发生冲突(除非您需要从页面的其他地方访问方法,但在这种情况下,您应该将小部件与某些小部件框架注册)。现在可能没有问题,但是为了将来遇到要求时节省大量工作,现在添加括号是一个好的实践,这也是良好的面向对象实践,以封装功能。


1
这看起来很有趣。我本来打算只是将数据复制为json并将其作为viewData传递,但这种方式看起来更有趣。我会试着用一下并告诉你结果。顺便说一句,这是我第一次在stackoverflow上发布问题,只用了5分钟就得到了很好的回应,太棒了! - Chris Stephens
没有什么“问题”,只是它不可定制。如果你想要任何自定义值的格式,你必须在使用前处理它,基本上把一切都变成字符串 :( 这在日期方面最为突出。我知道有简单的解决方法,但它们不应该是必需的! - Andrew Bullock
2
我撤回之前的说法“它没有任何问题”。它有很多问题。 - Andrew Bullock
正如安德鲁所说,JsonHelper最大的问题在于它不可定制。想要camelCase属性?那就太糟糕了。自己编写JsonHelper吧,让它经常与无法隐藏的JsonHelper混淆。 - crush
有没有一种方法可以从 JSON 对象返回创建原始模型对象? - Muflix
显示剩余2条评论

18

我发现这样做还是挺不错的(在视图中使用):

    @Html.HiddenJsonFor(m => m.TrackingTypes)

这是相应的帮助程序扩展类:

public static class DataHelpers
{
    public static MvcHtmlString HiddenJsonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        return HiddenJsonFor(htmlHelper, expression, (IDictionary<string, object>) null);
    }

    public static MvcHtmlString HiddenJsonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        return HiddenJsonFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public static MvcHtmlString HiddenJsonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
    {
        var name = ExpressionHelper.GetExpressionText(expression);
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        var tagBuilder = new TagBuilder("input");
        tagBuilder.MergeAttributes(htmlAttributes);
        tagBuilder.MergeAttribute("name", name);
        tagBuilder.MergeAttribute("type", "hidden");

        var json = JsonConvert.SerializeObject(metadata.Model);

        tagBuilder.MergeAttribute("value", json);

        return MvcHtmlString.Create(tagBuilder.ToString());
    }
}

它并不是非常复杂,但解决了放置它的问题(在控制器还是视图中?)答案显然是:都不需要 ;)


在我看来,这很好而且干净,还包装成了可重用的帮助程序。干杯,J。 - John

7
您可以直接从操作中使用Json
您的操作应该像这样:
virtual public JsonResult DisplaySomeWidget(int id)
{
    SomeModelView returnData = someDataMapper.getbyid(id);
    return Json(returnData);
}

编辑

我看到你认为这是一个视图的Model,所以上面的内容不完全正确,你需要通过Ajax调用控制器方法来获取数据,ascx文件本身并没有一个明确的模型,我将代码保留在此以备你参考,你可以修改调用方式。

编辑2 刚刚将id添加到了代码中。


1
但是他无法将其呈现在视图中,他必须进行第二个Ajax调用。 - Andrew Bullock
谢谢,我最初是使用 jQuery 的 get json 调用来完成这个操作的,计划让 HTML 元素从 json 中自动填充。然而,我们现在遵循的模式是视图应该返回一个 modelView,这是填充基本 HTML 元素(如表格等)最简单的方法。我可以将相同的数据以 ViewData 的 JSON 格式发送过去,但这似乎很浪费。 - Chris Stephens
2
你不应该在视图结果中嵌入JSON。原帖的作者要么需要坚持标准MVC实践并返回模型或视图模型,要么使用AJAX。绝对没有理由在视图中嵌入JSON。那只是非常糟糕的代码。这个答案是正确的,也是Microsoft实践推荐的方式。下投票是不必要的,这绝对是正确的答案。我们不应该鼓励不良编码实践。要么通过AJAX使用JSON,要么通过视图使用模型。没有人喜欢混合标记的意大利面式代码! - Piotr Kula
这个解决方案会导致以下问题:https://dev59.com/bWkv5IYBdhLWcg3wghEh - Krisztián Balla

2

@Html.Raw(Json.Encode(object)) 可以用于将视图模型对象转换为JSON格式


1

扩展Dave的出色答案。您可以创建一个简单的HtmlHelper

public static IHtmlString RenderAsJson(this HtmlHelper helper, object model)
{
    return helper.Raw(Json.Encode(model));
}

而在您的看法中:

@Html.RenderAsJson(Model)

通过这种方式,如果您因某些原因想要更改逻辑,您可以集中处理创建JSON的逻辑。

1

对于 ASP.NET Core 3.1 及更高版本,您可以使用 System.Text.Json 命名空间将 ViewModel 序列化为 JSON:

@using System.Text.Json

...

@section Scripts {
    <script>
        const model = @Html.Raw(JsonSerializer.Serialize(Model));
    </script>
}

0

Andrew的回答很好,但我想稍微调整一下。这种方式的不同之处在于,我喜欢我的ModelViews中没有额外的数据。只有对象的数据。看起来ViewData适合处理额外的数据,但当然我对此还很陌生。我建议像这样做。

控制器

virtual public ActionResult DisplaySomeWidget(int id)
{
    SomeModelView returnData = someDataMapper.getbyid(1);
    var serializer = new JavaScriptSerializer();
    ViewData["JSON"] = serializer.Serialize(returnData);
    return View(myview, returnData);
}

视图

//create base js object;
var myWidget= new Widget(); //Widget is a class with a public member variable called data.
myWidget.data= <%= ViewData["JSON"] %>;

这个功能可以让你在JSON中获得与ModelView相同的数据,因此你可以将JSON返回给控制器并且它会包含所有部分。这类似于通过JSONRequest请求数据,但是它需要少一次调用,因此可以节省开销。顺便说一下,对于日期来说可能有些棘手,但那似乎是另一个话题了。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接