如何使用ASP.NET MVC ViewModels knockout.js?

赏金

已经有一段时间了,我还有一些未解决的问题。 我希望通过增加一个赏金,也许这些问题将得到答案。

  1. 你如何使用knockout.js的HTML助手
  2. 为什么需要准备文档才能使其工作(请参阅第一次编辑以获取更多信息)

  3. 我如何做这样的事情,如果我使用我的视图模型的挖空映射? 由于我没有一个function,由于映射。

    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. 我担心为了使这项工作变得简单(通过使用制图),我将失去很多权力,但另一方面,我担心手工制图只是大量的工作,并会使我的观点包含太多的信息,可能会在将来变得更难以维护(比如,如果我在MVC模型中删除一个属性,我也必须在KO视图模型中移动它)


原始post

我使用的是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这样的基本属性,它会有一些简单的validation。 如果需要,Vm模型也可能包含其他视图模型。

然后我会通过这个Vm到视图我会使用HTML帮助器来帮助我显示给用户。

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

我可能有一些foreach循环或东西来从学生视图模型的集合中获取数据。

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

随着knockout.js它是所有不同的,因为你现在得到了它的viewmodels和我看到的所有例子,他们不使用HTML助手。

你如何使用MVC的这两个特性与knockout.js?

我发现这个video ,并简要地(video@ 18:48的最后几分钟)通过基本上有一个内联脚本的视图模型,具有knockout.js视图模型获取ViewModel中的值分配。

这是唯一的方法吗? 在我的例子中有一个视图模型的集合呢? 我必须有一个foreach循环或东西来提取所有的值,并将其分配到淘汰赛?

至于html助手,这个video并没有提到他们。

这两个领域混淆了我,因为并不是很多人谈论这个问题,当我看到一些硬编码的价值的例子时,我不明白初始值和一切是如何看待的。


编辑

我正在尝试达林·季米特洛夫所build议的,这看起来很有效(但我不得不对他的代码做一些修改)。 不知道为什么我不得不使用文件准备好,但不知何故,没有它,一切都没有准备好。

 @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 

所以我有一个起点,我想至less会更新,当我做了更多的玩耍,以及这是如何工作的。

我正在尝试通过交互式教程,而不是使用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); } 

我想我已经总结了所有的问题,如果我错过了一些事情,请让我知道( 如果你能在一个地方总结你所有的问题都会很好 =))

注意。 ko.editable插件兼容

下载完整的代码

你如何使用knockout.js的HTML助手

这很容易:

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

哪里:

  • value: CourseId表示您将input控件的CourseId属性与来自模型和脚本模型的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事件序列化模型,但它似乎只是需要 (虽然不用担心)

我如何做这样的事情,如果我使用我的视图模型的挖空映射? 由于我没有一个function,由于映射。

如果我理解正确,则需要在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条件编译已closures – > @ Html.Raw

你需要使用引号

与可编辑插件兼容

我认为这会变得更加复杂,但事实certificate,集成非常简单,为了使您的模型可编辑,只需添加以下行:(请记住,在这种情况下,我使用混合模型,从服务器和在客户端添加扩展和可编辑简单的作品…这是伟大的):

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

从这里开始,您只需要使用插件添加的扩展来处理绑定,例如,我有一个button来开始编辑我的字段,在这个button中我开始编辑过程:

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

然后我用下面的代码提交和取消button:

  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.isInEditMode = ko.observable(false); 

关于你的数组问题

我可能有一些foreach循环或东西来从学生视图模型的集合中获取数据。

然后,当我提交表单我会使用jQuery和序列化数组,并将其发送到控制器的操作方法,将绑定回视图模型。

你可以用KO做同样的事情,在下面的例子中,我将创build下面的输出:

在这里输入图像说明

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

掘金:

  • Newtonsoft
  • jQuery的
  • knockoutjs
  • Knockout.Mapping

外部脚本 。

控制器代码

  [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" }) 

因为当我提交表单时,我的字段被禁用,所以这些值不会被传输到服务器,这就是为什么我添加了一些隐藏的领域来做这个伎俩

您可以将您的ASP.NET MVC视图模型序列化为一个javascriptvariables:

 @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> 

在淘汰赛文件中有很多例子可以通过。

要在服务器映射之后实现额外的计算属性,您需要在客户端进一步增强您的视图模型。

例如:

 var viewModel = ko.mapping.fromJS(model); viewModel.capitalizedName = ko.computed(function() {...}, viewModel); 

所以每次你从原始的JSON映射,你将需要重新应用计算的属性。

此外,映射插件提供了增量更新视图模型的能力,而不是每次来回重新创build视图模型(在fromJS使用其他参数):

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

然后,对您映射的属性模型执行增量数据更新。 你可以在地图文档中阅读更多关于这个的信息

你在Darin的回答中提到了FluentJSON包。 我是那个的作者,但是它的用例比ko.mapping更具体。 我通常只使用它,如果你的viewmodels是一种方式(即。服务器 – >客户端),然后数据以不同的格式(或根本不)。 或者,如果您的javascript viewmodel需要与您的服务器模型大不相同的格式。