Injecting content into specific sections from a partial view ASP.NET MVC 3 with Razor View Engine
我在我的
1 | @RenderSection("Scripts", false) |
我可以从一个角度很容易地使用它:
1 2 3 | @section Scripts { @*Stuff comes here*@ } |
我正在努力解决的是如何从局部视图将一些内容注入到这个部分中。
假设这是我的视图页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @section Scripts { //code comes here } poo bar poo @Html.Partial("_myPartial") |
我需要在
我该怎么做?
部分视图不工作,这是按设计的。您可以使用一些自定义助手来实现类似的行为,但老实说,视图的职责是包括必要的脚本,而不是部分的职责。我建议使用主视图的@scripts部分来实现这一点,而不要让这些部分担心脚本。
这是一个很受欢迎的问题,所以我会发布我的解决方案。我也遇到过同样的问题,虽然这并不理想,但我认为它实际上工作得很好,并且不会使部分依赖于这个观点。我的设想是,一个操作本身是可以访问的,但也可以嵌入到一个视图中——一个谷歌地图。
我有:
1 | @RenderSection("body_scripts", false) |
在我看来,我有:
1 2 3 4 5 | @Html.Partial("Clients") @section body_scripts { @Html.Partial("Clients_Scripts") } |
在我的
1 2 3 4 | @section body_scripts { @Html.Partial("Clients_Scripts") } |
我的
这样,我的脚本被隔离,并且可以在需要时呈现到页面中,只有在Razor View引擎发现它的第一次出现时才会呈现
这让我把所有的东西都分开了——这是一个对我非常有效的解决方案,其他人可能会对此有问题,但它确实修补了"按设计"的漏洞。
从这个线程中的解决方案中,我提出了以下可能过于复杂的解决方案,它允许您延迟在使用块中呈现任何HTML(脚本)。
用法创建"分区"典型场景:在部分视图中,无论部分视图在页面中重复多少次,都只包含一次块:
1 2 3 4 5 | @using (Html.Delayed(isOnlyOne:"some unique name for this section")) { someInlineScript(); } |
在分部视图中,每次使用分部时都包含该块:
1 2 3 | @using (Html.Delayed()) { show me multiple times, @Model.Whatever } |
在局部视图中,无论部分重复多少次,都只包含一次块,但稍后将以名称
1 2 3 4 | @using (Html.Delayed("when-i-call-you", isOnlyOne:"different unique name")) { show me once by name <span>@Model.First().Value</span> } |
渲染"部分"
(即在父视图中显示延迟的部分)
1 2 3 4 | @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` |
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | 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 <wyn>@Html.RenderDelayed()</wyn>). Code: /// <wyn> /// @using (Html.Delayed()) { /// show at later /// <span>@Model.Name</span> /// etc /// } /// </wyn> /// </example> /// </para> /// <para> /// <example> /// Print once (i.e. if within a looped partial), using identified block via <wyn>@Html.RenderDelayed("one-time")</wyn>. Code: /// <wyn> /// @using (Html.Delayed("one-time", isOnlyOne:"one-time")) { /// show me once /// <span>@Model.First().Value</span> /// } /// </wyn> /// </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) /// <wyn> /// @using (Html.Delayed()) { /// show me later /// <span>@Model.Name</span> /// etc /// } /// </wyn> /// -- then later -- /// <wyn> /// @using (Html.Delayed()) { /// more for later /// etc /// } /// </wyn> /// -- then later -- /// <wyn> /// @Html.RenderDelayed() // will print both delayed blocks /// </wyn> /// </example> /// </para> /// <para> /// <example> /// Allow multiple repetitions of rendered blocks, using same <wyn>@Html.Delayed()...</wyn> as before. Code: /// <wyn> /// @Html.RenderDelayed(removeAfterRendering: false); /* will print */ /// @Html.RenderDelayed() /* will print again because not removed before */ /// </wyn> /// </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)); } } |
我遇到了这个问题,使用了这个技巧。
这是我发现的最好的解决方案,非常灵活。
另外,请在此处投票以增加对累积部分声明的支持
如果您确实有合法的需要从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <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(); |
遵循不引人注目的原则,"mypartial"不需要将内容直接插入脚本部分。您可以将这些部分视图脚本添加到单独的
我们对Web的思考方式存在一个根本缺陷,特别是在使用MVC时。这个缺陷是javascript不知何故是视图的责任。视图是视图,javascript(行为或其他)是javascript。在Silverlight和WPF的MVVM模式中,我们面临的是"视图优先"或"模型优先"。在MVC中,我们应该总是从模型的角度去思考,而javascript在很多方面都是这个模型的一部分。
我建议使用AMD模式(我自己喜欢RequireJS)。在模块中分离您的javascript,定义您的功能,并从javascript中钩住HTML,而不是依赖视图来加载javascript。这将清理你的代码,分离你的顾虑,让生活更容易在一下子。
您可以使用这些扩展方法:(另存为partialwithscript.cs)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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); } } } } } |
这样使用:
示例部分:(mypartial.cshtml)把HTML放在if中,把JS放在else中。
1 2 3 4 5 6 7 8 9 10 | @if (ViewBag.ScriptPartialHtml ?? true) <p> I has htmls </p> } else { <script type="text/javascript"> alert('I has javascripts'); } |
在layout.cshtml中,或者在希望呈现来自部分的脚本的任何位置,放置以下内容(一次):它将仅呈现位于此位置的当前页面上所有部分的javascript。
1 | @{ Html.RenderPartialScripts(); } |
然后要使用您的部分,只需这样做:它将只呈现这个位置的HTML。
1 | @{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");} |
我能想到的第一个解决方案是使用VIEWBAG存储必须呈现的值。
有一点我从来没有尝试过,如果这是从一个局部的观点,但它应该在我。
有一种方法可以在局部视图中插入节,尽管它并不漂亮。您需要从父视图访问两个变量。由于部分视图的部分目的是创建该部分,因此需要这些变量是有意义的。
以下是在局部视图中插入节的外观:
1 2 3 4 5 6 7 | @model KeyValuePair<WebPageBase, HtmlHelper> @{ Model.Key.DefineSection("SectionNameGoesHere", () => { Model.Value.ViewContext.Writer.Write("Test"); }); } |
在页面中插入部分视图…
1 | @Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html)) |
您还可以使用此技术在任何类中以编程方式定义节的内容。
享受!
这对我很有效,允许我在同一个文件中同时定位部分视图的javascript和HTML。帮助思考过程在同一部分视图文件中查看HTML和相关部分。
在使用名为"mypartialview.cshtml"的部分视图的视图中1 2 3 4 5 6 7 8 9 10 | @Html.Partial("_MyPartialView",< model for partial view>, new ViewDataDictionary { {"Region","HTMLSection" } } }) @section scripts{ @Html.Partial("_MyPartialView",<model for partial view>, new ViewDataDictionary { {"Region","ScriptSection" } }) } |
在部分视图文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @model SomeType @{ var region = ViewData["Region"] as string; } @if (region =="HTMLSection") { } @if (region =="ScriptSection") { <script type="text/javascript"> </script"> } |
在部分视图中不需要使用节。
包括在局部视图中。它在jquery加载后执行函数。您可以为代码更改de-condition子句。
1 2 3 4 5 6 7 8 9 10 11 12 | <script type="text/javascript"> var time = setInterval(function () { if (window.jQuery != undefined) { window.clearInterval(time); //Begin $(document).ready(function () { //.... }); //End }; }, 10); |
胡里奥斯帕德
操作的目标是,他希望在部分视图中定义内联脚本,我假定该脚本仅特定于该部分视图,并将该块包含在他的脚本部分中。
我知道他想有一种局部的观点来自我克制。这个想法类似于使用角的组件。
我的方法是将脚本保持在局部视图中。现在的问题是,当调用分部视图时,它可能会先执行其中的脚本,然后再执行所有其他脚本(通常添加到布局页面的底部)。在这种情况下,您只需要让部分视图脚本等待其他脚本。有几种方法可以做到这一点。我以前使用过的最简单的一个方法是在
在我的布局中,底部会有这样的东西:
1 2 3 4 5 6 7 8 9 | // global scripts <script src="js/jquery.min.js"> // view scripts @RenderSection("scripts", false) // then finally trigger partial view scripts (function(){ document.querySelector('body').dispatchEvent(new Event('scriptsLoaded')); })(); |
然后在我的局部视图(在底部):
1 2 3 4 5 6 7 | (function(){ document.querySelector('body').addEventListener('scriptsLoaded', function() { // .. do your thing here }); })(); |
另一种解决方案是使用堆栈推送所有脚本,并在末尾调用每个脚本。正如前面提到的,其他解决方案是RequireJS/AMD模式,它也非常有效。
我以完全不同的方式解决了这个问题(因为我很匆忙,不想实现新的HTMLHelper):
我把我的部分观点用一个大的假设陈述来概括:
1 2 3 4 5 | @if ((bool)ViewData["ShouldRenderScripts"] == true){ // Scripts }else{ // Html } |
然后,我用自定义的viewdata调用了部分两次:
1 2 3 4 5 6 7 | @Html.Partial("MyPartialView", Model, new ViewDataDictionary { {"ShouldRenderScripts", false } }) @section scripts{ @Html.Partial("MyPartialView", Model, new ViewDataDictionary { {"ShouldRenderScripts", true } }) } |
我有一个类似的问题,我有一个母版页,如下所示:
1 2 3 4 5 6 7 8 9 10 11 | @section Scripts { $(document).ready(function () { ... }); } ... @Html.Partial("_Charts", Model) |
但是部分视图依赖于脚本部分中的一些javascript。我通过将部分视图编码为JSON,将其加载到一个javascript变量中,然后使用它填充一个DIV来解决这个问题,因此:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @{ var partial = Html.Raw(Json.Encode(new { html = Html.Partial("_Charts", Model).ToString() })); } @section Scripts { $(document).ready(function () { ... var partial = @partial; $('#partial').html(partial.html); }); } |
选择,您可以使用文件夹/index.cshtml作为母版页,然后添加节脚本。然后,在您的布局中,您有:
1 | @RenderSection("scripts", required: false) |
以及index.cshtml:
1 2 3 | @section scripts{ @Scripts.Render("~/Scripts/file.js") } |
它将影响你所有的党派观点。它为我工作
冥王星的想法更美好:
自定义Web视图网页.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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:
1 | <pages pageBaseType="Web.Helpers.CustomWebViewPage"> |
观点:
1 | @PartialWithScripts("_BackendSearchForm") |
部分(_backendsearchform.cshtml):
1 2 3 4 5 6 7 | @{ RenderScriptsInBasePage(scripts()); } @helper scripts() { //code will be rendered in a"scripts" section of the Layout page } |
版面设计:
1 | @RenderSection("scripts", required: false) |
使用MVC核心,您可以创建一个整洁的taghelper
添加脚本时(例如部分)
1 2 3 4 5 | <scripts> <script type="text/javascript"> //anything here </scripts> |
输出脚本时(例如在布局文件中)
1 | <scripts render="true"></scripts> |
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 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())); } } } |
好吧,我想其他的海报已经为你提供了一种直接在你的部分中包含@部分的方法(通过使用第三方HTML助手)。
但是,我认为,如果您的脚本与您的部分紧密耦合,那么只需将您的javascript直接放在部分中的内联
我刚刚在局部视图中添加了这段代码,并解决了这个问题,虽然不是很干净,但它仍然有效。必须确保要渲染的对象的ID。
$(document).ready(函数()。{$(profile_profileid")。选择菜单(图标:按钮:'ui-icon-circle-arrow-s');$(titleid_fk")。选择菜单(图标:按钮:'ui-icon-circle-arrow-s');$(cityid_fk")。选择菜单(图标:按钮:'ui-icon-circle-arrow-s');$(genderid_fk")。选择菜单(图标:按钮:'ui-icon-circle-arrow-s');$(packageid_fk")。选择菜单(图标:按钮:'ui-icon-circle-arrow-s');(});假设您有一个名为contact.cshtml的部分视图,您的联系人可以是合法的(名称)或物理主题(名字、姓氏)。您的视图应该注意呈现的内容以及可以通过JavaScript实现的内容。因此,可能需要延迟渲染和JS内部视图。
我认为唯一的方法,也就是说,当我们创建一种不引人注目的方式来处理这种用户界面问题时,它是可以被忽略的。
还要注意,MVC6将有一个所谓的视图组件,甚至MVC期货也有一些类似的东西,Telerik也支持这样的东西…
我也有类似的问题,用这个解决了它:
1 2 3 | @section ***{ @RenderSection("****", required: false) } |
我猜这是一个很好的注射方法。