如何与ASP.NET MVC ViewModels一起使用Knockout.js?


129

赏金

已经有一段时间了,我仍然有几个悬而未决的问题。我希望通过悬赏,也许这些问题会得到解答。

  1. 您如何将Knockout.js与html助手一起使用
  2. 为什么需要准备好文档才能使其正常工作(有关更多信息,请参见第一个编辑)

  3. 如果我在视图模型中使用基因剔除映射,该怎么做?由于映射,我没有功能。

    function AppViewModel() {
    
        // ... leave firstName, lastName, and fullName unchanged here ...
    
        this.capitalizeLastName = function() {
    
        var currentVal = this.lastName();        // Read the current value
    
        this.lastName(currentVal.toUpperCase()); // Write back a modified value
    
    };
  4. 我想使用插件,例如,我希望能够回滚observables,就好像用户取消了一个我希望能够返回到上一个值的请求一样。根据我的研究,这似乎是通过制作可编辑的插件来实现的

    如果我正在使用映射,该如何使用类似的东西?我真的不想要一种方法,在我的视图手动映射中,我将每个MVC viewMode字段映射到KO模型字段,因为我需要尽可能少的内联javascript,这似乎是工作量的两倍,那是为什么我喜欢那种映射。

  5. 我担心要使此工作容易进行(通过使用映射),我会失去很多KO功能,但另一方面,我担心手动映射会很繁重,并且会使我的视图包含太多信息,并且将来可能会变得更难维护(例如,如果我删除了MVC模型中的属性,那么我也必须在KO视图模型中移动它)


原始帖子

我正在使用asp.net mvc 3,我正在研究淘汰赛,因为它看起来很酷,但是我很难弄清楚它如何与asp.net mvc一起工作,尤其是视图模型。

现在对我来说,我做这样的事情

 public class CourseVM
    {
        public int CourseId { get; set; }
        [Required(ErrorMessage = "Course name is required")]
        [StringLength(40, ErrorMessage = "Course name cannot be this long.")]
        public string CourseName{ get; set; }


        public List<StudentVm> StudentViewModels { get; set; }

}

我将拥有一个Vm,它具有一些基本属性,例如CourseName,并且在它上面还将进行一些简单的验证。如果需要,Vm模型也可能包含其他视图模型。

然后,我将把这个Vm传递给View,否则我将使用html helper来帮助我将其显示给用户。

@Html.TextBoxFor(x => x.CourseName)

我可能会有一些foreach循环或一些将数据从“学生视图模型”集合中获取的方法。

然后,当我提交表单时,我将使用jquery并将serialize array其发送到控制器操作方法,该方法会将其绑定回视图模型。

有了kickout.js,一切都不同了,因为您现在有了它的视图模型,从我看到的所有示例中,它们都没有使用html helper。

您如何将MVC的这2个功能与kickout.js结合使用?

我找到了这个视频,并且它简短地(视频的最后几分钟@ 18:48)通过基本上具有一个内联脚本进入了使用视图模型的方式,该脚本具有被分配了ViewModel中值的kickout.js视图模型。

这是唯一的方法吗?在我的示例中包含视图模型集合怎么样?我是否必须有一个foreach循环或某种东西来提取所有值并将其分配给淘汰赛?

至于html助手,视频中没有任何内容。

这是两个使我感到困惑的领域,因为似乎没有多少人谈论它,这使我困惑于当示例只是一些硬编码的值示例时,初始值和一切如何进入视野。


编辑

我正在尝试达林·迪米特洛夫(Darin Dimitrov)提出的建议,但这似乎行得通(尽管我必须对他的代码进行一些更改)。不知道为什么我必须使用准备就绪的文档,但是如果没有它,一切都会准备就绪。

@model MvcApplication1.Models.Test

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title>Index</title>
    <script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
    <script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
    <script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
   <script type="text/javascript">

   $(function()
   {
      var model = @Html.Raw(Json.Encode(Model));


// Activates knockout.js
ko.applyBindings(model);
   });

</script>

</head>
<body>
    <div>
        <p>First name: <strong data-bind="text: FirstName"></strong></p>
        <p>Last name: <strong data-bind="text: LastName"></strong></p>
        @Model.FirstName , @Model.LastName
    </div>
</body>
</html>

我不得不将其包装在准备好使其工作的jquery文档周围。

我也收到此警告。不知道这是怎么回事。

Warning 1   Conditional compilation is turned off   -> @Html.Raw

因此,我有一个起点,我想至少可以在我做更多的尝试以及其工作原理后进行更新。

我正在尝试阅读交互式教程,但改用ViewModel。

尚不确定如何解决这些问题

function AppViewModel() {
    this.firstName = ko.observable("Bert");
    this.lastName = ko.observable("Bertington");
}

要么

function AppViewModel() {
    // ... leave firstName, lastName, and fullName unchanged here ...

    this.capitalizeLastName = function() {
        var currentVal = this.lastName();        // Read the current value
        this.lastName(currentVal.toUpperCase()); // Write back a modified value
    };


编辑2

我能够找出第一个问题。没有第二个问题的线索。不过。任何人有任何想法吗?

 @model MvcApplication1.Models.Test

    @{
        Layout = null;
    }

    <!DOCTYPE html>

    <html>
    <head>
        <title>Index</title>
        <script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
        <script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
        <script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
       <script type="text/javascript">

       $(function()
       {
        var model = @Html.Raw(Json.Encode(Model));
        var viewModel = ko.mapping.fromJS(model);
        ko.applyBindings(viewModel);

       });

    </script>

    </head>
    <body>
        <div>
            @*grab values from the view model directly*@
            <p>First name: <strong data-bind="text: FirstName"></strong></p>
            <p>Last name: <strong data-bind="text: LastName"></strong></p>

            @*grab values from my second view model that I made*@
            <p>SomeOtherValue <strong data-bind="text: Test2.SomeOtherValue"></strong></p>
            <p>Another <strong data-bind="text: Test2.Another"></strong></p>

            @*allow changes to all the values that should be then sync the above values.*@
            <p>First name: <input data-bind="value: FirstName" /></p>
            <p>Last name: <input data-bind="value: LastName" /></p>
            <p>SomeOtherValue <input data-bind="value: Test2.SomeOtherValue" /></p>
            <p>Another <input data-bind="value: Test2.Another" /></p>

           @* seeing if I can do it with p tags and see if they all update.*@
            <p data-bind="foreach: Test3">
                <strong data-bind="text: Test3Value"></strong> 
            </p>

     @*took my 3rd view model that is in a collection and output all values as a textbox*@       
    <table>
        <thead><tr>
            <th>Test3</th>
        </tr></thead>
          <tbody data-bind="foreach: Test3">
            <tr>
                <td>    
                    <strong data-bind="text: Test3Value"></strong> 
<input type="text" data-bind="value: Test3Value"/>
                </td>
            </tr>    
        </tbody>
    </table>

控制者

  public ActionResult Index()
    {
              Test2 test2 = new Test2
        {
            Another = "test",
            SomeOtherValue = "test2"
        };

        Test vm = new Test
        {
            FirstName = "Bob",
            LastName = "N/A",
             Test2 = test2,

        };
        for (int i = 0; i < 10; i++)
        {
            Test3 test3 = new Test3
            {
                Test3Value = i.ToString()
            };

             vm.Test3.Add(test3);
        }

        return View(vm);
    }

2
我刚刚写了一篇博客文章来回答另一个类似的问题:roysvork.wordpress.com/2012/12/09/…它可能无法完全回答您的问题,但可以使您很好地了解事情的工作方式。我希望在不久的将来再发表一篇文章。如果您需要更多信息,请随时在帖子评论中或在这里询问我任何问题。
代码外

Answers:


180

我想我已经汇总了您所有的问题,如果我错过了什么,请让我知道(如果您可以将所有问题汇总到一个地方,那就很好 =))

注意。与已ko.editable添加插件的兼容性

下载完整代码

您如何将Knockout.js与html助手一起使用

这很简单:

@Html.TextBoxFor(model => model.CourseId, new { data_bind = "value: CourseId" })

哪里:

  • value: CourseId指示您正在valueinput控件的CourseId属性与模型和脚本模型中的属性绑定在一起

结果是:

<input data-bind="value: CourseId" data-val="true" data-val-number="The field CourseId must be a number." data-val-required="The CourseId field is required." id="CourseId" name="CourseId" type="text" value="12" />

为什么需要准备好文档才能使其正常工作(有关更多信息,请参见第一个编辑)

我还不明白为什么您需要使用该ready事件来序列化模型,但似乎只是必需的(尽管不必担心)

如果我在视图模型中使用基因剔除映射,该怎么做?由于映射,我没有功能。

如果我正确理解,您需要向KO模型添加新方法,那么很容易合并模型

有关更多信息,请在“从不同来源映射”部分中

function viewModel() {
    this.addStudent = function () {
        alert("de");
    };
};

$(function () {
    var jsonModel = '@Html.Raw(JsonConvert.SerializeObject(this.Model))';
    var mvcModel = ko.mapping.fromJSON(jsonModel);

    var myViewModel = new viewModel();
    var g = ko.mapping.fromJS(myViewModel, mvcModel);

    ko.applyBindings(g);
});

关于您收到的警告

警告1条件编译已关闭-> @ Html.Raw

您需要使用引号

与ko.editable插件的兼容性

我以为它将变得更加复杂,但是事实证明,集成真的很容易,为了使您的模型可编辑,只需添加以下行:(请记住,在这种情况下,我使用的是服务器和服务器之间的混合模型在客户端添加扩展名,然后可编辑就可以正常工作了。。。

    ko.editable(g);
    ko.applyBindings(g);

从这里开始,您只需要使用插件添加的扩展来玩您的绑定,例如,我有一个按钮可以像这样开始编辑我的字段,并在此按钮中开始编辑过程:

    this.editMode = function () {
        this.isInEditMode(!this.isInEditMode());
        this.beginEdit();
    };

然后,我使用以下代码提交和取消按钮:

    this.executeCommit = function () {
        this.commit();
        this.isInEditMode(false);
    };
    this.executeRollback = function () {
        if (this.hasChanges()) {
            if (confirm("Are you sure you want to discard the changes?")) {
                this.rollback();
                this.isInEditMode(false);
            }
        }
        else {
            this.rollback();
            this.isInEditMode(false);
        }
    };

最后,我有一个字段指示这些字段是否处于编辑模式,这只是绑定enable属性。

this.isInEditMode = ko.observable(false);

关于数组问题

我可能会有一些foreach循环或一些将数据从“学生视图模型”集合中获取的方法。

然后,当我提交表单时,我将使用jquery并序列化数组并将其发送到控制器操作方法,该方法会将其绑定回viewmodel。

您可以使用KO进行相同的操作,在以下示例中,我将创建以下输出:

在此处输入图片说明

基本上在这里,您有两个使用HelpersKO 创建并与KO绑定的列表,它们绑定了一个dblClick事件,该事件在被触发时,从当前列表中删除所选项目并将其添加到其他列表中,当您发布到时Controller,每个的内容列表作为JSON数据发送并重新附加到服务器模型

块金:

外部脚本

控制器代码

    [HttpGet]
    public ActionResult Index()
    {
        var m = new CourseVM { CourseId = 12, CourseName = ".Net" };

        m.StudentViewModels.Add(new StudentVm { ID = 545, Name = "Name from server", Lastname = "last name from server" });

        return View(m);
    }

    [HttpPost]
    public ActionResult Index(CourseVM model)
    {
        if (!string.IsNullOrWhiteSpace(model.StudentsSerialized))
        {
            model.StudentViewModels = JsonConvert.DeserializeObject<List<StudentVm>>(model.StudentsSerialized);
            model.StudentsSerialized = string.Empty;
        }

        if (!string.IsNullOrWhiteSpace(model.SelectedStudentsSerialized))
        {
            model.SelectedStudents = JsonConvert.DeserializeObject<List<StudentVm>>(model.SelectedStudentsSerialized);
            model.SelectedStudentsSerialized = string.Empty;
        }

        return View(model);
    }

模型

public class CourseVM
{
    public CourseVM()
    {
        this.StudentViewModels = new List<StudentVm>();
        this.SelectedStudents = new List<StudentVm>();
    }

    public int CourseId { get; set; }

    [Required(ErrorMessage = "Course name is required")]
    [StringLength(100, ErrorMessage = "Course name cannot be this long.")]
    public string CourseName { get; set; }

    public List<StudentVm> StudentViewModels { get; set; }
    public List<StudentVm> SelectedStudents { get; set; }

    public string StudentsSerialized { get; set; }
    public string SelectedStudentsSerialized { get; set; }
}

public class StudentVm
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Lastname { get; set; }
}

CSHTML页面

@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>CourseVM</legend>

        <div>
            <div class="editor-label">
                @Html.LabelFor(model => model.CourseId)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.CourseId, new { data_bind = "enable: isInEditMode, value: CourseId" })
                @Html.ValidationMessageFor(model => model.CourseId)
            </div>

            <div class="editor-label">
                @Html.LabelFor(model => model.CourseName)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.CourseName, new { data_bind = "enable: isInEditMode, value: CourseName" })
                @Html.ValidationMessageFor(model => model.CourseName)
            </div>
            <div class="editor-label">
                @Html.LabelFor(model => model.StudentViewModels);
            </div>
            <div class="editor-field">

                @Html.ListBoxFor(
                    model => model.StudentViewModels,
                    new SelectList(this.Model.StudentViewModels, "ID", "Name"),
                    new
                    {
                        style = "width: 37%;",
                        data_bind = "enable: isInEditMode, options: StudentViewModels, optionsText: 'Name', value: leftStudentSelected, event: { dblclick: moveFromLeftToRight }"
                    }
                )
                @Html.ListBoxFor(
                    model => model.SelectedStudents,
                    new SelectList(this.Model.SelectedStudents, "ID", "Name"),
                    new
                    {
                        style = "width: 37%;",
                        data_bind = "enable: isInEditMode, options: SelectedStudents, optionsText: 'Name', value: rightStudentSelected, event: { dblclick: moveFromRightToLeft }"
                    }
                )
            </div>

            @Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
            @Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
            @Html.HiddenFor(model => model.StudentsSerialized, new { data_bind = "value: StudentsSerialized" })
            @Html.HiddenFor(model => model.SelectedStudentsSerialized, new { data_bind = "value: SelectedStudentsSerialized" })
        </div>

        <p>
            <input type="submit" value="Save" data-bind="enable: !isInEditMode()" /> 
            <button data-bind="enable: !isInEditMode(), click: editMode">Edit mode</button><br />
            <div>
                <button data-bind="enable: isInEditMode, click: addStudent">Add Student</button>
                <button data-bind="enable: hasChanges, click: executeCommit">Commit</button>
                <button data-bind="enable: isInEditMode, click: executeRollback">Cancel</button>
            </div>
        </p>
    </fieldset>
}

