使用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>
我需要从_myPartial
局部视图的Scripts
部分注入一些内容。
我该怎么做?
部分不能在部分视图中工作,这是设计。 您可以使用一些自定义的助手来实现类似的行为,但老实说,视图的责任是包含必要的脚本,而不是部分的责任。 我建议使用主视图的@scripts部分来做到这一点,而不是部分担心脚本。
这是一个非常受欢迎的问题,所以我会发布我的解决方案。
我也有同样的问题,虽然不是很理想,但我觉得它实际上工作得很好,不会使局部依赖于观点。
我的情况是,一个行动本身是可以访问的,但也可以嵌入到一个视图 – 谷歌地图。
在我的_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
视图包含要呈现在页面上的JavaScript
这样,我的脚本就被隔离了,并且可以在需要的时候渲染到页面中,而body_scripts
标签只在剃须刀视图引擎第一次发现它时才被渲染。
这让我把所有的东西都分开了 – 这个解决方案对我来说效果很好,其他的可能会遇到问题,但是它确实为“设计”漏洞打了补丁。
从这个线程的解决方案中,我想出了以下可能过于复杂的解决方案,可以让您在使用块中延迟渲染任何html(脚本)。
用法
创建“部分”
-
典型场景:在部分视图中,无论页面中重复部分视图的次数,只需将块包含一次:
@using (Html.Delayed(isOnlyOne: "some unique name for this section")) { <script> someInlineScript(); </script> }
-
在局部视图中,每次使用部分时都要包含该块:
@using (Html.Delayed()) { <b>show me multiple times, @Model.Whatever</b> }
-
在局部视图中,无论局部重复多少次,只包含一次块,但是稍后
when-i-call-you
通过名称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 (ie 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 (ie 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 (ie 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 (ie 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)); } }
我有这个问题,并使用这种技术。
它是我发现的最好的解决方案,非常灵活。
也请 在这里 投票加入对累积部分声明的支持
遵循不引人注目的原则,“_myPartial”不直接将内容直接注入脚本部分。 您可以将这些部分视图脚本添加到单独的.js
文件中,并将其从父视图引用到@scripts部分。
如果你确实有一个合理的需要从一个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>
我们对web的思考方式存在根本的缺陷,特别是在使用MVC时。 缺点是JavaScript是某种视图的责任。 一个视图是一个视图,JavaScript(行为或其他)是JavaScript。 在Silverlight和WPF的MVVM模式中,我们需要面对“先查看”或“先模型化”。 在MVC中,我们应该总是试图从模型的角度来推理,而JavaScript在很多方面都是这个模型的一部分。
我会建议使用AMD模式(我自己像RequireJS )。 在模块中分离你的JavaScript,定义你的功能,并从JavaScript钩入你的HTML,而不是依靠视图来加载JavaScript。 这将清理你的代码,分离你的顾虑,使所有的生活更容易一举。
我能想到的第一个解决方案是使用ViewBag来存储必须呈现的值。
唯一我从来没有尝试过,如果从局部的角度看这个工作,但它应该imo。
您不需要在部分视图中使用部分。
包含在您的部分视图中。 它在jQuery加载后执行该函数。 你可以修改你的代码的de条件。
<script type="text/javascript"> var time = setInterval(function () { if (window.jQuery != undefined) { window.clearInterval(time); //Begin $(document).ready(function () { //.... }); //End }; }, 10); </script>
胡里奥·斯派德
有一种方法可以在部分视图中插入部分,虽然不是很漂亮。 您需要有权访问父视图中的两个变量。 由于部分视图的部分目的是创建该部分,所以需要这些变量是有意义的。
以下是在部分视图中插入部分的样子:
@model KeyValuePair<WebPageBase, HtmlHelper> @{ Model.Key.DefineSection("SectionNameGoesHere", () => { Model.Value.ViewContext.Writer.Write("Test"); }); }
并在页面插入部分视图…
@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))
您也可以使用这种技术在任何类中以编程方式定义部分的内容。
请享用!
您可以使用这些扩展方法 :(另存为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); } } } } }
像这样使用:
部分示例:(_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");}
这工作对我来说,允许我在同一个文件中共同定位javascript和html部分视图。 帮助思考过程在相同的部分视图文件中看到html和相关的部分。
在视图中使用部分视图称为“_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"> }
我解决了这个完全不同的路线(因为我很匆忙,不想实施一个新的HtmlHelper):
我用大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 } }) }
我有一个类似的问题,我有一个母版页如下:
@section Scripts { <script> $(document).ready(function () { ... }); </script> } ... @Html.Partial("_Charts", Model)
但部分视图取决于脚本部分中的一些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>
那么,我猜其他海报已经提供了一种手段,直接包括@部分在您的部分(通过使用第三方HTML帮手)。
但是,我认为,如果你的脚本与你的脚本紧密结合, 就把你的<script>
直接放在<script>
内部的<script>
标签中 ,然后完成它(如果你打算使用部分脚本不止一次在一个单一的观点);
您可以使用您的Folder / index.cshtml作为主页面,然后添加部分脚本。 然后,在你的布局中你有:
@RenderSection("scripts", required: false)
和你的index.cshtml:
@section scripts{ @Scripts.Render("~/Scripts/file.js") }
它会处理所有的部分视图。 它为我工作
冥王星的想法更好:
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)
假设您有一个名为_contact.cshtml的分部视图,您的联系人可以是合法的(名称)或物理主题(名字,姓氏)。 你的看法应该关心什么是渲染和可以通过JavaScript来实现。 所以延迟渲染和视图内的JS可能是需要的。
我认为唯一的办法就是如何创建一个不显眼的方式来处理这样的UI问题。
还要注意,MVC 6将有一个所谓的视图组件,即使MVC未来有一些类似的东西,Telerik也支持这样的事情… … –
我有类似的问题解决了这个问题:
@section ***{ @RenderSection("****", required: false) }
这是一个很好的方式来注入我猜。
- 如何在ASP.MVC中指定多行编辑器的列和行?
- 不显眼的validation不适用于dynamic添加的局部视图
- 在ASP.NET MVC中:从Razor视图调用Controller Action Method的所有可能的方法
- ASP.NET MVC剃刀渲染没有编码
- DisplayNameFor()从模型中的List <Object>
- 命名约定来区分部分视图和普通视图
- asp.net mvc 3 razor视图 – >强types元组列表问题
- 剃刀语法 – foreach循环
- 尝试“System.Web.Mvc.PreApplicationStartCode.Start()”到关键方法“System.Web.WebPages.Razor.PreApplicationStartCode.Start()”失败