现有的答案不够详细,所以让我提供一份带有代码的详细答案。我大部分遵循了JotaBe的建议,并且以下是具体步骤。
首先,我设计了一个自定义(“data”)属性的方案,并创建了一个辅助函数来应用它,以便使我与ASP.Net捆绑兼容。当打开捆绑优化(BundleTable.EnableOptimizations = True)时,该属性需要提供下载单个捆绑文件所需的信息,否则需要提供多个独立文件。您可以在下面的代码注释中看到我为“data-model”属性确定的格式。这段代码被放入名为Helpers.vbhtml的文件中,并添加到我的主项目中的新文件夹App_Code中。
App_Code/Helpers.vbhtml
@*
Purpose: Retrieve a value for the WebUI-specific data-model attribute which will
apply knockout bindings for the current node based on the specified
bundle, factory, and context.
BundleNameUrl: Bundle URL like "~/bundles/inventory"
FactoryName: Client side factory class of the view model like "inventoryViewModel"
ContextName: Client side context object that provides methods for retrieving
and updating the data fromt he client, like "inventorycontext"
ForceNew: If True, a new instance of the view model will always be created;
If False, a previously created instance will be reused when possible.
Output: In debug mode, the escaped (") version of a string like
{"bundle": "~/bundles/inventory", "sources": ["/Scripts/app/inventory.datacontext.js",
"/Scripts/app/inventory.model.js","/Scripts/app/inventorydetail.viewmodel.js",
"/Scripts/app/inventory.viewmodel.js"], "factory": "inventoryViewModel",
"context": "inventorycontext", "forceNew": false}
Or in release mode, like
{"bundle": "~/bundles/inventory", "sources":
["/bundles/inventory?v=YaRZhEhGq-GkPEQDut6enckUI6FH663GEN4u2-0Lo1g1"],
"factory": "inventoryViewModel", "context": "inventorycontext", "forceNew": false}
*@
@Helper GetModel(BundleNameUrl As String, FactoryName As String, ContextName As String, Optional ForceNew As Boolean = False)
@Code
Dim result As New System.Text.StringBuilder()
result.Append("{""bundle"": """ & BundleNameUrl & """, ""sources"": [")
Dim httpCtx As New HttpContextWrapper(HttpContext.Current)
' When EnableOptimizations = True, there will be one script source URL per bundle
' When EnableOptimizations = False, each script in the bundle is delivered separately
If BundleTable.EnableOptimizations Then
result.Append("""" & System.Web.Mvc.UrlHelper.GenerateContentUrl( _
BundleResolver.Current.GetBundleUrl(BundleNameUrl), httpCtx) & """")
Else
Dim first As Boolean = True
For Each bundle In BundleResolver.Current.GetBundleContents(BundleNameUrl)
If first Then first = False Else result.Append(",")
result.Append("""" & System.Web.Mvc.UrlHelper.GenerateContentUrl(bundle, httpCtx) & """")
Next
End If
result.Append("], ""factory"": """ & FactoryName & """, ""context"": """ & ContextName & """")
result.Append(", ""forceNew"": " & If(ForceNew, "true", "false") & "}")
End Code
@<text>@result.ToString()</text>
End Helper
然后,我可以像这样在节点上应用该属性,以指示它希望如何将knockout绑定应用于自身及其后代,并在执行之前需要哪些脚本。请注意,我的意图是能够在多个节点中引用相同的脚本包和模型,而无需重复下载或具有除非使用
forceNew
明确请求单独的模型实例,否则不会出现重复实例。最好将一个容器添加到一个单一位置来存放此属性,但我想演示这并非必要。
Views/Inventory/Details.html
<a href="#" data-bind="click: loadPrevious" data-model="@Helpers.GetModel("~/bundles/inventory", "inventoryDetailViewModel", "inventorycontext")" title="Previous">Previous</a>
<a href="#" data-bind="click: loadNext" data-model="@Helpers.GetModel("~/bundles/inventory", "inventoryDetailViewModel", "inventorycontext")" title="Next">Next</a>
<fieldset data-bind="with: fsItem" data-model="@Helpers.GetModel("~/bundles/inventory", "inventoryDetailViewModel", "inventorycontext")">
最后,我创建了一个JavaScript文件,它被引用在现有的bundle中,在
_Layout.vbhtml
中总是被加载。它包含了处理新的"data-model"属性所需的客户端代码。这个想法是在这些特定的节点上调用
ko.applyBindings
,并且只实例化视图模型一次,除非明确请求多个节点的不同实例。
Scripts/app/webui.main.js
window.webui = window.webui || { "scriptCache": {} };
window.webui.getScript = function (url, callback) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = url;
{
var done = false;
script.onload = script.onreadystatechange = function () {
if (!done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete")) {
done = true;
if (callback)
callback();
script.onload = script.onreadystatechange = null;
}
};
}
head.appendChild(script);
return undefined;
};
window.webui.applyBindings = function (cacheObj, forceNew, factory, context, node) {
cacheObj.models = cacheObj.models || {};
if (forceNew || !cacheObj.models[factory])
cacheObj.models[factory] = window.webui[factory](ko, window.webui[context]);
ko.applyBindings(cacheObj.models[factory], node);
};
window.webui.onModelLoaded = function (cacheObj) {
cacheObj.loadedCount += 1;
if (cacheObj.loadedCount == cacheObj.totalCount) {
for (var callback in cacheObj.onComplete) {
var onComplete = cacheObj.onComplete[callback];
window.webui.applyBindings(cacheObj, onComplete.forceNew,
onComplete.factory, onComplete.context, onComplete.node);
}
}
};
window.webui.require = function (modelAttribute, node) {
model = $.parseJSON(modelAttribute);
window.webui.scriptCache = window.webui.scriptCache || {};
if (!window.webui.scriptCache[model.bundle]) {
var cacheObj = {
totalCount: model.sources.length, loadedCount: 0, onComplete:
[{ "factory": model.factory, "context": model.context, "node": node, "forceNew": model.forceNew }]
};
window.webui.scriptCache[model.bundle] = cacheObj;
for (var script in model.sources) {
window.webui.getScript(model.sources[script], function () {
window.webui.onModelLoaded(cacheObj)
});
}
} else {
var cacheObj = window.webui.scriptCache[model.bundle];
if (cacheObj.totalCount == cacheObj.loadedCount) {
window.webui.applyBindings(cacheObj, model.forceNew, model.factory, model.context, node);
} else {
window.webui.scriptCache[model.bundle].onComplete.push({
"factory": model.factory, "context": model.context, "node": node, "forceNew": model.forceNew
});
}
}
};
$(document).ready(function () {
$('[data-model]').each(function () {
var model = $(this).data("model");
window.webui.require(model, this);
});
});
通过这个解决方案,我可以依赖现有的ASP.NET MVC4捆绑框架(无需r.js)来优化和合并JavaScript文件,同时实现按需下载以及一个不显眼的机制来定义与knockout绑定相关的脚本和视图模型。
@Html.Partial
(Razor语法)函数将视图作为部分视图包含在其父级中。这是一个.NET/MVC框架函数,我更喜欢不使用自定义函数来混淆我的代码,因为已经有了标准。 - BlueMonkMN