剧本

<script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ko.editables.js")" type="text/javascript"></script>

<script type="text/javascript">
    var g = null;
    function ViewModel() {
        this.addStudent = function () {
            this.StudentViewModels.push(new Student(25, "my name" + new Date(), "my last name"));
            this.serializeLists();
        };
        this.serializeLists = function () {
            this.StudentsSerialized(ko.toJSON(this.StudentViewModels));
            this.SelectedStudentsSerialized(ko.toJSON(this.SelectedStudents));
        };
        this.leftStudentSelected = ko.observable();
        this.rightStudentSelected = ko.observable();
        this.moveFromLeftToRight = function () {
            this.SelectedStudents.push(this.leftStudentSelected());
            this.StudentViewModels.remove(this.leftStudentSelected());
            this.serializeLists();
        };
        this.moveFromRightToLeft = function () {
            this.StudentViewModels.push(this.rightStudentSelected());
            this.SelectedStudents.remove(this.rightStudentSelected());
            this.serializeLists();
        };
        this.isInEditMode = ko.observable(false);
        this.executeCommit = function () {
            this.commit();
            this.isInEditMode(false);
        };
        this.executeRollback = function () {
            if (this.hasChanges()) {
                if (confirm("Are you sure you want to discard the changes?")) {
                    this.rollback();
                    this.isInEditMode(false);
                }
            }
            else {
                this.rollback();
                this.isInEditMode(false);
            }
        };
        this.editMode = function () {
            this.isInEditMode(!this.isInEditMode());
            this.beginEdit();
        };
    }

    function Student(id, name, lastName) {
        this.ID = id;
        this.Name = name;
        this.LastName = lastName;
    }

    $(function () {
        var jsonModel = '@Html.Raw(JsonConvert.SerializeObject(this.Model))';
        var mvcModel = ko.mapping.fromJSON(jsonModel);

        var myViewModel = new ViewModel();
        g = ko.mapping.fromJS(myViewModel, mvcModel);

        g.StudentsSerialized(ko.toJSON(g.StudentViewModels));
        g.SelectedStudentsSerialized(ko.toJSON(g.SelectedStudents));

        ko.editable(g);
        ko.applyBindings(g);
    });
