Injecting content into specific sections from a partial view ASP.NET MVC 3 with Razor View Engine

后端 未结 24 1917
无人共我
无人共我 2020-11-22 06:13

I have this section defined in my _Layout.cshtml

@RenderSection(\"Scripts\", false)

I can easily use it from a view:

24条回答
  •  迷失自我
    2020-11-22 06:36

    From the solutions in this thread, I came up with the following probably overcomplicated solution that lets you delay rendering any html (scripts too) within a using block.

    USAGE

    Create the "section"

    1. Typical scenario: In a partial view, only include the block one time no matter how many times the partial view is repeated in the page:

      @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
          
      }
      
    2. In a partial view, include the block for every time the partial is used:

      @using (Html.Delayed()) {
          show me multiple times, @Model.Whatever
      }
      
    3. In a partial view, only include the block once no matter how many times the partial is repeated, but later render it specifically by name when-i-call-you:

      @using (Html.Delayed("when-i-call-you", isOnlyOne: "different unique name")) {
          show me once by name
          @Model.First().Value
      }
      

    Render the "sections"

    (i.e. display the delayed section in a parent view)

    @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 {
    
        /// 
        /// Delegate script/resource/etc injection until the end of the page
        /// @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/ 
        /// 
        private class DelayedInjectionBlock : IDisposable {
            /// 
            /// Unique internal storage key
            /// 
            private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";
    
            /// 
            /// Internal storage identifier for remembering unique/isOnlyOne items
            /// 
            private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;
    
            /// 
            /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
            /// 
            private const string EMPTY_IDENTIFIER = "";
    
            /// 
            /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
            /// 
            /// the helper from which we use the context
            /// optional unique sub-identifier for a given injection block
            /// list of delayed-execution callbacks to render internal content
            public static Queue GetQueue(HtmlHelper helper, string identifier = null) {
                return _GetOrSet(helper, new Queue(), identifier ?? EMPTY_IDENTIFIER);
            }
    
            /// 
            /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
            /// 
            /// the helper from which we use the context
            /// 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
            /// optional unique sub-identifier for a given injection block
            /// list of delayed-execution callbacks to render internal content
            private static T _GetOrSet(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));
            }
    
            /// 
            /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
            /// 
            /// 
            /// 
            public static Dictionary GetStorage(HtmlHelper helper) {
                var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary;
                if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary());
                return storage;
            }
    
    
            private readonly HtmlHelper helper;
            private readonly string identifier;
            private readonly string isOnlyOne;
    
            /// 
            /// Create a new using block from the given helper (used for trapping appropriate context)
            /// 
            /// the helper from which we use the context
            /// optional unique identifier to specify one or many injection blocks
            /// 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)
            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;
            }
    
            /// 
            /// Append the internal content to the context's cached list of output delegates
            /// 
            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(), 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)
                }
            }
        }
    
    
        /// 
        /// Start a delayed-execution block of output -- this will be rendered/printed on the next call to .
        /// 
        /// 
        /// Print once in "default block" (usually rendered at end via @Html.RenderDelayed()).  Code:
        /// 
        /// @using (Html.Delayed()) {
        ///     show at later
        ///     @Model.Name
        ///     etc
        /// }
        /// 
        /// 
        /// 
        /// 
        /// 
        /// Print once (i.e. if within a looped partial), using identified block via @Html.RenderDelayed("one-time").  Code:
        /// 
        /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
        ///     show me once
        ///     @Model.First().Value
        /// }
        /// 
        /// 
        /// 
        /// 
        /// the helper from which we use the context
        /// optional unique identifier to specify one or many injection blocks
        /// 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)
        /// using block to wrap delayed output
        public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
            return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
        }
    
        /// 
        /// Render all queued output blocks injected via .
        /// 
        /// 
        /// Print all delayed blocks using default identifier (i.e. not provided)
        /// 
        /// @using (Html.Delayed()) {
        ///     show me later
        ///     @Model.Name
        ///     etc
        /// }
        /// 
        /// -- then later --
        /// 
        /// @using (Html.Delayed()) {
        ///     more for later
        ///     etc
        /// }
        /// 
        /// -- then later --
        /// 
        /// @Html.RenderDelayed() // will print both delayed blocks
        /// 
        /// 
        /// 
        /// 
        /// 
        /// Allow multiple repetitions of rendered blocks, using same @Html.Delayed()... as before.  Code:
        /// 
        /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
        /// @Html.RenderDelayed() /* will print again because not removed before */
        /// 
        /// 
        /// 
    
        /// 
        /// the helper from which we use the context
        /// optional unique identifier to specify one or many injection blocks
        /// only render this once
        /// rendered output content
        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("", 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("", injectionBlockId) + 
    #endif
                string.Join(Environment.NewLine, stack));
        }
    
    
    }
    

提交回复
热议问题