Подключение представления модели MVC к связанной модели представления Knockout.js

Предыстория: я новичок в MVC и Knockout.js, но я пытаюсь освоить эти технологии. Я использую MVC 5 с EF6 и Knockout.JS 3.2.

У меня есть подробное представление, которое извлекает объект «VoteAnswer» с использованием MVC на основе идентификатора, переданного в URL-адресе:

Например, я могу перейти по URL-адресу MyDomain/VoteAnswers/Details/1, и он правильно извлечет информацию из моей базы данных (он извлечет VoteAnswer с идентификатором 1) и отобразит в моем представлении «Подробности». Однако я пытаюсь подключить свою ViewModel Knockout.js "VoteAnswer" для работы таким же образом, и у меня возникают проблемы.

Вот мое подробное представление: (Обратите внимание, что @Html.DisplayFor(model => model.VoteAnswerId) etc работает и отображает данные из моей базы данных.

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

Вот моя ViewModel Knockout.Js

// 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);
});

Я знаю, чего мне не хватает, так это инициализации ViewModel с идентификатором 1, но я думал, что модель MVC уже имеет данные, и Knockout.js ДОЛЖЕН сопоставлять эти данные без ручной инициализации путем повторной отправки запроса в базу данных. Что мне не хватает? благодаря.

EDIT: Добавлено решение ниже. Я не уверен, что остановился на этом подходе, но вот он. Обновлен контроллер, чтобы он ТОЛЬКО возвращал представление, а не запрашивал БД. (иначе у меня было бы два вызова базы данных для одних и тех же данных.

    // 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) я ссылаюсь на свой ModelView с нокаутом.js, вид ниже:

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

Обновлен скрипт My ViewModel для доступа к базе данных на основе идентификатора URL.

// 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());
});

person cmartin    schedule 04.09.2014    source источник
comment
Javascript ViewModel находится в отдельном файле JS или встроен непосредственно на странице бритвы?   -  person Robert Slaney    schedule 05.09.2014
comment
вы пробовали ko.applyBindings(new VoteAnswerVM()); что-то подобное   -  person super cool    schedule 05.09.2014
comment
@Robert - да, это отдельный файл JS, я проверил, что он загружается. Я также проверил, что viewModel и нокаут-код работают, если я вручную передаю параметры. Например, если я инициализировал так: 'ko.applyBindings (новый VoteAnswerVM (1, My Text, true));' и изменил функцию, за исключением этих свойств.   -  person cmartin    schedule 05.09.2014
comment
Я думаю, что ваше решение кажется довольно хорошим.   -  person Rich Bianco    schedule 17.09.2014


Ответы (2)


Надеюсь, я правильно вас понял, если мой ответ на ваш вопрос неверен, дайте мне знать, где я ошибся.

Первое, что нужно понять, это то, что если вы привязываете наблюдаемый KO к полю ввода, нокаут не будет смотреть на начальное значение этого поля ввода и сохранять его в наблюдаемом. Он будет делать обратное: он будет смотреть текущее значение в наблюдаемом и сохранять его в значении поля ввода. В вашем случае наблюдаемые инициализируются без значения, что в JavaScript означает значение undefined. Поэтому, если вы привяжете свои наблюдаемые объекты к полям, которые вы заполнили с помощью модели представления Razor/MVC, вы немедленно перезапишете эти значения пустыми значениями, хранящимися в ваших наблюдаемых.

СУЩЕСТВУЕТ способ заполнить вашу модель Knockout вашими данными через Razor, но он включает встроенный JavaScript и является плохой практикой по ряду причин (я подробнее расскажу об этом по запросу).

Лучший способ сделать это — отделить ваши представления от ваших данных: не внедряйте модель представления MVC в представление, а создайте отдельную конечную точку, которая возвращает JSON и возвращает туда данные (эта конечная точка получит параметр ID вместо параметра Посмотреть). Конечная точка JSON вызывается из JavaScript и может использоваться для заполнения вашей модели правильными значениями.

Плюсы: разделение задач, возможность включить кэширование представлений для более отзывчивого интерфейса, нет необходимости использовать синтаксис бритвы или, что еще хуже, комбинировать его со встроенным JS. Вся ваша привязка данных к UI будет происходить через Knockout. Я узнал об этом сам, потому что мы тоже начали использовать бритву, но в долгосрочной перспективе это решение было неприемлемо для большого проекта. Мы ни разу не пожалели, что переключились на получение данных всегда из отдельных конечных точек JSON.

Если вы не знаете, как это сделать, я могу написать псевдокод, чтобы проиллюстрировать эту идею.

person Hans Roerdinkholder    schedule 05.09.2014
comment
никогда не проходил через эту линию, но я хотел бы увидеть ур pseudocode, если вы можете показать нам. - person super cool; 05.09.2014
comment
Спасибо за ответ, Ганс. В основном вы инициализируете свой собственный вызов JSON. В моем сценарии URL-адрес /VoteAnswers/Details/1 ({controller}/{action}/{id} из файла конфигурации маршрута). Вы говорите, что просто вручную проанализируете URL-адрес, чтобы получить идентификатор для инициализации нокаутирующей модели представления? - person cmartin; 05.09.2014
comment
Это один из способов: проанализируйте URL-адрес во внешнем интерфейсе, чтобы вы знали, какой идентификатор отправить вместе с вашим запросом на получение данных. Из вашего кода кажется, что вы создаете SPA, и в этом случае обычно для вас уже выполняется анализ URL-адресов, поэтому может быть проще подобрать часть идентификатора. Используйте идентификатор для выполнения запроса на получение к конечной точке JSON, который возвращает модель, которая теперь является вашей моделью представления MVC, только преобразованную в JSON (в .NET есть множество преобразователей JSON). - person Hans Roerdinkholder; 05.09.2014

Это могут быть возможные решения

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

Если вы обнаружите, что чего-то еще не хватает, предоставьте подробную информацию.

person super cool    schedule 05.09.2014