</script>

注意:我刚刚添加了以下几行:

        @Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
        @Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })

因为当我提交表单时,我的字段被禁用,所以值没有传输到服务器,这就是为什么我添加了几个隐藏字段来解决问题的原因


嗯,非常有用。从您的回答和Pual的回答中,我想我几乎回答了所有问题,除了如何使用可编辑之类的插件。希望有人知道我如何使用它。
chobo2 2012年

1
我刚刚添加了与ko.editables插件的兼容性,您可以检查更新的响应,或者如果需要,可以下载整个项目以在本地运行
Jupaol 2012年

如果可以的话,我会检查一下。要使其正常工作需要做很多改变吗?我想知道是否为每个插件找到了是否必须对其进行更改,然后必须保留自己的版本。
chobo2 2012年

不。你会惊奇地发现,这几乎超出现成
Jupaol

1
感谢一群人,我从您的回应中学到了几种新策略。荣誉!
sky-dev 2012年

23

您可以将ASP.NET MVC视图模型序列化为javascript变量:

@model CourseVM
<script type="text/javascript">
    var model = @Html.Raw(Json.Encode(Model));
    // go ahead and use the model javascript variable to bind with ko
</script>

您可以阅读淘汰文件中的许多示例。


1
是的,我浏览了他们在网站上的互动式教程,但实际上我从来没有发现与asp.net mvc有任何关系。我看到他们也有一些映射插件,但是不确定如何安装。在您的示例中,如何将其绑定到淘汰模型(在另一个脚本中)。我真的很想尽量减少内联javascript(非首选,但我想不可能)
chobo2 2012年

