[asp.net] Razor View Engine을 사용하여 부분보기 ASP.NET MVC 3에서 특정 섹션으로 컨텐츠 주입

이 섹션을 정의했습니다 _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>

Scripts섹션 내부에 내용을 삽입해야합니다._myPartial부분보기 .

어떻게해야합니까?



답변

섹션은 부분보기에서 작동하지 않으며 의도적으로 설계된 것입니다. 일부 사용자 지정 도우미 를 사용 하여 유사한 동작을 수행 할 수 있지만 정직하게는 부분 스크립트의 책임이 아니라 필요한 스크립트를 포함시키는 것이 뷰의 책임입니다. 메인 뷰의 @scripts 섹션을 사용하여 그렇게하고 부분에 대해서는 스크립트에 대해 걱정하지 않는 것이 좋습니다.


답변

이것은 매우 인기있는 질문이므로 솔루션을 게시하겠습니다.
나는 같은 문제가 있었지만 이상적이지는 않지만 실제로는 잘 작동하며 부분에 따라 뷰에 의존하지 않는다고 생각합니다.
내 시나리오는 작업 자체로 액세스 할 수 있지만 Google지도 인보기에 포함될 수 있다는 것입니다.

내 안에는 _layout:

@RenderSection("body_scripts", false)

index견해로는 다음과 같습니다.

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

clients견해로는 (모든지도와 관련 HTML)이 있습니다.

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

나의 Clients_Scripts 보기에는 페이지에 렌더링 될 자바 스크립트가 포함되어 있습니다.

이렇게하면 내 스크립트가 격리되어 필요한 경우 페이지에서 렌더링 될 수 있습니다. body_scripts , 면도기보기 엔진이 찾은 첫 번째 항목에서만 태그가 렌더링됩니다.

그것은 나에게 모든 것을 분리시킬 수있게한다.-그것은 나에게 아주 잘 작동하는 솔루션이고, 다른 사람들은 그것에 문제가있을 수 있지만, “디자인에 의한”구멍을 패치한다.


답변

이 스레드 의 솔루션에서 using 블록 내에서 html (스크립트도) 렌더링을 지연시킬 수있는 다음과 같은 복잡한 솔루션을 생각해 냈습니다.

용법

“섹션”만들기

  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`

암호

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


}


답변

나는이 문제가 있었고이 기술을 사용 했다 .

내가 찾은 최고의 솔루션은 매우 유연합니다.

또한 누적 섹션 선언에 대한 지원을 추가하려면 여기투표 하십시오.


답변

당신이 어떤 실행하는 합법적 인 필요성이있는 경우 jsA로부터를 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>


답변

눈에 거슬리지 않는 원칙에 따라 “_myPartial”이 내용을 스크립트 섹션에 직접 주입 할 필요는 없습니다. 부분 뷰 스크립트를 별도의 .js파일에 추가하고 상위 뷰에서 @scripts 섹션으로 참조 할 수 있습니다.


답변

특히 MVC를 사용할 때 웹에 대해 생각하는 방식에는 근본적인 결함이 있습니다. 결함은 JavaScript가 어떻게 든 뷰의 책임이라는 점입니다. 보기는보기이며 JavaScript (동작 또는 기타)는 JavaScript입니다. Silverlight와 WPF의 MVVM 패턴에서 우리는 “먼저보기”또는 “모델 우선”에 직면합니다. MVC에서는 항상 모델의 관점에서 추론을 시도해야하며 JavaScript는 여러 가지면에서이 모델의 일부입니다.

AMD 패턴을 사용하는 것이 좋습니다 ( RequireJS 좋아 합니다 ). JavaScript를 모듈로 분리하고, 기능을 정의하고, JavaScript를로드하기 위해 뷰에 의존하지 않고 JavaScript에서 HTML로 연결하십시오. 이렇게하면 코드를 정리하고 걱정을 분리하며 한 번에 모두 쉽게 생활 할 수 있습니다.