Best practice on passing Mvc Model to KnockoutJS

2019-01-13 04:27发布

I googled around on how to pass mvc model to knockoutjs and it seems there are two ways:

  • Using @Html.Raw(Json.Encode(Model))
  • Using $.get or $.ajax

Which of the ways is a best practice of passing mvc model to knockoutjs? I know it is a per requirement basis but it seems using $.get is more cleaner versus the @Html.Raw method.

8条回答
一夜七次
2楼-- · 2019-01-13 05:06

I use @Html.Raw because there's nothing useful the user can do on the page until the Knockout UI is generated anyway, and any potential trivial time lag due to the extra initial download from writing the JSON into the page is offset by the user not seeing an unnerving delay before the UI is built. I don't have a problem at all with the JSON used to construct my initial view model being directly in my page.

Where I do make efficiency gains is by retrieving any reusable reference data for things like select options (lists of products etc) in a separate dynamic script download from an MVC controller, which then gets cached by the browser. That way they can be reused across view models and the view models only need to store the selected item value.

查看更多
ゆ 、 Hurt°
3楼-- · 2019-01-13 05:07

My approach:

  • view model is written in its own JS file with no server-side code generation
  • view model loads data via $.get or $.ajax
  • view passes in an object when it creates the view model, and this object contains all the server-side generated URLs

An example:

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

This means I have an extra AJAX call for the data, but IMO users are coming to realise that data != UI. The benefit is that my UI can be served out quickly because there is no real data access involved. The data load may take some time depending on the database, volume of data, etc, etc. It also gives my code a very clean separation of concerns.

查看更多
Viruses.
4楼-- · 2019-01-13 05:09

In my mind it is desirable to keep HTML clean and to avoid mixing in inline JavaScript and Data.

I prefer to inject data with an AJAX call to an endpoint.

查看更多
我只想做你的唯一
5楼-- · 2019-01-13 05:23

As you say, it's pretty much a per requirement basis.

If you're doing a "one page application", you're going to do a lot of $.get and $.ajax calls, if you're just rendering a single page, it might be quicker to put the model in the Html, saving an extra request to the server.

Also depends on how much code you want server-side, if my applications need an API anyway, I tend to reusing the API and do $.get and $.ajax calls, if not, I put the model in the Html.

查看更多
The star\"
6楼-- · 2019-01-13 05:23

Old question, but I think I have a nice tidy solution for passing model data to a KO view model, in a reusable fashion. Note, I'm using require.js as well.

Add the HtmlHelper extension method, in .NET:

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

Add the partial view, KnockoutJsBinderPartial (I've had to use 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>

The model referenced by that view is:

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

Now, to wire up your .NET model to your knockout view model, all you need to do is:

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

Note, the Javascript file containing your view model SHOULD NOT call ko.applyBindings and should return the Constructor function for the view model. The constructor should take one argument - your JSON data for the model.

查看更多
爷、活的狠高调
7楼-- · 2019-01-13 05:26

I've successfully used several approaches.

In a strongly typed Razor view, you can write the JavaScript ViewModel object like you would any other HTML, inserting the Model elements as you go. I find this klunky as Razor and JS don't play well together with Visual Studio and Intellisense, but even with a bunch of red squigglies the resulting code works fine.

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

This code can get ugly in a hurry depending on the complexity of your Model. As you mentioned, you can also serialize the model object into JSON using Html.Raw(). If you go that route, you can use it to build your Knockout ViewModel using the KO Mapping library:

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

This isn't as kludgy as the first option, but I feel like I'm giving up too much control this way. It means my KO ViewModel is pretty tightly coupled to the structure of my MVC ViewModel, which I may or may not want. Not to mention, for this to work my JavaScript needs to be in my cshtml page which I really don't like. Lastly, both of these approaches are purely server side. For more responsive web pages, like SPIs, you'll want to do more on the client side.

My preference is to use $.getJSON calls client side from the JavaScript itself. At that point, you can process the return data into your ViewModel, either hand rolling or using the mapping library. If you are calling back to actions in your MVC controller, just have the action return a JsonResult type (instead of an ActionResult). (You can also do similar stuff with ContentResult) If you can use the new MVC4 WebAPI, those controllers will return JSON by default.

查看更多
登录 后发表回答