2
您要解决什么问题?如果您想要MVC视图并且对如何使用它们感到满意,则可以坚持使用。如果要客户端数据绑定和操作,那么KO是一个不错的选择。如答案所示,您可以从MVC代码生成KO视图模型。它需要vm并将其序列化为json。然后在客户端上,您可以将结果映射到javascript视图模型。然后将viewmodel绑定到视图,您就已经准备就绪。关键是MVC和KO不必以任何方式耦合,除非您希望它们耦合。这完全取决于您要解决的问题。
约翰·帕帕2012年

1
正常情况下,您看不到与asp.net mvc有任何关系。淘汰赛是一个客户端框架。它既不知道,也不在乎您使用的是哪种服务器端语言。这两个框架应该绝对分离。
Darin Dimitrov

@JohnPapa-我喜欢现在做事的方式,但我也喜欢学习新事物(我看到KO在某些情况下可能非常有用)。我知道KO是客户端脚本,但对我来说,我认为它们是一起工作的。我目前使用视图模型和html帮助器生成视图。因此,在我看来,KO需要与此合作。例如说您有编辑对话框。您将如何设计数据库中的值并将其填充到这些字段中。如果我使用我的方式,那将是具有viewModel的html helper视图。将填充视图模型并通过Action Method发送并使用它。
chobo2 2012年

