使用Razor View Engine从部分视图ASP.NET MVC 3中将内容注入特定部分

tug*_*erk 316 asp.net asp.net-mvc partial-views razor asp.net-mvc-3

我在本节中定义了此部分 _Layout.cshtml

@RenderSection("Scripts", false)
Run Code Online (Sandbox Code Playgroud)

我可以从视图中轻松使用它:

@section Scripts { 
    @*Stuff comes here*@
}
Run Code Online (Sandbox Code Playgroud)

我正在努力的是如何从局部视图中获取本节内注入的一些内容.

我们假设这是我的视图页面:

@section Scripts { 

    <script>
        //code comes here
    </script>
}

<div>
    poo bar poo
</div>

<div>
  @Html.Partial("_myPartial")
</div>
Run Code Online (Sandbox Code Playgroud)

我需要Scripts_myPartial局部视图中注入部分内容.

我怎样才能做到这一点?

Dar*_*rov 229

部分在部分视图中不起作用,这是设计的.您可以使用一些自定义帮助程序来实现类似的行为,但老实说,视图的责任是包含必要的脚本,而不是部分责任.我建议使用主视图的@scripts部分来做到这一点,而不要让部分人担心脚本.

  • 但是如果脚本非常特定于部分呢?在部分而不是视图中定义它是否合乎逻辑? (418认同)
  • @Darin:我不同意.DRY原则怎么样?我不想重复自己,即使它只是脚本引用. (54认同)
  • 它为什么设计? (39认同)
  • 借调@JoshNoe和其他 - 一个"小部件"(显示+丰富的交互)是一个与关联的javascript紧密耦合的局部视图的完美示例._By design_我不应该在不同的地方编写两个include语句来获得完整的功能,因为显示将永远不会没有伴随的交互,并且交互将永远不会出现在其他地方. (32认同)
  • @fretje,每个人都有权就这个话题发表意见.我尊重你的.在我的回答中,我已经表达了我的意见,并与一个可以让你完成这项任务的答案相关联.但我也强调了我会为这种情况推荐和做的事情. (14认同)
  • 这对我来说完全没有意义.当然,部分视图会并且应该具有相关的逻辑,因此具有特定于它们的javascript文件,而不是它们的父视图. (13认同)
  • @Darin:当然可以!我只是指出(对你和未来的读者)用你的方法违反DRY原则......并不意味着我不尊重你;-) (7认同)
  • @Murali,不,你不能在局部内部使用部分.这不受支持.你没看过我的回答吗?如果你没有,请改写一下:`部分在部分视图中不起作用,这是设计的. (4认同)
  • @Jordan它不仅仅是关于脚本引用.它是一个依赖于视图的脚本,如`@section ScriptSection {$("#@ Html.IdFor(m => m.SomeProperty)").doJavaScript(); }`.我想在我的"主"视图的ScriptSection中注入这些部分. (4认同)
  • 唯一的例外如下.如果JavaScript脚本包含Razor代码,则无法将其移出cshtml文件,因为它必须与视图文件(cshtml,vbhtml)相关联.同时,你不想重复自己(DRY校长),所以你宁愿把它作为参考包括在内.我建议将脚本和只有SCRIPT放在它自己的cshtml文件中,然后让它在视图的@scripts部分中呈现,就像你引用一个脚本包一样.这样,您可以在任何视图上注入脚本,在布局的脚本部分中呈现它,并保留Razor功能. (4认同)
  • 部分用于隔离通用视图代码,其中可能包括特定于部分的脚本。这个参数没有任何意义,因此我需要知道在我使用Partial时可能需要的任何脚本引用吗?还是您想说部分使用脚本是错误的?这种答复不会助长所出现的问题,而只会暴露出不符合实际情况的任意规则。 (4认同)
  • @fretje,DRY是一个了不起的原则,但它经常被滥用.DRY允许代码的可维护性,但如果过度使用,实际上可以减少它.在一个中心位置具有脚本引用似乎很好,但这意味着即使不需要这些脚本也会下载.达林,我知道这是你的拙见,但我从不喜欢强迫人们以特定的方式编码.说完全没必要是一个广泛的声明,我相信也有例外.我尊重你们所有人! (3认同)
  • @CodeCaster,我知道.我想要同样的事情.我同意你的看法.部分应允许切片注射. (3认同)
  • 通常,您不应该将脚本放在partials中.如果partial表示可以在单个文档中多次呈现的Web控件,则您将拥有重复的脚本.更重要的是,文档中间的脚本将阻止解析,直到脚本完成,这将最终延迟文档就绪事件的执行.最好为每个页面定义一个脚本包(只有一个),它将所有外部脚本捆绑到一个压缩文件中. (3认同)
  • @Shimmy,因为恕我直言,这是一个不必要的功能.无论如何,脚本在部分视图中无关.它们属于单独的javascript文件.托管这些部分的父视图负责注册所有必需的脚本. (2认同)
  • 请查看https://www.nuget.org/packages/Forloop.HtmlHelpers/和相关文档https://bitbucket.org/forloop/forloop-htmlhelpers.对于你所追求的东西,听起来就像一个好的冷杉 (2认同)
  • 这不是设计使然。如果设计该方法不起作用,则会引发异常(可能在呈现网页时看到)。有意的结果应始终来自有意的/“通过设计”决定。作为缺少功能的“最小可行产品”,您可能会接受。但这不是设计使然。当作为功能被请求时,应将其实现为成功或失败。它不应被忽略并且被认为是预期的。恕我直言 (2认同)
  • 在生成请求结果时,可能会多次包含一个部分页面,因此可能导致多次包含同一脚本节。我想这就是为什么渲染部分不可用的原因,但是如果他们花一些时间来解决这个问题,那就更受欢迎了。部分页面很可能仅在使用时才依赖脚本。 (2认同)

dan*_*son 82

这是一个非常受欢迎的问题,所以我会发布我的解决方案.
我有同样的问题,虽然它不理想,我认为它实际上运作良好,并没有使部分依赖于视图.
我的情况是,一个动作本身可以访问,但也可以嵌入到视图中 - 谷歌地图.

在我的_layout身上:

@RenderSection("body_scripts", false)
Run Code Online (Sandbox Code Playgroud)

在我index看来,我有:

@Html.Partial("Clients")
@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}
Run Code Online (Sandbox Code Playgroud)

在我clients看来,我有(所有的地图和assoc .html):

@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}
Run Code Online (Sandbox Code Playgroud)

我的Clients_Scripts视图包含要在页面上呈现的javascript

这样我的脚本就被隔离了,并且可以在需要的地方呈现到页面中,body_scripts标签只在剃刀视图引擎找到它的第一次出现时呈现.

这让我可以把所有东西分开 - 这是一个对我来说很有效的解决方案,其他人可能会遇到问题,但它确实修补了"按设计"的漏洞.

  • 其他20个人和我有不同的看法.您仍然可以将脚本直接与位于单独文件中的视图相关联,如果您不将脚本与视图一起包含,则会出现编程错误.将它放在一个单独的文件中将交互与表示分开,并允许它在一个单独的文件中带来丰富的其他好处. (3认同)
  • 我并不是拒绝您投票的人,但是我会说我不太喜欢这种解决方案,因为它仍然将特定于视图的脚本与视图本身分开。 (2认同)
  • 该解决方案还有一个额外的好处,即仍然能够执行您希望在典型视图中执行的所有 MVC 风格的操作,例如能够对传入的模型进行 JSON 编码并使用 Url 生成 URL。行动。这种方法是设置 AngularJS 控制器的一种优雅方式 - 每个部分视图都可以代表 Angular 模块中的一个单独的控制器。好干净! (2认同)

drz*_*aus 39

这个线程的解决方案中,我提出了以下可能过于复杂的解决方案,它允许您延迟在使用块中呈现任何html(脚本).

用法

创建"部分"

  1. 典型场景:在局部视图中,无论页面中重复部分视图多少次,都只包括一次块:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 在局部视图中,每次使用部分时都包括块:

    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在局部视图中,无论部分重复多少次,都只包括块一次,但稍后通过名称具体呈现它when-i-call-you:

    @using (Html.Delayed("when-i-call-you", isOnlyOne: "different unique name")) {
        <b>show me once by name</b>
        <span>@Model.First().Value</span>
    }
    
    Run Code Online (Sandbox Code Playgroud)

渲染"部分"

(即在父视图中显示延迟部分)

@Html.RenderDelayed(); // writes unnamed sections (#1 and #2, excluding #3)
@Html.RenderDelayed("when-i-call-you", false); // writes the specified block, and ignore the `isOnlyOne` setting so we can dump it again
@Html.RenderDelayed("when-i-call-you"); // render the specified block by name
@Html.RenderDelayed("when-i-call-you"); // since it was "popped" in the last call, won't render anything due to `isOnlyOne` provided in `Html.Delayed`
Run Code Online (Sandbox Code Playgroud)

public static class HtmlRenderExtensions {

    /// <summary>
    /// Delegate script/resource/etc injection until the end of the page
    /// <para>@via https://stackoverflow.com/a/14127332/1037948 and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ </para>
    /// </summary>
    private class DelayedInjectionBlock : IDisposable {
        /// <summary>
        /// Unique internal storage key
        /// </summary>
        private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";

        /// <summary>
        /// Internal storage identifier for remembering unique/isOnlyOne items
        /// </summary>
        private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;

        /// <summary>
        /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
        /// </summary>
        private const string EMPTY_IDENTIFIER = "";

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null) {
            return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
        }

        /// <summary>
        /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="defaultValue">the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value</param>
        /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
        /// <returns>list of delayed-execution callbacks to render internal content</returns>
        private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
            var storage = GetStorage(helper);

            // return the stored item, or set it if it does not exist
            return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
        }

        /// <summary>
        /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
        /// </summary>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetStorage(HtmlHelper helper) {
            var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
            if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
            return storage;
        }


        private readonly HtmlHelper helper;
        private readonly string identifier;
        private readonly string isOnlyOne;

        /// <summary>
        /// Create a new using block from the given helper (used for trapping appropriate context)
        /// </summary>
        /// <param name="helper">the helper from which we use the context</param>
        /// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
        /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
        public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
            this.helper = helper;

            // start a new writing context
            ((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());

            this.identifier = identifier ?? EMPTY_IDENTIFIER;
            this.isOnlyOne = isOnlyOne;
        }

        /// <summary>
        /// Append the internal content to the context's cached list of output delegates
        /// </summary>
        public void Dispose() {
            // render the internal content of the injection block helper
            // make sure to pop from the stack rather than just render from the Writer
            // so it will remove it from regular rendering
            var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
            var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
            // if we only want one, remove the existing
            var queue = GetQueue(this.helper, this.identifier);

            // get the index of the existing item from the alternate storage
            var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);

            // only save the result if this isn't meant to be unique, or
            // if it's supposed to be unique and we haven't encountered this identifier before
            if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
                // remove the new writing context we created for this block
                // and save the output to the queue for later
                queue.Enqueue(renderedContent);

                // only remember this if supposed to
                if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
            }
        }
    }


    /// <summary>
    /// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
    /// <para>
    /// <example>
    /// Print once in "default block" (usually rendered at end via <code>@Html.RenderDelayed()</code>).  Code:
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show at later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Print once (i.e. if within a looped partial), using identified block via <code>@Html.RenderDelayed("one-time")</code>.  Code:
    /// <code>
    /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
    ///     <b>show me once</b>
    ///     <span>@Model.First().Value</span>
    /// }
    /// </code>
    /// </example>
    /// </para>
    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
    /// <returns>using block to wrap delayed output</returns>
    public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
        return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
    }

    /// <summary>
    /// Render all queued output blocks injected via <see cref="Delayed"/>.
    /// <para>
    /// <example>
    /// Print all delayed blocks using default identifier (i.e. not provided)
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>show me later</b>
    ///     <span>@Model.Name</span>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @using (Html.Delayed()) {
    ///     <b>more for later</b>
    ///     etc
    /// }
    /// </code>
    /// -- then later --
    /// <code>
    /// @Html.RenderDelayed() // will print both delayed blocks
    /// </code>
    /// </example>
    /// </para>
    /// <para>
    /// <example>
    /// Allow multiple repetitions of rendered blocks, using same <code>@Html.Delayed()...</code> as before.  Code:
    /// <code>
    /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
    /// @Html.RenderDelayed() /* will print again because not removed before */
    /// </code>
    /// </example>
    /// </para>

    /// </summary>
    /// <param name="helper">the helper from which we use the context</param>
    /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
    /// <param name="removeAfterRendering">only render this once</param>
    /// <returns>rendered output content</returns>
    public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
        var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);

        if( removeAfterRendering ) {
            var sb = new StringBuilder(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
#endif
                );
            // .count faster than .any
            while (stack.Count > 0) {
                sb.AppendLine(stack.Dequeue());
            }
            return MvcHtmlString.Create(sb.ToString());
        } 

        return MvcHtmlString.Create(
#if DEBUG
                string.Format("<!-- delayed-block: {0} -->", injectionBlockId) + 
#endif
            string.Join(Environment.NewLine, stack));
    }


}
Run Code Online (Sandbox Code Playgroud)


iBo*_*Boy 17

我有这个问题并使用了这种技术.

它是我发现的最佳解决方案,非常灵活.

此外, 在此投票以添加对累积部分声明的支持


Ser*_*gan 9

如果你确实有合法的需要js从a 运行一些partial,这是你可以做到的,这jQuery是必需的:

<script type="text/javascript">        
    function scriptToExecute()
    {
        //The script you want to execute when page is ready.           
    }

    function runWhenReady()
    {
        if (window.$)
            scriptToExecute();                                   
        else
            setTimeout(runWhenReady, 100);
    }
    runWhenReady();
</script>
Run Code Online (Sandbox Code Playgroud)

  • 你知道`setTimeout(SeeIfReady,100)`也适用,不需要隐式评估... (4认同)

arc*_*hil 8

遵循不引人注目的原则,"_ myPartial"并不需要将内容直接注入脚本部分.您可以将这些部分视图脚本添加到单独的.js文件中,并从父视图引用它们到@scripts部分.

  • 如果部分视图根本没有在页面中呈现,会发生什么?我们是否仍然在父级中引用这些.js文件并使其超载? (10认同)

ala*_*ans 7

OP 的目标是他想将内联脚本定义到他的部分视图中,我假设该脚本仅特定于该部分视图,并将该块包含在他的脚本部分中。

我知道他想要让部分视图自包含。这个想法类似于使用 Angular 时的组件。

我的方法是将脚本按原样保留在部分视图中。现在的问题是在调用 Partial View 时,它可能会在所有其他脚本(通常添加到布局页面的底部)之前在那里执行脚本。在这种情况下,您只需让部分视图脚本等待其他脚本。有几种方法可以做到这一点。我以前使用过的最简单的方法是在body

在我的布局上,我会在底部有这样的东西:

// global scripts
<script src="js/jquery.min.js"></script>
// view scripts
@RenderSection("scripts", false)
// then finally trigger partial view scripts
<script>
  (function(){
    document.querySelector('body').dispatchEvent(new Event('scriptsLoaded'));
  })();
</script>
Run Code Online (Sandbox Code Playgroud)

然后在我的部分视图(在底部):

<script>
  (function(){
    document.querySelector('body').addEventListener('scriptsLoaded', function() {

      // .. do your thing here

    });
  })();
</script>
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是使用堆栈推送所有脚本,并在最后调用每个脚本。如前所述,其他解决方案是 RequireJS/AMD 模式,它也非常有效。


Mr.*_*din 6

我们对Web的思考方式存在一个根本缺陷,尤其是在使用MVC时。缺陷在于,JavaScript是视图的责任。视图是视图,JavaScript(行为或其他)是JavaScript。在Silverlight和WPF的MVVM模式中,我们面临的是“视图优先”或“模型优先”。在MVC中,我们应始终尝试从模型的角度进行推理,而JavaScript在许多方面都是该模型的一部分。

我建议使用AMD模式(我自己喜欢RequireJS)。将模块中的JavaScript分开,定义功能并从JavaScript插入html,而不是依赖于视图来加载JavaScript。这将清理您的代码,分离您的关注点,并使生活更加轻松。

  • JavaScript也可以是View的责任。 (6认同)