将Mvc模型传递给KnockoutJS的最佳实践

24

我谷歌搜索了一下如何将mvc模型传递给knockoutjs,似乎有两种方法:

  • 使用@ Html.Raw(Json.Encode(Model))
  • 使用$.get或$.ajax

哪种方法是将mvc模型传递给knockoutjs的最佳实践? 我知道这是基于需求的,但使用$.get似乎比@ Html.Raw方法更清晰。

8个回答

23

我已经成功地使用了几种方法。

在强类型的Razor视图中,您可以像处理其他HTML一样编写JavaScript ViewModel对象,插入Model元素。我认为这种方法有些笨拙,因为Razor和JS在Visual Studio和Intellisense中不太兼容,但即使有很多红色的波浪线,生成的代码也能正常工作。

<script type="text/javascript">

var data = [
    @for (int i=0; i < Model.Packages.Count; i++)
    {
        var package = Model.Packages[i];
        <text>{Id: ko.observable(package.Id),
               Name: ko.observable(package.Name)
              }</text>
    }

    var viewModel = {
        var packages = ko.observableArray(data);
        // more code
    }

    ko.applyBindings(viewModel);
</script>

根据您的模型复杂度,此代码可能会变得非常混乱。就像您提到的那样,您也可以使用Html.Raw()将模型对象序列化为JSON。如果您选择这条路线,可以使用KO Mapping库构建Knockout ViewModel:

<script type="text/javascript">
    var data = @Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model));
    var viewModel = ko.mapping.fromJS(data);
    ko.applyBindings(viewModel);
</script>

第一种选项比这种方式不那么笨拙,但我觉得这样做我要放弃太多的控制权。这意味着我的KO ViewModel与我的MVC ViewModel的结构非常紧密地耦合在一起,这可能是我想要的,也可能不是我想要的。更不用说,为了让这个工作,我的JavaScript需要在我的cshtml页面中,我真的不喜欢这样。最后,这两种方法都是纯服务器端的。对于像SPI这样的响应更快的网页,你需要在客户端做更多的事情。

我更喜欢使用来自JavaScript的$.getJSON调用客户端。在这一点上,您可以将返回数据处理成您的ViewModel,手动滚动或使用映射库。如果您正在调用MVC控制器中的操作,只需将操作返回JsonResult类型(而不是ActionResult)。 (您也可以使用ContentResult进行类似的操作)。如果您可以使用新的MVC4 WebAPI,则这些控制器将默认返回JSON。


1
我很想看到一个代码示例,其中您在上面的答案中使用了$.getJSON。 - dmikester1

8
@Html.Raw(Json.Encode(Model))用于在实际页面下载的一部分中发送数据。这可能导致您的页面需要较长时间才能呈现给用户,但当它呈现时,应该已经准备好了。 $.get或$.ajax将在页面呈现时获取数据。这会创建一个单独的调用,结果是在页面呈现后更新页面。
至于使用哪个...取决于您的页面布局,是否必须从一开始就拥有所有数据以及获取数据与呈现页面所需的时间长短。

7

我的做法:

  • 视图模型在自己的JS文件中编写,没有服务器端代码生成
  • 视图模型通过$.get或$.ajax加载数据
  • 视图在创建视图模型时传入一个对象,该对象包含所有服务器端生成的URL

一个例子:

function MyViewModel(urls) {
    var self = this;
    self.isLoading = ko.observable(true);
    self.items = ko.observableArray();
    self.loadData = function() {
        $.get(urls.load, function(data) {
                // Process data and push into our items array
                self.isLoading(false);
            });
    }
}

var vm = new MyViewModel({
        load: '@Url.Action("GetData", "MyItemController")'
    });

$(function() {
    ko.applyBindings(vm);
    viewModel.loadData();
});

这意味着我需要额外进行一次AJAX调用来获取数据,但是我认为用户已经逐渐意识到数据并不等同于UI。好处是我的UI可以快速提供,因为没有实际的数据访问涉及。数据加载可能需要一些时间,这取决于数据库、数据量等等。此外,它还让我的代码非常干净地分离关注点。

我喜欢这个。当我还在以“旧方式”(postbacks和有限的ajax)处理事务时,我总是通过部分视图将初始数据填充到页面的HTML中。但我现在不再这样做了,因为这毫无意义。这种方式的好处是我不需要担心缓存页面带回旧数据。 - Simon_Weaver

3

