在编辑器/显示模板中使用部分

我想保留所有的JavaScript代码在一个部分; 就在我的主布局页面closuresbody标签之前,只是想知道最好的,MVC风格。

例如,如果我创build一个使用jQuery UI的DateTime Picker的DisplayTemplate\DateTime.cshtml文件,那么我将直接将JavaScriptembedded到该模板中,然后它将呈现中间页面。

在我的普通视图中,我可以在我的主布局中使用@section JavaScript { //js here } ,然后使用@RenderSection("JavaScript", false) ,但这似乎不适用于显示/编辑器模板 – 任何想法?

您可以继续使用两个助手的连接:

 public static class HtmlExtensions { public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template) { htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template; return MvcHtmlString.Empty; } public static IHtmlString RenderScripts(this HtmlHelper htmlHelper) { foreach (object key in htmlHelper.ViewContext.HttpContext.Items.Keys) { if (key.ToString().StartsWith("_script_")) { var template = htmlHelper.ViewContext.HttpContext.Items[key] as Func<object, HelperResult>; if (template != null) { htmlHelper.ViewContext.Writer.Write(template(null)); } } } return MvcHtmlString.Empty; } } 

然后在你的_Layout.cshtml

 <body> ... @Html.RenderScripts() </body> 

并在某个模板的某处:

 @Html.Script( @<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script> ) 

达林的答案的修改版本,以确保订购。 也适用于CSS:

  public static IHtmlString Resource(this HtmlHelper HtmlHelper, Func<object, HelperResult> Template, string Type) { if (HtmlHelper.ViewContext.HttpContext.Items[Type] != null) ((List<Func<object, HelperResult>>)HtmlHelper.ViewContext.HttpContext.Items[Type]).Add(Template); else HtmlHelper.ViewContext.HttpContext.Items[Type] = new List<Func<object, HelperResult>>() { Template }; return new HtmlString(String.Empty); } public static IHtmlString RenderResources(this HtmlHelper HtmlHelper, string Type) { if (HtmlHelper.ViewContext.HttpContext.Items[Type] != null) { List<Func<object, HelperResult>> Resources = (List<Func<object, HelperResult>>)HtmlHelper.ViewContext.HttpContext.Items[Type]; foreach (var Resource in Resources) { if (Resource != null) HtmlHelper.ViewContext.Writer.Write(Resource(null)); } } return new HtmlString(String.Empty); } 

添加JS / CSS:

 @Html.Resource(@<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>, "js")` @Html.Resource(@<link rel="stylesheet" href="@Url.Content("~/CSS/style.css")" />, "css")` 

@Html.RenderResources("js")@Html.RenderResources("css")来渲染。

你可以做一个string检查,看看它是否以脚本/链接开始,所以你不必显式定义每个资源是什么。

我面临同样的问题,但这里提出的解决scheme只适用于添加对资源的引用,并不适合内联JS代码。 我发现了一个非常有用的文章,并将所有内联JS(也包括脚本标签)

 @using (Html.BeginScripts()) { <script src="@Url.Content("~/Scripts/jquery-ui-1.8.18.min.js")" type="text/javascript"></script> <script> // my inline scripts here <\script> } 

并且在closures“body”标签之前放置在@Html.PageScripts()中的_Layout视图中。 对我来说就像一个魅力。


帮手本身:

 public static class HtmlHelpers { private class ScriptBlock : IDisposable { private const string scriptsKey = "scripts"; public static List<string> pageScripts { get { if (HttpContext.Current.Items[scriptsKey] == null) HttpContext.Current.Items[scriptsKey] = new List<string>(); return (List<string>)HttpContext.Current.Items[scriptsKey]; } } WebViewPage webPageBase; public ScriptBlock(WebViewPage webPageBase) { this.webPageBase = webPageBase; this.webPageBase.OutputStack.Push(new StringWriter()); } public void Dispose() { pageScripts.Add(((StringWriter)this.webPageBase.OutputStack.Pop()).ToString()); } } public static IDisposable BeginScripts(this HtmlHelper helper) { return new ScriptBlock((WebViewPage)helper.ViewDataContainer); } public static MvcHtmlString PageScripts(this HtmlHelper helper) { return MvcHtmlString.Create(string.Join(Environment.NewLine, ScriptBlock.pageScripts.Select(s => s.ToString()))); } } 

我喜欢@ john-w-harding发布的解决scheme ,所以我将它与@ darin-dimitrov的答案结合起来,以使下面的可能过于复杂的解决scheme,让您延迟呈现任何HTML(脚本太)在一个使用块。

用法

在重复的局部视图中,只包含一次块:

 @using (Html.Delayed(isOnlyOne: "MYPARTIAL_scripts")) { <script> someInlineScript(); </script> } 

在(重复?)部分视图中,每次使用部分时都要包含块:

 @using (Html.Delayed()) { <b>show me multiple times, @Model.Whatever</b> } 

在(重复的)局部视图中,将该块包括一次,并且稍后一次性使用名称来进行渲染:

 @using (Html.Delayed("one-time", isOnlyOne: "one-time")) { <b>show me once by name</b> <span>@Model.First().Value</span> } 

渲染:

 @Html.RenderDelayed(); // the "default" unidentified blocks @Html.RenderDelayed("one-time", false); // render the specified block by name, and allow us to render it again in a second call @Html.RenderDelayed("one-time"); // render the specified block by name @Html.RenderDelayed("one-time"); // since it was "popped" in the last call, won't render anything 

 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)); } } 

