提问



我在_Layout.cshtml中定义了这一部分


@RenderSection("Scripts", false)


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


@section Scripts { 
    @*Stuff comes here*@
}


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


我们假设这是我的观点页面:


@section Scripts { 

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

<div>
    poo bar poo
</div>

<div>
  @Html.Partial("_myPartial")
</div>


我需要从_myPartial局部视图的Scripts部分中注入一些内容。


我怎样才能做到这一点?

最佳参考


章节不适用于局部视图,而且是设计上的。您可以使用一些自定义帮助程序来实现类似的行为,但老实说,视图有责任包含必要的脚本,而不是部分责任。我建议使用主视图的@scripts部分来执行此操作,没有部分担心脚本。

其它参考1


这是一个非常受欢迎的问题,所以我会发布我的解决方案。

我有同样的问题,虽然它不是理想的,但我认为它实际上运作良好并且不会使部分依赖于视图。

我的情况是,一个动作本身可以访问,但也可以嵌入一个视图 - 谷歌地图。


在我_layout中,我有:


@RenderSection("body_scripts", false)


在我的index视图中,我有:


@Html.Partial("Clients")
@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}


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


@section body_scripts
{
    @Html.Partial("Clients_Scripts")
}


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


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


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

其它参考2


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


USAGE



创建部分




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


    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
        <script>
            someInlineScript();
        </script>
    }
    

  2. 在局部视图中,每次使用部分时都包含块:


    @using (Html.Delayed()) {
        <b>show me multiple times, @Model.Whatever</b>
    }
    

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



渲染部分



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


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


CODE



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


}

其它参考3


我遇到了这个问题并使用了这种技术。[56]


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


此外请投票以增加对累积部分声明的支持[57]

其它参考4


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

其它参考5


如果你确实有合法的需要从partial运行一些js,这里你可以这样做,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>

其它参考6


我们对Web的思考方式存在根本性缺陷,尤其是在使用MVC时。缺点是JavaScript在某种程度上是视图的责任。视图是视图,JavaScript(行为或其他)是JavaScript。在Silverlight和WPF的MVVM模式中,我们面对视图优先或模型优先在MVC中,我们应该总是试图从模型的角度来推理,JavaScript在很多方面都是这个模型的一部分。


我建议使用AMD模式(我自己喜欢RequireJS)。在模块中分离JavaScript,定义您的功能并从JavaScript挂钩到html,而不是依赖于视图来加载JavaScript。这将清理你的代码,分离你的顾虑,一举使生活更轻松。[59] [60]

其它参考7


我能想到的第一个解决方案是使用ViewBag来存储必须呈现的值。


我从未尝试过如果从局部视角开展这项工作,但它应该是imo。

其它参考8


有一种方法可以在局部视图中插入部分,但它并不漂亮。您需要从父视图访问两个变量。由于部分视图的一部分目的是创建该部分,因此它是有意义的要求这些变量。


这是在部分视图中插入一个部分的样子:


@model KeyValuePair<WebPageBase, HtmlHelper>
@{
    Model.Key.DefineSection("SectionNameGoesHere", () =>
    {
        Model.Value.ViewContext.Writer.Write("Test");
    });
}


并在插入局部视图的页面中...


@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))


您还可以使用此技术在任何类中以编程方式定义节的内容。


请享用!

其它参考9


您可以使用这些扩展方法 :(另存为PartialWithScript.cs)


namespace System.Web.Mvc.Html
{
    public static class PartialWithScript
    {
        public static void RenderPartialWithScript(this HtmlHelper htmlHelper, string partialViewName)
        {
            if (htmlHelper.ViewBag.ScriptPartials == null)
            {
                htmlHelper.ViewBag.ScriptPartials = new List<string>();
            }

            if (!htmlHelper.ViewBag.ScriptPartials.Contains(partialViewName))
            {
                htmlHelper.ViewBag.ScriptPartials.Add(partialViewName);
            }

            htmlHelper.ViewBag.ScriptPartialHtml = true;
            htmlHelper.RenderPartial(partialViewName);
        }