1
@ chobo2,敲除是一个客户端框架。它在客户端上使用视图模型在客户端上实现MVC模式。服务器已解耦。您也可以在其上使用视图模型。只是两个不同的地方。如果您要使用javascript在客户端上实现一些复杂的逻辑,那么敲除可以简化此过程。否则,说实话,您不需要它。
Darin Dimitrov 2012年

2

为了在服务器映射后获得其他计算的属性,您将需要在客户端进一步增强视图模型。

例如:

var viewModel = ko.mapping.fromJS(model);

viewModel.capitalizedName = ko.computed(function() {...}, viewModel);

因此,每次从原始JSON映射时,都需要重新应用计算出的属性。

此外,映射插件还提供了增量更新视图模型的功能,而不是每次来回创建视图模型时都可以重新创建视图模型(使用中的附加参数fromJS):

// Every time data is received from the server:
ko.mapping.fromJS(data, viewModel);

然后,在仅映射的属性的模型上执行增量数据更新。您可以在映射文档中阅读有关此内容的更多信息。

您在对Darin的FluentJSON软件包的评论中提到了这一点。我是它的作者,但是它的用例比ko.mapping更具体。我通常只会在您的视图模型是一种方式(即服务器->客户端)然后以某种不同的格式回发数据(或根本不回发)的情况下使用它。或者,如果您的javascript视图模型需要采用与服务器模型完全不同的格式。


嗯,那么我想也许FluentJSON不适合我,因为我的视图模型大多数时候都是双向的(我通常通过json将其发送回去,然后将其绑定到action方法参数中的视图模型上)。您知道如何使用我提到的可编辑插件吗?最后,通过使用映射并尝试使用视图模型而不是不使用它,我是否失去了任何功能?
chobo2

我还没有使用过任何插件,所以不确定。过去我所做的只是订阅每个更改,并保留一堆序列化的视图模型状态,这些状态将在更改时推送到并在撤消时弹出(请参阅此问题)。
Paul Tyng,2012年

映射并不能阻止您使用任何功能,您只需要确保并遵守其约定以了解如何处理往返于JS的映射,即可使它们完美地结合在一起使用。
Paul Tyng,2012年

那么,您发布的问题的公认答案基本上是该插件是什么。那就是让我感到困惑的原因,因为您可以看到他们创建了一个视图模型,然后使用他们创建的函数(ko.observableArrayWithUndo([]))。如果我正在做映射,我不知道该怎么做。唯一想到的是编写自己的映射(我怀疑我现在是否可以纠正),该映射具有可撤销的可观察性或将每个属性映射出,但是我基本上有重复的视图模型,一个用于服务器端,一个用于客户端,害怕将变得难以
维持

嗯,对不起,我在说我对这个问题的回答,对不起,应该直接链接。
保罗·廷
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.