可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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.
回答2:
@Html.Raw(Json.Encode(Model)) is used when you want the data to be sent as part of the actual page download. This could result in your page taking longer to be rendered to the user but when it does render it should be all ready to go.
$.get or $.ajax will instead get the data while the page is rendering. This creates a separate call which will result in your page updating after it's rendered.
As for which to use.. it depends on how your page is laid out, whether or not you have to have all data there to start with and how long it takes to get your data vs. render your page.
回答3:
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.
回答4:
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.
回答5:
What i do is a Html.Raw and then that js object i pass it to my knockout js model. Something like this:
//I'm using Newtonsoft library to serialize the objects
@{
var jsModel = Newtonsoft.Json.JsonConvert.SerializeObject(Model);
}
<script type="text/javascript">
var model = @Html.Raw(jsModel);
var vm = new MyKnockoutModel(model);
ko.applyBindings(vm);
var MyKnockoutModel = function(model) {
var self = this;
this.Id = ko.observable(model.Id);
this.Name = ko.observable(model.Name);
this.Array1 = ko.observableArray(model.Array1);
}
</script>
That's what i do.
回答6:
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.
回答7:
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.
回答8:
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.