我所做的是使用Html.Raw,然后将该js对象传递给我的knockout js模型。就像这样:

//I'm using Newtonsoft library to serialize the objects
@{
    var jsModel = Newtonsoft.Json.JsonConvert.SerializeObject(Model);
}

<script type="text/javascript">
var model = @Html.Raw(jsModel);

var vm = new MyKnockoutModel(model);
ko.applyBindings(vm);

var MyKnockoutModel = function(model) {
    var self = this;
    this.Id = ko.observable(model.Id);
    this.Name = ko.observable(model.Name);
    this.Array1 = ko.observableArray(model.Array1);
}

</script>

这就是我所做的。


3

我使用 @Html.Raw 是因为在生成 Knockout UI 之前,用户无法在页面上执行任何有用的操作。此外,将 JSON 写入页面中会导致潜在的微小延迟,但由于用户在构建 UI 之前看不到令人不安的延迟,因此可以抵消这些延迟。对于用于构建初始视图模型的 JSON 直接放在页面中,我完全没有问题。

我在效率方面取得了提高,是通过从 MVC 控制器中检索用于选择选项(产品列表等)的可重复使用的参考数据,并在单独的动态脚本下载中进行缓存。这样,它们可以在视图模型之间被重复使用,而视图模型只需要存储所选项目的值。


2
正如您所说,这基本上是根据需求而定的。
如果您正在做一个“单页应用程序”,那么您将会进行很多$.get和$.ajax调用,如果您只是渲染一个单独的页面,将模型放在Html中可能会更快,节省了向服务器发送额外请求的时间。
这也取决于您需要多少服务器端代码,如果我的应用程序需要一个API,我倾向于重用API并进行$.get和$.ajax调用,如果不需要,我就把模型放在Html中。

同意,这取决于情况。如果您不在服务器上使用模型,则无需使用MVC来处理模型。相反,只需使用MVC 3控制器操作、WCF服务或新的Web API与API控制器返回即可。我更喜欢后者。 - John Papa

0

虽然这是一个老问题,但我认为我有一个漂亮而整洁的解决方案,可以以可重用的方式将模型数据传递给KO视图模型。请注意,我也在使用require.js。

在.NET中添加HtmlHelper扩展方法:

using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace PROJECT.YOUR.EXTENSIONS.NAMESPACE {

    public static class HtmlHelperJavascriptExtensions {

        public static IHtmlString Serialize(this HtmlHelper helper, object obj) {
            return helper.Raw(new JavaScriptSerializer().Serialize(obj));
        }
    }
}

添加局部视图,KnockoutJsBinderPartial(我不得不使用aspx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<PROJECT.Views.Shared.KnockoutJsBinderViewData>" %>
<%@ Import Namespace="PROJECT.YOUR.EXTENSIONS.NAMESPACE" %>

<script type="text/javascript">
    require(["Path/To/KnockoutJs", "<%= Model.ViewModelPath %>"], function (ko, ViewModel) {
        ko.applyBindings(new ViewModel(<%= Html.Serialize(Model.InnerModel) %>));
    });
</script>

该视图引用的模型是:

namespace PROJECT.Views.Shared {

    public class KnockoutJsBinderViewData {

        public object InnerModel { get; private set; }

        public string ViewModelPath { get; private set; }

        public KnockoutJsBinderViewData(object innerModel, string viewModelPath) {
            InnerModel = innerModel;
            ViewModelPath = viewModelPath;
        }
    }
}

现在,要将您的.NET模型连接到您的knockout视图模型,您需要做的就是:
<% Html.RenderPartial(
   "~/Views/Shared/KnockoutJsBinderPartial.ascx",
   new PROJECT.Views.Shared.KnockoutJsBinderViewData(
        Model, // The model from your strongly typed MVC view
        "Path/To/Knockout/ViewModel")); // The path to the Javascript file containing your view model
%>

注意,包含视图模型的Javascript文件不应调用ko.applyBindings,并且应返回视图模型的构造函数。构造函数应接受一个参数 - 模型的JSON数据。

-3
在我看来,保持 HTML 的简洁并避免混合内联 JavaScript 和数据是可取的。
我更喜欢通过 AJAX 调用端点来注入数据。

你好。我通常不会从低质量队列中出来进行投票,但当我遇到非常糟糕的事情时,我还是会这样做。在这种情况下,它就是jQuery。 - bjb568
你能看一下如何改进这篇文章吗? - Engineer2021

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