        public static void RenderPartialScripts(this HtmlHelper htmlHelper)
        {
            if (htmlHelper.ViewBag.ScriptPartials != null)
            {
                htmlHelper.ViewBag.ScriptPartialHtml = false;
                foreach (string partial in htmlHelper.ViewBag.ScriptPartials)
                {
                    htmlHelper.RenderPartial(partial);
                }
            }
        }
    }
}


像这样使用:


示例partial:(_ MyPartial.cshtml)
将html放在if中,将js放在else中。


@if (ViewBag.ScriptPartialHtml ?? true)
    <p>I has htmls</p>
}
else {
    <script type="text/javascript">
        alert('I has javascripts');
    </script>
}


在您的_Layout.cshtml中,或者您希望渲染部分脚本的地方,请添加以下内容(一次):它将仅呈现此位置当前页面上所有部分的javascript。


@{ Html.RenderPartialScripts(); }


然后使用你的部分,只需执行此操作:它将仅渲染此位置的html。


@{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");}

其它参考10


这对我有用,允许我在同一个文件中共同定位javascript和html以进行局部视图。帮助思考过程在同一局部视图文件中查看html和相关部分。





在View中使用名为_MyPartialView.cshtml的部分视图



<div>
    @Html.Partial("_MyPartialView",< model for partial view>,
            new ViewDataDictionary { { "Region", "HTMLSection" } } })
</div>

@section scripts{

    @Html.Partial("_MyPartialView",<model for partial view>, 
                  new ViewDataDictionary { { "Region", "ScriptSection" } })

 }





在部分视图文件



@model SomeType

@{
    var region = ViewData["Region"] as string;
}

@if (region == "HTMLSection")
{


}

@if (region == "ScriptSection")
{
        <script type="text/javascript">
    </script">
}

其它参考11


您不需要在局部视图中使用部分。


包含在您的部分视图中。
它在jQuery加载后执行该函数。
您可以为代码更改de condition子句。


<script type="text/javascript">    
var time = setInterval(function () {
    if (window.jQuery != undefined) {
        window.clearInterval(time);

        //Begin
        $(document).ready(function () {
           //....
        });
        //End
    };
}, 10); </script>


胡里奥斯派德

其它参考12


我有一个类似的问题,我有一个母版页如下:


@section Scripts {
<script>
    $(document).ready(function () {
        ...
    });
</script>
}

...

@Html.Partial("_Charts", Model)


但部分视图取决于Scripts部分中的一些JavaScript。我通过将部分视图编码为JSON,将其加载到JavaScript变量然后使用它来填充div来解决它,所以:


@{
    var partial = Html.Raw(Json.Encode(new { html = Html.Partial("_Charts", Model).ToString() }));
}

@section Scripts {
<script>
    $(document).ready(function () {
        ...
        var partial = @partial;
        $('#partial').html(partial.html);
    });
</script>
}

<div id="partial"></div>

其它参考13


您可以选择使用Folder/index.cshtml作为母版页,然后添加部分脚本。然后,在您的布局中,您有:


@RenderSection("scripts", required: false) 


和你的index.cshtml:


@section scripts{
     @Scripts.Render("~/Scripts/file.js")
}


它会影响你所有的偏见。它对我有用

其它参考14


冥王星的想法以更好的方式:


CustomWebViewPage.cs:


    public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> {

    public IHtmlString PartialWithScripts(string partialViewName, object model) {
        return Html.Partial(partialViewName: partialViewName, model: model, viewData: new ViewDataDictionary { ["view"] = this, ["html"] = Html });
    }

    public void RenderScriptsInBasePage(HelperResult scripts) {
        var parentView = ViewBag.view as WebPageBase;
        var parentHtml = ViewBag.html as HtmlHelper;
        parentView.DefineSection("scripts", () => {
            parentHtml.ViewContext.Writer.Write(scripts.ToHtmlString());
        });
    }
}


查看\\ web.config中:


<pages pageBaseType="Web.Helpers.CustomWebViewPage">


视图:


@PartialWithScripts("_BackendSearchForm")


部分(_BackendSearchForm.cshtml):


@{ RenderScriptsInBasePage(scripts()); }

@helper scripts() {
<script>
    //code will be rendered in a "scripts" section of the Layout page
</script>
}