安装Forloop.HtmlHelpers nuget包 – 它在部分视图和编辑器模板中添加了一些用于pipe理脚本的助手。

在你的布局的某个地方,你需要打电话

 @Html.RenderScripts() 

这将是任何脚本文件和脚本块将在页面中输出的地方,所以我build议把它放在你的主要脚本之后的布局和脚本部分(如果有的话)。

如果您使用捆绑Web优化框架,则可以使用重载

 @Html.RenderScripts(Scripts.Render) 

所以这个方法是用来写出脚本文件的。

现在,无论何时您想在视图,局部视图或模板中添加脚本文件或块,只需使用

 @using (Html.BeginScriptContext()) { Html.AddScriptFile("~/Scripts/jquery.validate.js"); Html.AddScriptBlock( @<script type="text/javascript"> $(function() { $('#someField').datepicker(); }); </script> ); } 

助手确保只添加一个脚本文件引用,如果多次添加,还可确保脚本文件按照预期顺序呈现

  1. 布局
  2. 部分和模板(按照它们在视图中的显示顺序,从上到下)

这篇文章真的帮助了我,所以我想我会发布我的基本思想的实现。 我已经引入了一个帮助函数,可以返回脚本标记以便在@ Html.Resource函数中使用。

我还添加了一个简单的静态类,以便我可以使用types化variables来标识JS或CSS资源。

 public static class ResourceType { public const string Css = "css"; public const string Js = "js"; } public static class HtmlExtensions { public static IHtmlString Resource(this HtmlHelper htmlHelper, Func<object, dynamic> template, string Type) { if (htmlHelper.ViewContext.HttpContext.Items[Type] != null) ((List<Func<object, dynamic>>)htmlHelper.ViewContext.HttpContext.Items[Type]).Add(template); else htmlHelper.ViewContext.HttpContext.Items[Type] = new List<Func<object, dynamic>>() { template }; return new HtmlString(String.Empty); } public static IHtmlString RenderResources(this HtmlHelper htmlHelper, string Type) { if (htmlHelper.ViewContext.HttpContext.Items[Type] != null) { List<Func<object, dynamic>> resources = (List<Func<object, dynamic>>)htmlHelper.ViewContext.HttpContext.Items[Type]; foreach (var resource in resources) { if (resource != null) htmlHelper.ViewContext.Writer.Write(resource(null)); } } return new HtmlString(String.Empty); } public static Func<object, dynamic> ScriptTag(this HtmlHelper htmlHelper, string url) { var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext); var script = new TagBuilder("script"); script.Attributes["type"] = "text/javascript"; script.Attributes["src"] = urlHelper.Content("~/" + url); return x => new HtmlString(script.ToString(TagRenderMode.Normal)); } } 

并在使用中

 @Html.Resource(Html.ScriptTag("Areas/Admin/js/plugins/wysiwyg/jquery.wysiwyg.js"), ResourceType.Js) 

感谢@Darin Dimitrov谁在这里提供了我的问题的答案。

使用RequireScript HtmlHelper 从部分填充剃刀部分中给出的答案遵循相同的模式。 它还具有检查并抑制对同一个Javascript URL的重复引用的好处,并且具有可用于控制sorting的显式priority参数。

我通过添加以下方法扩展了此解决scheme:

 // use this for scripts to be placed just before the </body> tag public static string RequireFooterScript(this HtmlHelper html, string path, int priority = 1) { ... } public static HtmlString EmitRequiredFooterScripts(this HtmlHelper html) { ... } // use this for CSS links public static string RequireCSS(this HtmlHelper html, string path, int priority = 1) { ... } public static HtmlString EmitRequiredCSS(this HtmlHelper html) { ... } 

我喜欢Darin的eth0解决scheme,因为他们使用HelperResult模板,它允许脚本和CSS块,而不仅仅是链接到Javascript和CSS文件。

@Darin Dimitrov和@ eth0答案用于捆绑扩展使用:

 @Html.Resources(a => new HelperResult(b => b.Write( System.Web.Optimization.Scripts.Render("~/Content/js/formBundle").ToString())), "jsTop")