使用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(脚本)。

用法

创建“部分”

  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通过名称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) } 

这是一个很好的方式来注入我猜。