包括特定于ASP.NET MVC4视图或部分视图的脚本 [英] Including script specific to an ASP.NET MVC4 view or partial view

查看:62
本文介绍了包括特定于ASP.NET MVC4视图或部分视图的脚本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看了很多类似如何在MVC4的局部视图中添加脚本? MVC4部分视图javascript捆绑问题,当涉及特定于视图的脚本时,我仍然在努力理解ASP.NET MVC架构。对于试图在其MVC4部分视图中包含脚本的其他人而言,似乎是将脚本置于更高级别的答案。但是某些脚本无法移动到更高级别,它将在全局范围内运行更多。例如,我不想运行将knockout.js数据绑定应用于未加载控件的视图模型的脚本。而且我不想为每一次加载页面时都没有活动的一大堆视图运行一大堆脚本。

I've looked at a number of questions similar to How to add a script in a partial view in MVC4? and MVC4 partial view javascript bundling Issue and am still struggling to understand ASP.NET MVC architecture when it comes to view-specific script. It seems the answer to others who have tried to include script in their MVC4 partial views is to put the script at a higher level. But some script can't be moved to a higher level where it will run more globally. For example, I don't want to run script that applies knockout.js data bindings for a view model whose controls aren't loaded. And I don't want to run a whole bunch of script for a whole bunch of views that aren't active every time I load a page.

所以我开始使用我的 .vbhtml 视图中特定于视图的 @Section脚本块,以包含特定于视图的脚本。但是,正如其他人所指出的,这在局部视图中不起作用。我正在构建我们的架构原型,看看我们能做什么,不能做什么。我想在某些情况下,我可以将视图用作部分视图,反之亦然。但是当您拉入视图以用作部分视图时, @Section脚本块不会呈现。我已经设法以一种方式全局定义我的所有viewmodel脚本,这样我只需要运行一行代码来创建和绑定视图模型,但我仍然只需要在特定视图处于活动状态时运行一行代码。我在哪里可以在局部视图中适当添加这行代码?

So I started using the view-specific @Section Script blocks in my .vbhtml views to include script specific to a view. However, as pointed out by others, this does not work in a partial view. I am prototyping our architecture to see what we can and can't do here. I'd like to think that I might be able, in some cases, to use a view as a partial view and vice versa. But when you pull in a view to use as a partial view the @Section Script block does not render. I have managed to get all my viewmodel script defined globally in a way such that I need only run one line of code to create and bind a view model, but I still need that one line of code to run only when a particular view is active. Where can I appropriately add this line of code in a partial view?

ko.applyBindings(window.webui.inventoryDetailViewModel(ko, webui.inventorycontext));

我是否走在正确的道路上?这是构建MVC应用程序的正确方法吗?

Am I going down the right path here? Is this a proper way to architect an MVC application?

编辑发现这个问题与我的问题密切相关,并且包含了很大一部分我的回答:你能调用ko.applyBindings绑定部分视图吗?

Edit Found this question very closely related to my problem, and includes a significant part of my answer: Can you call ko.applyBindings to bind a partial view?

推荐答案

现有答案不够详细,所以请允许我提供详细的答案和代码。我大多遵循JotaBe的答案的建议,而这正是如何。

The existing answers weren't quite detailed enough, so allow me to provide a detailed answer with code. I mostly followed the suggestion of JotaBe's answer, and here's exactly how.

首先,我设计了一个方案,用于我将使用的自定义(数据)属性并创建一个帮助器以一种有助于我与ASP.Net捆绑兼容的方式应用它的功能。当打开捆绑优化( BundleTable.EnableOptimizations = True )时,该属性需要提供下载单个捆绑文件的必要信息,否则需要几个独立文件。您可以在下面代码的注释中看到我为数据模型属性确定的格式。此代码进入名为 Helpers.vbhtml 的文件,该文件已添加到我的主项目中的新文件夹 App_Code 。 / p>

App_Code / Helpers.vbhtml



First I devised a scheme for what custom ("data") attribute I would use and created a helper function to apply it in a way that would help me be compatible with ASP.Net bundling. The attribute needs to provide the necessary information to download a single bundle file when bundling optimizations are turned on (BundleTable.EnableOptimizations = True) and several independent files otherwise. You can see the format I settled on for a data-model attribute in the comments on the code below. This code went into a file called Helpers.vbhtml which was added to a new folder App_Code in my main project.

@*
    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 。在一个地方添加一个容器来容纳这个属性可能会更好,但我想证明它没有必要。

Then I can apply that attribute on a node like this to have it indicate how it wants knockout bindings applied to itself and its descendants and what scripts are needed before doing so. Notice how my intention is to be able to refer to the same script bundle and model from multiple nodes without duplicating the download or having duplicate instances of the model unless I specifically request separate instances of the model with forceNew. It would probably be better to add a container to house this attribute in a single place, but I want to demonstrate that it's not necessary.

<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文件,该文件始终在 _Layout.vbhtml 。它具有处理新数据模型属性所需的客户端代码。我们的想法是在这些特定节点上调用 ko.applyBindings ,并且只在一次实例化视图模型,除非在多个节点上显式请求模型的不同实例。