布局页面:


@RenderSection("scripts", required: false)

其它参考15


使用Mvc Core,您可以创建一个整洁的TagHelper scripts,如下所示。这可以很容易地变成section标签,你也可以给它一个名字(或者名字来自派生类型)。请注意,需要为IHttpContextAccessor设置依赖注入。


添加脚本时(例如在部分中)


<scripts>
    <script type="text/javascript">
        //anything here
    </script>
</scripts>


输出脚本时(例如在布局文件中)


<scripts render="true"></scripts>


代码


public class ScriptsTagHelper : TagHelper
    {
        private static readonly object ITEMSKEY = new Object();

        private IDictionary<object, object> _items => _httpContextAccessor?.HttpContext?.Items;

        private IHttpContextAccessor _httpContextAccessor;

        public ScriptsTagHelper(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            var attribute = (TagHelperAttribute)null;
            context.AllAttributes.TryGetAttribute("render",out attribute);

            var render = false;

            if(attribute != null)
            {
                render = Convert.ToBoolean(attribute.Value.ToString());
            }

            if (render)
            {
                if (_items.ContainsKey(ITEMSKEY))
                {
                    var scripts = _items[ITEMSKEY] as List<HtmlString>;

                    var content = String.Concat(scripts);

                    output.Content.SetHtmlContent(content);
                }
            }
            else
            {
                List<HtmlString> list = null;

                if (!_items.ContainsKey(ITEMSKEY))
                {
                    list = new List<HtmlString>();
                    _items[ITEMSKEY] = list;
                }

                list = _items[ITEMSKEY] as List<HtmlString>;

                var content = await output.GetChildContentAsync();

                list.Add(new HtmlString(content.GetContent()));
            }
        }
    }

其它参考16


我刚刚在我的局部视图中添加了这个代码并解决了问题,虽然不是很干净,但它确实有效。您必须确保要渲染的对象的ID。



    $(document).ready(function(){
        $(#Profile_ProfileID)。selectmenu({icons:{button:ui-icon-circle-arrow-s}});
        $(#TitleID_FK)。selectmenu({icons:{button:ui-icon-circle-arrow-s}});
        $(#CityID_FK)。selectmenu({icons:{button:ui-icon-circle-arrow-s}});
        $(#GenderID_FK)。selectmenu({icons:{button:ui-icon-circle-arrow-s}});
        $(#PackageID_FK)。selectmenu({icons:{button:ui-icon-circle-arrow-s}});
    });

其它参考17


我解决了这个完全不同的路线(因为我匆忙而且不想实现新的HtmlHelper):


我将Partial View包装在一个很大的if-else语句中:


@if ((bool)ViewData["ShouldRenderScripts"] == true){
// Scripts
}else{
// Html
}


然后,我使用自定义ViewData两次调用Partial:


@Html.Partial("MyPartialView", Model, 
    new ViewDataDictionary { { "ShouldRenderScripts", false } })

@section scripts{
    @Html.Partial("MyPartialView", Model, 
        new ViewDataDictionary { { "ShouldRenderScripts", true } })
}

其它参考18


好吧,我猜其他海报为你提供了一种方法,可以直接在你的部分中包含一个@section(通过使用第三方html助手)。


但是,我认为,如果你的脚本与你的部分紧密耦合,只是将你的javascript直接放在你的部分内联<script>标签中并完成它(只需注意如果您打算在单个视图中多次使用部分,则脚本重复);

其它参考19


假设您有一个名为_contact.cshtml的部分视图,您的联系人可以是合法(名称)或物理主题(名字,姓氏)。你的视图应该关注渲染的内容以及javascript可以实现的内容。因此可能需要延迟渲染和JS内部视图。


我认为,如何将其省略的唯一方法是,当我们创建一种处理此类UI问题的不引人注目的方式时。


还要注意MVC 6会有一个所谓的View Component,甚至MVC期货都有类似的东西,Telerik也支持这样的东西......

其它参考20


我有类似的问题解决了这个问题:


@section ***{
@RenderSection("****", required: false)
}


这是一个很好的方式来注入我的猜测。