GrayMP

Untitled

Nov 20th, 2017
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 9.23 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Web;
  7. using System.Web.Mvc;
  8.  
  9. namespace arkAS.Common.Helpers
  10. {
  11.     public static class HtmlRenderExtensions
  12.     {
  13.  
  14.         /// <summary>
  15.         /// Delegate script/resource/etc injection until the end of the page
  16.         /// <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>
  17.         /// </summary>
  18.         private class DelayedInjectionBlock : IDisposable
  19.         {
  20.             /// <summary>
  21.             /// Unique internal storage key
  22.             /// </summary>
  23.             private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";
  24.  
  25.             /// <summary>
  26.             /// Internal storage identifier for remembering unique/isOnlyOne items
  27.             /// </summary>
  28.             private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;
  29.  
  30.             /// <summary>
  31.             /// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
  32.             /// </summary>
  33.             private const string EMPTY_IDENTIFIER = "";
  34.  
  35.             /// <summary>
  36.             /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
  37.             /// </summary>
  38.             /// <param name="helper">the helper from which we use the context</param>
  39.             /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
  40.             /// <returns>list of delayed-execution callbacks to render internal content</returns>
  41.             public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null)
  42.             {
  43.                 return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
  44.             }
  45.  
  46.             /// <summary>
  47.             /// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
  48.             /// </summary>
  49.             /// <param name="helper">the helper from which we use the context</param>
  50.             /// <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>
  51.             /// <param name="identifier">optional unique sub-identifier for a given injection block</param>
  52.             /// <returns>list of delayed-execution callbacks to render internal content</returns>
  53.             private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class
  54.             {
  55.                 var storage = GetStorage(helper);
  56.  
  57.                 // return the stored item, or set it if it does not exist
  58.                 return (T)(storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
  59.             }
  60.  
  61.             /// <summary>
  62.             /// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
  63.             /// </summary>
  64.             /// <param name="helper"></param>
  65.             /// <returns></returns>
  66.             public static Dictionary<string, object> GetStorage(HtmlHelper helper)
  67.             {
  68.                 var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
  69.                 if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
  70.                 return storage;
  71.             }
  72.  
  73.  
  74.             private readonly HtmlHelper helper;
  75.             private readonly string identifier;
  76.             private readonly string isOnlyOne;
  77.  
  78.             /// <summary>
  79.             /// Create a new using block from the given helper (used for trapping appropriate context)
  80.             /// </summary>
  81.             /// <param name="helper">the helper from which we use the context</param>
  82.             /// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
  83.             /// <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>
  84.             public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null)
  85.             {
  86.                 this.helper = helper;
  87.  
  88.                 // start a new writing context
  89.                 ((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());
  90.  
  91.                 this.identifier = identifier ?? EMPTY_IDENTIFIER;
  92.                 this.isOnlyOne = isOnlyOne;
  93.             }
  94.  
  95.             /// <summary>
  96.             /// Append the internal content to the context's cached list of output delegates
  97.             /// </summary>
  98.             public void Dispose()
  99.             {
  100.                 // render the internal content of the injection block helper
  101.                 // make sure to pop from the stack rather than just render from the Writer
  102.                 // so it will remove it from regular rendering
  103.                 var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
  104.                 var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
  105.                 // if we only want one, remove the existing
  106.                 var queue = GetQueue(this.helper, this.identifier);
  107.  
  108.                 // get the index of the existing item from the alternate storage
  109.                 var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);
  110.  
  111.                 // only save the result if this isn't meant to be unique, or
  112.                 // if it's supposed to be unique and we haven't encountered this identifier before
  113.                 if (null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne))
  114.                 {
  115.                     // remove the new writing context we created for this block
  116.                     // and save the output to the queue for later
  117.                     queue.Enqueue(renderedContent);
  118.  
  119.                     // only remember this if supposed to
  120.                     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)
  121.                 }
  122.             }
  123.         }
  124.  
  125.  
  126.         /// <summary>
  127.         /// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
  128.         /// <para>
  129.         /// <example>
  130.         /// Print once in "default block" (usually rendered at end via <code>@Html.RenderDelayed()</code>).  Code:
  131.         /// <code>
  132.         /// @using (Html.Delayed()) {
  133.         ///     <b>show at later</b>
  134.         ///     <span>@Model.Name</span>
  135.         ///     etc
  136.         /// }
  137.         /// </code>
  138.         /// </example>
  139.         /// </para>
  140.         /// <para>
  141.         /// <example>
  142.         /// Print once (i.e. if within a looped partial), using identified block via <code>@Html.RenderDelayed("one-time")</code>.  Code:
  143.         /// <code>
  144.         /// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
  145.         ///     <b>show me once</b>
  146.         ///     <span>@Model.First().Value</span>
  147.         /// }
  148.         /// </code>
  149.         /// </example>
  150.         /// </para>
  151.         /// </summary>
  152.         /// <param name="helper">the helper from which we use the context</param>
  153.         /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
  154.         /// <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>
  155.         /// <returns>using block to wrap delayed output</returns>
  156.         public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null)
  157.         {
  158.             return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
  159.         }
  160.  
  161.         /// <summary>
  162.         /// Render all queued output blocks injected via <see cref="Delayed"/>.
  163.         /// <para>
  164.         /// <example>
  165.         /// Print all delayed blocks using default identifier (i.e. not provided)
  166.         /// <code>
  167.         /// @using (Html.Delayed()) {
  168.         ///     <b>show me later</b>
  169.         ///     <span>@Model.Name</span>
  170.         ///     etc
  171.         /// }
  172.         /// </code>
  173.         /// -- then later --
  174.         /// <code>
  175.         /// @using (Html.Delayed()) {
  176.         ///     <b>more for later</b>
  177.         ///     etc
  178.         /// }
  179.         /// </code>
  180.         /// -- then later --
  181.         /// <code>
  182.         /// @Html.RenderDelayed() // will print both delayed blocks
  183.         /// </code>
  184.         /// </example>
  185.         /// </para>
  186.         /// <para>
  187.         /// <example>
  188.         /// Allow multiple repetitions of rendered blocks, using same <code>@Html.Delayed()...</code> as before.  Code:
  189.         /// <code>
  190.         /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
  191.         /// @Html.RenderDelayed() /* will print again because not removed before */
  192.         /// </code>
  193.         /// </example>
  194.         /// </para>
  195.  
  196.         /// </summary>
  197.         /// <param name="helper">the helper from which we use the context</param>
  198.         /// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
  199.         /// <param name="removeAfterRendering">only render this once</param>
  200.         /// <returns>rendered output content</returns>
  201.         public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true)
  202.         {
  203.             var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);
  204.  
  205.             if (removeAfterRendering)
  206.             {
  207.                 var sb = new StringBuilder(
  208. #if DEBUG
  209.                 string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
  210. #endif
  211.                 );
  212.                 // .count faster than .any
  213.                 while (stack.Count > 0)
  214.                 {
  215.                     sb.AppendLine(stack.Dequeue());
  216.                 }
  217.                 return MvcHtmlString.Create(sb.ToString());
  218.             }
  219.  
  220.             return MvcHtmlString.Create(
  221. #if DEBUG
  222.                 string.Format("<!-- delayed-block: {0} -->", injectionBlockId) +
  223. #endif
  224.             string.Join(Environment.NewLine, stack));
  225.         }
  226.  
  227.  
  228.     }
  229. }
Advertisement
Add Comment
Please, Sign In to add comment