Finally I create a javascript file referenced in an existing bundle that's always pulled in in _Layout.vbhtml. It has the client side code necessary for processing the new "data-model" attribute. The idea is to call ko.applyBindings on these specific nodes, and to only instantiate the view model once unless distinct instances of the model are explicitly requested on multiple nodes.

// Make sure we have our namespace carved out, and we
// know we're going to put a scriptCache in it.
window.webui = window.webui || { "scriptCache": {} };

// Copied from http://stackoverflow.com/a/691661/78162
// jQuery's getScript uses a mechanism that is not debuggable
// when operating within the domain, so we use this code to
// make sure the code is always a debuggable part of the DOM.
window.webui.getScript = function (url, callback) {
    var head = document.getElementsByTagName("head")[0];
    var script = document.createElement("script");
    script.src = url;

    // Handle Script loading
    {
        var done = false;

        // Attach handlers for all browsers
        script.onload = script.onreadystatechange = function () {
            if (!done && (!this.readyState ||
                  this.readyState == "loaded" || this.readyState == "complete")) {
                done = true;
                if (callback)
                    callback();

                // Handle memory leak in IE
                script.onload = script.onreadystatechange = null;
            }
        };
    }
    head.appendChild(script);
    // We handle everything using the script element injection
    return undefined;
};

// Call knockout's applyBindings function based on values specified in the
// data-model attribute after the script is done downloading (which is the
// responsibility of the caller).
window.webui.applyBindings = function (cacheObj, forceNew, factory, context, node) {
    // Store instantiated view model objects for each factory in
    // window.webui.scriptCache[bundleName].models for reuse on other nodes.
    cacheObj.models = cacheObj.models || {};
    // If an instance of the model doesn't exist yet, create one by calling the
    // factory function, which should be implemented in a script in the
    // downloaded bundle somewhere. And the context object should have already
    // been instantiated when the script was downloaded.
    if (forceNew || !cacheObj.models[factory])
        cacheObj.models[factory] = window.webui[factory](ko, window.webui[context]);
    // Apply bindings only to the node where data-model attribute was applied
    ko.applyBindings(cacheObj.models[factory], node);
};

// Callback function when a script specified in the data-model attribute is
// done being downloaded on demand.
window.webui.onModelLoaded = function (cacheObj) {
    // Count how many scripts inteh bundle have finished downloading
    cacheObj.loadedCount += 1;
    // If we have downloaded all scripts in the bundle, call applyBindings
    // for all the nodes stored in the onComplete array.
    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);
        }
    }
};

// Process the data-model attribute of one HTML node by downloading the related bundle
// scripts if they haven't yet been downloaded and then calling applyBindings based on
// the values embedded in the attribute.
window.webui.require = function (modelAttribute, node) {
    model = $.parseJSON(modelAttribute);
    // Keep a cache of all the bundles that have been downloaded so we don't download the same
    // bundle more than once even if multiple nodes refer to it.
    window.webui.scriptCache = window.webui.scriptCache || {};
    // The cache is keyed by bundle name. All scripts in a bundle are downloaded before
    // any bindings are applied.
    if (!window.webui.scriptCache[model.bundle]) {
        // Store the expectd count and the loaded count so we know when the last
        // script in the bundle is done that it's time to apply the bindings.
        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 each script in the bundle, start the download, and pass in cacheObj
        // so the callback will know if it has downloaded the last script and what
        // to do when it has.
        for (var script in model.sources) {
            window.webui.getScript(model.sources[script], function () {
                window.webui.onModelLoaded(cacheObj)
            });
        }
    } else {
        // If the bundle referenced already has a space allocated in the cache, that means
        // its scripts are already downloaded or are in the process of being downloaded.
        var cacheObj = window.webui.scriptCache[model.bundle];
        if (cacheObj.totalCount == cacheObj.loadedCount) {
            // If the bundle is already completely downloadad, just apply the bindings directly
            window.webui.applyBindings(cacheObj, model.forceNew, model.factory, model.context, node);
        } else {
            // If the bundle is still being downloaded, add work to be done when bindings
            // are applied upon completion.
            window.webui.scriptCache[model.bundle].onComplete.push({
                "factory": model.factory, "context": model.context, "node": node, "forceNew": model.forceNew
            });
        }
    }
};

// When the document is done loading, locate every node with a data-model attribute
// and process the attribute value with the require function above on that node.
$(document).ready(function () {
    $('[data-model]').each(function () {
        var model = $(this).data("model");
        window.webui.require(model, this);
    });
});

使用此解决方案,我可以依赖现有的ASP.NET MVC4捆绑框架(我不需要r.js)优化和组合javascript文件,还要实现按需下载和一种不显眼的机制,用于定义脚本和查看与敲除绑定相关的模型。

With this solution, I can rely on the existing ASP.NET MVC4 bundling framework (I don't need r.js) to optimize and combine javascript files, but also implement download on demand and an unobstrusive mechanism for defining the scripts and view models related to knockout bindings.

这篇关于包括特定于ASP.NET MVC4视图或部分视图的脚本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