Html 使用编辑器/显示模板中的部分
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5433531/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using sections in Editor/Display templates
提问by eth0
I want to keep all of my JavaScript code in one section; just before the closing body
tag in my master layout page and just wondering the best to go about it, MVC style.
我想把我所有的 JavaScript 代码放在一个部分;就body
在我的主布局页面中的结束标记之前,只是想知道最好的方法是 MVC 风格。
For example, if I create a DisplayTemplate\DateTime.cshtml
file which uses jQuery UI's DateTime Picker than I would embed the JavaScript directly into that template but then it will render mid-page.
例如,如果我创建一个DisplayTemplate\DateTime.cshtml
使用 jQuery UI 的 DateTime Picker的文件,那么我会将 JavaScript 直接嵌入到该模板中,但它会呈现中间页面。
In my normal views I can just use @section JavaScript { //js here }
and then @RenderSection("JavaScript", false)
in my master layout but this doesn't seem to work in display/editor templates - any ideas?
在我的普通视图中,我可以只使用@section JavaScript { //js here }
然后@RenderSection("JavaScript", false)
在我的主布局中使用,但这似乎在显示/编辑器模板中不起作用 - 有什么想法吗?
回答by Darin Dimitrov
You could proceed with a conjunction of two helpers:
您可以继续使用两个助手的连接:
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;
}
}
and then in your _Layout.cshtml
:
然后在你的_Layout.cshtml
:
<body>
...
@Html.RenderScripts()
</body>
and somewhere in some template:
以及某个模板中的某处:
@Html.Script(
@<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
)
回答by eth0
Modified version of Darin's answer to ensure ordering. Also works with CSS:
Darin 答案的修改版本以确保排序。也适用于 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);
}
You can add JS and CSS resources like this:
您可以像这样添加 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")
And render JS and CSS resources like this:
并像这样渲染 JS 和 CSS 资源:
@Html.RenderResources("js")
@Html.RenderResources("css")
You could do a string check to see if it starts with script/link so you don't have to explicitly define what each resource is.
您可以进行字符串检查以查看它是否以脚本/链接开头,这样您就不必明确定义每个资源是什么。
回答by John.W.Harding
I faced the same problem, but solutions proposed here work good only for adding reference to the resource and are not very suitable for inline JS code. I found a very helpful articleand wrapped all my inline JS (and also script tags) in
我遇到了同样的问题,但这里提出的解决方案仅适用于添加对资源的引用,不太适合内联 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>
}
And in the _Layout view placed @Html.PageScripts()
just before closing 'body' tag. Works like a charm for me.
并在 _Layout 视图中放置@Html.PageScripts()
在关闭“body”标签之前。对我来说就像一种魅力。
帮手自己:
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())));
}
}
回答by drzaus
I liked the solutionposted by @john-w-harding, so I combined it with the answerby @darin-dimitrov to make the following probably overcomplicated solution that lets you delay rendering any html (scripts too) within a using block.
我喜欢@john-w-harding 发布的解决方案,所以我将它与@darin-dimitrov的答案结合起来,制作了以下可能过于复杂的解决方案,让您在 using 块中延迟渲染任何 html(脚本)。
USAGE
用法
In a repeated partial view, only include the block one time:
在重复的局部视图中,只包含一次块:
@using (Html.Delayed(isOnlyOne: "MYPARTIAL_scripts")) {
<script>
someInlineScript();
</script>
}
In a (repeated?) partial view, include the block for every time the partial is used:
在(重复?)局部视图中,每次使用局部时都包含块:
@using (Html.Delayed()) {
<b>show me multiple times, @Model.Whatever</b>
}
In a (repeated?) partial view, include the block once, and later render it specifically by name one-time
:
在(重复?)局部视图中,包含块一次,然后按名称专门渲染它one-time
:
@using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
<b>show me once by name</b>
<span>@Model.First().Value</span>
}
To render:
渲染:
@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
CODE
代码
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 <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 (i.e. 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 (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)
/// <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));
}
}
回答by Russ Cam
Install the Forloop.HtmlHelpersnuget package - it adds some helpers for managing scripts in partial views and editor templates.
安装Forloop.HtmlHelpersnuget 包 - 它添加了一些帮助程序来管理部分视图和编辑器模板中的脚本。
Somewhere in your layout, you need to call
在布局中的某个地方,您需要调用
@Html.RenderScripts()
This will be where any script files and script blocks will be outputted in the page so I would recommend putting it after your main scripts in the layout and after a scripts section (if you have one).
这将是任何脚本文件和脚本块将在页面中输出的地方,因此我建议将其放在布局中的主要脚本之后和脚本部分之后(如果您有)。
If you're using The Web Optimization Framework with bundling, you can use the overload
如果您使用捆绑的 Web 优化框架,则可以使用重载
@Html.RenderScripts(Scripts.Render)
so that this method is used for writing out script files.
以便此方法用于写出脚本文件。
Now, anytime you want to add script files or blocks in a view, partial view or template, simply use
现在,只要您想在视图、局部视图或模板中添加脚本文件或块,只需使用
@using (Html.BeginScriptContext())
{
Html.AddScriptFile("~/Scripts/jquery.validate.js");
Html.AddScriptBlock(
@<script type="text/javascript">
$(function() { $('#someField').datepicker(); });
</script>
);
}
The helpers ensure that only one script file reference is rendered if added multiple times and it also ensures that script files are rendered out in an expected order i.e.
如果多次添加,帮助程序确保仅呈现一个脚本文件引用,并且还确保脚本文件以预期的顺序呈现,即
- Layout
- Partials and Templates (in the order in which they appear in the view, top to bottom)
- 布局
- 部分和模板(按照它们在视图中出现的顺序,从上到下)
回答by Chris
This post really helped me so I thought I would post my implementation of the basic idea. I've introduced a helper function that can return script tags for use in the @Html.Resource function.
这篇文章真的对我有帮助,所以我想我会发布我对基本想法的实现。我已经引入了一个辅助函数,它可以返回脚本标签以在 @Html.Resource 函数中使用。
I also added a simple static class so that I can use typed variables to identify a JS or CSS resource.
我还添加了一个简单的静态类,以便我可以使用类型化变量来识别 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));
}
}
And in use
并且在使用中
@Html.Resource(Html.ScriptTag("Areas/Admin/js/plugins/wysiwyg/jquery.wysiwyg.js"), ResourceType.Js)
Thanks to @Darin Dimitrov who supplied the answer in my question here.
回答by Martin_W
The answer given in Populate a Razor Section From a Partialusing the RequireScript
HtmlHelper follows the same pattern. It also has the benefit that it checks for and suppresses duplicate references to the same Javascript URL, and it has an explicit priority
parameter that can be used to control ordering.
使用HtmlHelper从部分填充 Razor 部分中给出的答案RequireScript
遵循相同的模式。它还具有检查和抑制对同一 Javascript URL 的重复引用的好处,并且它具有priority
可用于控制排序的显式参数。
I extended this solution by adding methods for:
我通过添加以下方法扩展了这个解决方案:
// 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) { ... }
I like Darin's & eth0's solutions though since they use the HelperResult
template, which allows for script and CSS blocks, not just links to Javascript and CSS files.
我喜欢 Darin 和 eth0 的解决方案,因为它们使用HelperResult
模板,允许脚本和 CSS 块,而不仅仅是指向 Javascript 和 CSS 文件的链接。
回答by Erkan
@Darin Dimitrov and @eth0 answers to use with bundle extention usage :
@Darin Dimitrov 和 @eth0 回答与捆绑扩展使用一起使用:
@Html.Resources(a => new HelperResult(b => b.Write( System.Web.Optimization.Scripts.Render("~/Content/js/formBundle").ToString())), "jsTop")