将MVC模型视图与关联的Knockout.js ViewModel连接

时间:2014-09-04 20:24:13

标签: asp.net-mvc knockout.js asp.net-mvc-viewmodel

背景:我对MVC& amp; Knockout.js但我正在努力加快这些技术的速度。我正在使用MVC 5和EF6以及Knockout.JS 3.2。

我有一个Detail视图,根据URL中传递的ID使用MVC提取“VoteAnswer”对象:

例如,我可以转到url MyDomain / VoteAnswers / Details / 1,它将正确地从我的数据库中提取信息(它将ID为1的VoteAnswer)并显示在我的详细信息视图中。但是我试图连接我的Knockout.js“VoteAnswer”ViewModel以同样的方式运行并遇到麻烦。

这是我的详细信息视图:(请注意@ Html.DisplayFor(model => model.VoteAnswerId)等工作并显示数据库中的数据。

@model AM_SPA_TestSite.Models.VoteAnswer

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
     <meta name="viewport" content="width=device-width" />
    <title>Details</title>
    <script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
    <div>
        <h4>VoteAnswer</h4>
    <hr />
    <table>
        <tr>
            <td>Id</td>
            <td data-bind="text: id"></td>
        </tr>
        <tr>
            <td>Display Text</td>
            <td data-bind="text: isActive"></td>
        </tr>
        <tr>
            <td>IsActive</td>
            <td data-bind="text: displayText"></td>
        </tr>
    </table>
    <table>
        <tr>
            <td>Id</td>
            <td><input type="text" data-bind="value: id" /></td>
        </tr>
        <tr>
            <td>Display Text</td>
            <td><input type="text" data-bind="value: displayText" /></td>
        </tr>
        <tr>
            <td>IsActive</td>
            <td><input type="text" data-bind="value: isActive" /></td>
        </tr>
    </table>
    <table>
        <tr>
            <td>Id</td>
            <td>@Html.DisplayFor(model => model.VoteAnswerId)</td>
        </tr>
        <tr>
            <td>Display Text</td>
            <td>@Html.DisplayFor(model => model.DisplayText)</td>
        </tr>
        <tr>
            <td>IsActive</td>
            <td>@Html.DisplayFor(model => model.IsActive)</td>
        </tr>
    </table>
</div>

这是我的Knockout.Js ViewModel

// VoteAnswer ViewModel
var VoteAnswerVM = {

id: ko.observable(),
displayText: ko.observable(),
isActive: ko.observable(),
SaveVoteAnswer: function () {
    $.ajax({
        url: '/VoteAnswers/Create',
        type: 'post',
        dataType: 'json',
        data: ko.toJSON(this),
        contentType: 'application/json',
        success: function (result) {
        },
        error: function (err) {
            if (err.responseText == "Creation Failed")
            { window.location.href = '/VoteAnswers/Index/'; }
            else {
                alert("Status:" + err.responseText);
                window.location.href = '/VoteAnswers/Index/';;
            }
        },
        complete: function () {
            window.location.href = '/VoteAnswers/Index/';
        }
    });
}
};

//Go
$(document).ready(function () {
    //initialize and create new VoteAnswerVM by URL value here?
    ko.applyBindings(VoteAnswerVM);
});

我知道我缺少的是初始化ID为1的ViewModel,但我认为MVC模型已经拥有数据并且knockout.js应该映射到该数据而无需通过向数据库发送请求来手动初始化再次。我错过了什么?感谢。

编辑已添加 解决方案 。我不确定我是否已采用这种方法,但现在确实如此。更新控制器以仅返回视图而不查询数据库。 (否则我会对同一数据进行两次数据库调用。

    // GET: VoteAnswers/Details/5
    public ViewResult Details(int? id)
    {
        return View();
    }

添加了一个查询数据库的API控制器。

    // GET: api/VoteAnswers/5
    [ResponseType(typeof(VoteAnswer))]
    public async Task<IHttpActionResult> GetVoteAnswer(int id)
    {
        VoteAnswer voteAnswer = await db.VoteAnswers.FindAsync(id);
        if (voteAnswer == null)
        {
            return NotFound();
        }

        return Ok(voteAnswer);
    }

在我的视图(.cshtml文件)中,我引用了我的knockout.js ModelView,View在下面:

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Details</title>
    <script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
<div>
    <h4>VoteAnswer</h4>
    <hr />
    <table>
        <tr>
            <td>Id</td>
            <td data-bind="text: VoteAnswerId"></td>
        </tr>
        <tr>
            <td>Display Text</td>
            <td data-bind="text: IsActive"></td>
        </tr>
        <tr>
            <td>IsActive</td>
            <td data-bind="text: DisplayText"></td>
        </tr>
    </table>
    <table>
        <tr>
            <td>Id</td>
            <td><input type="text" data-bind="value: VoteAnswerId" /></td>
        </tr>
        <tr>
            <td>Display Text</td>
            <td><input type="text" data-bind="value: DisplayText" /></td>
        </tr>
        <tr>
            <td>IsActive</td>
            <td><input type="text" data-bind="value: IsActive" /></td>
        </tr>
    </table>
</div>
<div id="error"></div>
</body>
</html>

更新了我的ViewModel脚本,以根据URL ID访问数据库。

// VoteAnswer ViewModel
var VoteAnswer = function () {
var self = this;
self.VoteAnswerId = ko.observable();
self.DisplayText = ko.observable();
self.IsActive = ko.observable();

self.SaveVoteAnswer = function () {
    $.ajax({
        url: '/VoteAnswers/Create',
        type: 'post',
        dataType: 'json',
        data: ko.toJSON(this),
        contentType: 'application/json',
        success: function (result) {
        },
        error: function (err) {
            if (err.responseText == "Creation Failed")
            { window.location.href = '/VoteAnswers/Index/'; }
            else {
                alert("Status:" + err.responseText);
                window.location.href = '/VoteAnswers/Index/';;
            }
        },
        complete: function () {
            window.location.href = '/VoteAnswers/Index/';
        }
    });
}
self.load = function (id) {
    if (id != 0) {
        $.ajax({
            url: '/api/VoteAnswers/' + id,
            type: 'get',
            data: ko.toJSON(this),
            contentType: 'application/json',
            success: function(data) {
                self.VoteAnswerId = ko.observable(data.voteAnswerId);
                self.DisplayText = ko.observable(data.displayText);
                self.IsActive = ko.observable(data.isActive);
                ko.applyBindings(self);
            },
            error: function(err) {
                if (err.responseText == "Creation Failed") {
                    window.location.href = '/VoteAnswers/Index/';
                } else {
                    $("#error").text("Status:" + err.responseText);
                    //window.location.href = '/VoteAnswers/Index/';;
                }
            },
            complete: function() {
                //window.location.href = '/VoteAnswers/Index/';
            }
        });
    } else {
        window.location.href = '/VoteAnswers/Index/';
    }
}
};
function GetURLParameter() {
var sPageUrl = window.location.href;
var indexOfLastSlash = sPageUrl.lastIndexOf("/");

if (indexOfLastSlash > 0 && sPageUrl.length - 1 != indexOfLastSlash)
    return sPageUrl.substring(indexOfLastSlash + 1);
else
    return 0;
}
//Go
$(document).ready(function () {
    //initialize and create new VoteAnswerVM by URL value here?
    var viewModel = new VoteAnswer();
    viewModel.load(GetURLParameter());
});

2 个答案:

答案 0 :(得分:0)

我希望我理解正确,如果我的回答错误,请告诉我错误的地方。

要实现的第一件事是,如果将KO observable绑定到输入字段,则knockout将不会查看该输入字段的初始值并将其存储在observable中。它将执行相反的操作:它将查看observable中的当前值并将其存储在输入字段的值中。在您的情况下,observable初始化时没有值,在JavaScript中表示值undefined。因此,如果将observable绑定到已填充Razor / MVC视图模型的字段,您将立即用您的observable中存储的空值覆盖这些值。

有一种方法可以通过Razor用你的数据填充你的Knockout模型,但是它涉及内联JavaScript,并且出于多种原因这是一种不好的做法(我会根据要求详细说明)。

执行此操作的最佳方法是将视图与数据分开:不要将MVC视图模型注入视图,而是创建一个返回JSON并在那里返回数据的单独端点(此端点将接收ID参数而不是视图)。 JSON端点是从JavaScript调用的,可用于使用正确的值填充模型。

好处:关注点分离,为更敏感的前端启用视图缓存的可能性,不需要使用剃刀语法,甚至更糟糕的是,将它与内联JS结合使用。您对UI的所有数据绑定都将通过Knockout进行。我自己也学到了这一点,因为我们也开始使用剃须刀,但从长远来看,这个解决方案对于一个大项目来说并不可行。我们从不后悔总是从单独的JSON端点获取数据。

如果你不确定如何做到这一点,我可以写一些伪代码来说明这个想法。

答案 1 :(得分:0)

这些可能是可能的解决方案

1。)错误地使用了data_bind

<td><input type="text" data-bind="value: isActive" /></td> // which is wrong

<td><input type="text" data_bind="value: isActive" /></td> //data_bind is wrongly used

<强> 2)。  如果仍然存在问题,您可以尝试这种语法

 @Html.DisplayFor(model => model.IsActive, new { data_bind = "value:IsActive" });

如果您发现仍然缺少某些内容,请提供一些详细信息。