从一个局部视图填充Razor部分

111

我尝试这样做的主要动机是将仅在页面底部的部分所需的JavaScript与其余JavaScript放在一起,而不是在呈现该部分的页面中间。

以下是我所尝试做的简化示例:

这里是一个布局,其中在body之前有一个Scripts节。

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />    
</head>

<body>
    @RenderBody()
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
    @RenderSection("Scripts", false)
</body>
</html>

以下是使用这个布局的示例视图。

<h2>This is the view</h2>

@{Html.RenderPartial("_Partial");}

@section Scripts {
<script type="text/javascript">
        alert("I'm a view.");
</script>
}

这里是从视图中呈现的部分。

<p>This is the partial.</p>

@* this never makes it into the rendered page *@
@section Scripts {
<script type="text/javascript">
    alert("I'm a partial."); 
</script>
}
在这个例子中,在视图中指定的标记被放置在部分中,但来自部分的标记没有。是否有可能使用Razor从部分视图填充部分?如果不能,有什么其他方法可以在页面底部只包含部分所需的JavaScript而不是全局包含它?

也许是因为你在部分代码中有另一个脚本部分,所以出现了问题。我不确定。你的代码有点混乱。 - gideon
1
不是这样的。即使该部分在视图中被省略,偏见中的代码也不会出现在最终呈现的页面中。我认为SLaks是正确的,部分无法参与父视图的部分。 - Craig M
12个回答

80
我处理这个问题的方式是编写了一些HtmlHelper类的扩展方法。这使得部分视图可以声明它们需要一个脚本,然后在编写包含 < script > 标签的布局视图时,我调用我的辅助方法来输出所需的脚本。
以下是辅助方法:
public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
{
    var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
    if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
    if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
    return null;
}

public static HtmlString EmitRequiredScripts(this HtmlHelper html)
{
    var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
    if (requiredScripts == null) return null;
    StringBuilder sb = new StringBuilder();
    foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
    {
        sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
    }
    return new HtmlString(sb.ToString());
}
public class ResourceInclude
{
    public string Path { get; set; }
    public int Priority { get; set; }
}

一旦你做好了这个,你的部分视图只需要调用@Html.RequireScript("/Path/To/Script")

在布局视图的头部区域调用@Html.EmitRequiredScripts()

这样做的额外好处是可以避免重复请求脚本。如果多个视图/部分视图都需要给定脚本,你可以安全地假设它只会被输出一次。


我无法让这个解决方案工作。看起来 EmitRequiredScripts() 在任何部分视图调用 RequireScript() 之前就被调用了。我做错了什么吗? - Bryan Roth
这个在子操作中不起作用,你应该检查它是否为子操作,如果是,则应使用Html.ViewContext.ParentActionViewContext! - dampee
1
在部署新版本的应用程序时,这个有没有缓存破坏支持?@scripts.Render()方法默认会在URL末尾添加一个参数,该参数是在构建时生成的,以便在部署新版本时强制浏览器获取最新版本。 - Simon Green
有人为.NET Core 2编写了更新版本吗? - Mathieu VIALES
对于任何人涉及@MathieuVIALES的问题,贡献者Sebastian已将Mr. Bell的代码移植到[ASP.NET Core 2.0](https://dev59.com/-G435IYBdhLWcg3wigqx#57049500)。话虽如此,我没有测试过它是否在ASP.NET Core 3.x或ASP.NET Core 5.x中继续工作。 - Jeremy Caney
显示剩余5条评论

31

局部视图无法参与其父视图的区块。


1
这正是我所怀疑的。谢谢。 - Craig M
2
@Shimmy:你应该使用资源管理系统,比如 Cassette。 - SLaks
@SLaks:哪里写了这个?显然它不起作用,但我找不到任何文件指出这一点,也没有错误发生。什么都没发生。 @RenderBody 在这里是特殊的。 - nicodemus13
我来晚了,但这对我来说似乎很愚蠢。 - nportelli
好的,你可以在 @section Scripts 部分内直接调用 @Html.Partial。假设这样的局部视图仅包含一个带有 JavaScript 的 <script> 标签,这是一种优雅的重用脚本的方式,同时仍然具有完整的 Razor 支持(与静态 JS 文件不同)。实质上,它将 cshtml 文件视为 js 文件以获得 Razor 支持(尽管您会失去捆绑支持,但对于这样的动态脚本,您不需要它)。 - Triynko
显示剩余2条评论

14

你可以创建一个第二个局部视图,专门负责注入所需的JavaScript代码。如果需要,可以在@if块周围放置多个脚本:

@model string
@if(Model == "bla") {
    <script type="text/javascript">...</script>
}

@else if(Model == "bli") {
    <script type="text/javascript">...</script>
}

显然这部分可以再精简一些,但是在你的视图的Scripts部分:

@section Scripts
{
    @Html.Partial("_Scripts", "ScriptName_For_Partial1")
}

再次强调,它可能不会获得美丽奖,但它可以工作。


1
这与我最终所做的非常接近。它肯定不太美观,但它能正常工作。唯一的缺点是您无法通过ajax调用获取部分并包含JS。我认为从长远来看,我将使用jQuery模板进行重构,并仅从我的控制器发送JSON而不是在服务器端构建HTML。 - Craig M
@CraigM 我也是这样考虑的。MVC是合法的,但对我来说更合理的是在客户端使用模板(我正在研究Backbone.js),然后从API进行推拉。 - one.beat.consumer
@one.beat.customer - 我一直在使用underscore的模板,因为我也使用Backbone,但我正在考虑切换到Twitter的Hogan库或Nodejitsu的Plates。两者都有非常不错的功能。 - Craig M

10

更加优雅的做法是将局部视图脚本移动到单独的文件中,然后在视图的 Scripts 部分进行渲染:

<h2>This is the view</h2>

@Html.RenderPartial("_Partial")

@section Scripts
{
    @Html.RenderPartial("_PartialScripts")

    <script type="text/javascript">
        alert("I'm a view script.");
    </script>
}

部分视图 _Partial.cshtml

<p>This is the partial.</p>

只包含脚本的局部视图 _PartialScripts.cshtml:

<script type="text/javascript">
    alert("I'm a partial script!");
</script>

这并不像其他答案中建议的一些扩展方法或插件那样自动化,但它具有简单和清晰的优点。我喜欢它。 - Mark Meuer

7

[更新版本] 根据 @Necrocubus 的问题,更新版本包含内联脚本。

public static class ScriptsExtensions
{
    const string REQ_SCRIPT = "RequiredScript";
    const string REQ_INLINESCRIPT = "RequiredInlineScript";
    const string REQ_STYLE = "RequiredStyle";

    #region Scripts
    /// <summary>
    /// Adds a script 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="path"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="bottom"></param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static string RequireScript(this IHtmlHelper html, string path, int priority = 1, bool bottom=false, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceToInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_SCRIPT] = requiredScripts = new List<ResourceToInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceToInclude() { Path = path, Priority = priority, Options = options, Type=ResourceType.Script, Bottom=bottom});
        return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="script"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="bottom"></param>
    /// <returns></returns>
    public static string RequireInlineScript(this IHtmlHelper html, string script, int priority = 1, bool bottom = false)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_INLINESCRIPT] as List<InlineResource>;
        if (requiredScripts == null) ctxt.Items[REQ_INLINESCRIPT] = requiredScripts = new List<InlineResource>();
        requiredScripts.Add(new InlineResource() { Content=script, Priority = priority, Bottom=bottom, Type=ResourceType.Script});
        return null;
    }

    /// <summary>
    /// Just call @Html.EmitRequiredScripts(false)
    /// at the end of your head tag and 
    /// @Html.EmitRequiredScripts(true) at the end of the body if some scripts are set to be at the bottom.
    /// </summary>
    public static HtmlString EmitRequiredScripts(this IHtmlHelper html, bool bottom)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceToInclude>;
        var requiredInlineScripts = ctxt.Items[REQ_INLINESCRIPT] as List<InlineResource>;
        var scripts = new List<Resource>();
        scripts.AddRange(requiredScripts ?? new List<ResourceToInclude>());
        scripts.AddRange(requiredInlineScripts ?? new List<InlineResource>());
        if (scripts.Count==0) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in scripts.Where(s=>s.Bottom==bottom).OrderByDescending(i => i.Priority))
        {
            sb.Append(item.ToString());
        }
        return new HtmlString(sb.ToString());
    }
    #endregion Scripts

    #region Styles
    /// <summary>
    /// 
    /// </summary>
    /// <param name="html"></param>
    /// <param name="path"></param>
    /// <param name="priority">Ordered by decreasing priority </param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static string RequireStyle(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceToInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_STYLE] = requiredScripts = new List<ResourceToInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceToInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }

    /// <summary>
    /// Just call @Html.EmitRequiredStyles()
    /// at the end of your head tag
    /// </summary>
    public static HtmlString EmitRequiredStyles(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceToInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            sb.Append(item.ToString());
        }
        return new HtmlString(sb.ToString());
    }
    #endregion Styles

    #region Models
    public class InlineResource : Resource
    {
        public string Content { get; set; }
        public override string ToString()
        {
            return "<script>"+Content+"</script>";
        }
    }

    public class ResourceToInclude : Resource
    {
        public string Path { get; set; }
        public string[] Options { get; set; }
        public override string ToString()
        {
            switch(Type)
            {
                case ResourceType.CSS:
                    if (Options == null || Options.Length == 0)
                        return String.Format("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" />\n", Path);
                    else
                        return String.Format("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" {1} />\n", Path, String.Join(" ", Options));
                default:
                case ResourceType.Script:
                    if (Options == null || Options.Length == 0)
                        return String.Format("<script src=\"{0}\" type=\"text/javascript\"></script>\n", Path);
                    else
                        return String.Format("<script src=\"{0}\" type=\"text/javascript\" {1}></script>\n", Path, String.Join(" ", Options));
            }
        }
    }
    public class Resource
    {
        public ResourceType Type { get; set; }
        public int Priority { get; set; }
        public bool Bottom { get; set; }
    }
    public enum ResourceType
    {
        Script,
        CSS
    }
    #endregion Models
}

以下是关于IT技术的内容翻译:

我的建议是,虽然这篇文章已经有些年头了,但仍然具有参考价值。以下是Bell先生的解决方案的升级版,可用于ASP.Net Core。

它允许从导入的部分视图和子视图中添加脚本和样式到主布局,并且可以添加选项来控制脚本/样式的导入(如async defer等):

public static class ScriptsExtensions
{
    const string REQ_SCRIPT = "RequiredScript";
    const string REQ_STYLE = "RequiredStyle";

    public static string RequireScript(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_SCRIPT] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }


    public static HtmlString EmitRequiredScripts(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_SCRIPT] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            if (item.Options == null || item.Options.Length == 0)
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
            else
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\" {1}></script>\n", item.Path, String.Join(" ", item.Options));

        }
        return new HtmlString(sb.ToString());
    }


    public static string RequireStyle(this IHtmlHelper html, string path, int priority = 1, params string[] options)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceInclude>;
        if (requiredScripts == null) ctxt.Items[REQ_STYLE] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority, Options = options });
        return null;
    }


    public static HtmlString EmitRequiredStyles(this IHtmlHelper html)
    {
        var ctxt = html.ViewContext.HttpContext;

        var requiredScripts = ctxt.Items[REQ_STYLE] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            if (item.Options == null || item.Options.Length == 0)
                sb.AppendFormat("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" />\n", item.Path);
            else
                sb.AppendFormat("<link rel=\"stylesheet\" href=\"{0}\" type=\"text/css\" {1} />\n", item.Path, String.Join(" ", item.Options));
        }
        return new HtmlString(sb.ToString());
    }


    public class ResourceInclude
    {
        public string Path { get; set; }
        public int Priority { get; set; }
        public string[] Options { get; set; }
    }
}

谢谢你!这个回答比六年前的回答更相关,应该得到更多的赞同。 - Necroqubus
此外,这些扩展能否被修改以允许脚本的部分成为输入?例如@<text></text>或类似的部分?否则,我仍需要一个小的JS脚本来使用服务器端模型变量初始化其他脚本 :/ - Necroqubus
@Necroqubus 你可以检查更新版本,不过我还没有测试过 :) - Jean
好的,我会为您尝试测试它。我希望它能够在ASP.NET Core 1.0 MVC中工作。为了上下文,我有多个嵌套部分,希望它们的脚本在页脚呈现。 - Necroqubus
不需要 <text>,只需将其作为字符串添加(如果您喜欢,仍然可以使用 @"" 前缀来进行多行处理),而无需 <script> 标签。 - Jean

7

安装Forloop.HtmlHelpers nuget包 - 它添加了一些帮助程序,用于在部分视图和编辑器模板中管理脚本。

在您的布局中的某个位置,您需要调用

@Html.RenderScripts()

这将是页面输出任何脚本文件和脚本块的位置,因此建议将其放在布局中主要脚本之后以及脚本部分之后(如果有)。

如果您正在使用捆绑的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. 部分和模板(按照视图中出现的顺序从上到下)

2

如果你正在寻找aspnet core 2.0版本:

最初的回答

    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.AspNetCore.Html;
    using Microsoft.AspNetCore.Http;

    public static class HttpContextAccessorExtensions
    {
        public static string RequireScript(this IHttpContextAccessor htmlContextAccessor, string path, int priority = 1)
        {
            var requiredScripts = htmlContextAccessor.HttpContext.Items["RequiredScripts"] as List<ResourceInclude>;
            if (requiredScripts == null) htmlContextAccessor.HttpContext.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
            if (requiredScripts.All(i => i.Path != path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
            return null;
        }

        public static HtmlString EmitRequiredScripts(this IHttpContextAccessor htmlContextAccessor)
        {
            var requiredScripts = htmlContextAccessor.HttpContext.Items["RequiredScripts"] as List<ResourceInclude>;
            if (requiredScripts == null) return null;
            StringBuilder sb = new StringBuilder();
            foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
            {
                sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
            }
            return new HtmlString(sb.ToString());
        }
        public class ResourceInclude
        {
            public string Path { get; set; }
            public int Priority { get; set; }
        }
    }

请在脚本呈现部分之后添加到您的布局中:

在脚本渲染部分调用后添加到您的布局中:

@HttpContextAccessor.EmitRequiredScripts()

"最初的回答":在你的部分视图中:
@inject IHttpContextAccessor HttpContextAccessor

...

@HttpContextAccessor.RequireScript("/scripts/moment.min.js")

您可以使用htmlHelper.ViewContext.HttpContext.Items(在HtmlHelper扩展中)而不需要注入IHttpContextAccessor。 - mems

1

您可以创建一个新的Layout页面,并将PartialView包装在负责呈现内容和任何库部分的完整视图中。

例如,假设我有以下代码:

HomeController.cs

[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return View("About", vm);
}

当全页视图被渲染时,通常是通过合并两个文件来完成的:

About.cshtml

@model AboutViewModel

@{
    ViewBag.Title = "About CSHN";
}

<h3>@ViewBag.Title</h3>

@section Styles {
    <style> /* style info here */ </style>
}

@section Scripts {
    <script> /* script info here */ </script>
}

_Layout.cshtml (或者在_ViewStart中指定的任何内容,或在页面中覆盖)

<!DOCTYPE html>

<html>
<head>
    @RenderSection("Styles", false)
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderBody()

    @RenderSection("scripts", false)
</body>
</html>

现在,假设您想要将About.cshtml呈现为部分视图,例如作为响应AJAX调用的模态窗口。这里的目标是仅返回指定在about页面中的内容,包括所有脚本,而不包括在_Layout.cshtml主布局中包含的所有冗余内容(如完整的<html>文档)。

您可以尝试像这样使用它,但它不会带有任何部分块:

return PartialView("About", vm);

相反,添加一个像这样简单的布局页面:

_PartialLayout.cshtml

<div>
    @RenderBody()
    @RenderSection("Styles", false)
    @RenderSection("scripts", false)
</div>

或者支持像这样的模态窗口:

_ModalLayout.cshtml

<div class="modal modal-page fade" tabindex="-1" role="dialog" >
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">

            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title">@ViewBag.Title</h4>
            </div>

            <div class="modal-body">

                @RenderBody()
                @RenderSection("Styles", false)
                @RenderSection("scripts", false)

            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-inverse" data-dismiss="modal">Dismiss</button>
            </div>
        </div>
    </div>
</div>

那么您可以在此控制器或任何其他处理程序中指定自定义主视图,以便同时呈现视图的内容和脚本。
[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return !Request.IsAjaxRequest()
              ? View("About", vm)
              : View("About", "~/Views/Shared/_ModalLayout.cshtml", vm);
}

0
这是我的解决方案,用于解答有关如何将部分视图注入到 asp.net mvc 的主视图或主布局视图的常见问题。如果您在 stackoverflow 上按“section + partial”关键字搜索,您会得到一个相关问题的很长列表,并给出了回答,但似乎没有一个优美的解决方法适用于 Razor 引擎语法。因此,我研究了一下 Razor 引擎,看看是否有更好的解决方案。

幸运的是,我发现了一些有趣的东西,即 Razor 引擎如何对视图模板文件(*.cshtml、*.vbhtml)进行编译(稍后我会解释),下面是我认为使用起来相当简单和优雅的解决方案的代码。

namespace System.Web.Mvc.Html
{
    public static class HtmlHelperExtensions
    {
        /// <summary>
        /// 确保所有视图,包括分部视图(PartialView)中的节(Section)定义被按照先后顺序追加到最终文档输出流中
        /// </summary>
        public static MvcHtmlString EnsureSection(this HtmlHelper helper)
        {
            var wp = (WebViewPage)helper.ViewDataContainer;
            Dictionary<string, WebPages.SectionWriter> sw = (Dictionary<string, WebPages.SectionWriter>)typeof(WebPages.WebPageBase).GetProperty("SectionWriters", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance).GetValue(wp);
            if (!helper.ViewContext.HttpContext.Items.Contains("SectionWriter"))
            {
                Dictionary<string, Stack<WebPages.SectionWriter>> qss = new Dictionary<string, Stack<WebPages.SectionWriter>>();
                helper.ViewContext.HttpContext.Items["SectionWriter"] = qss;
            }
            var eqs = (Dictionary<string, Stack<WebPages.SectionWriter>>)helper.ViewContext.HttpContext.Items["SectionWriter"];
            foreach (var kp in sw)
            {
                if (!eqs.ContainsKey(kp.Key)) eqs[kp.Key] = new Stack<WebPages.SectionWriter>();
                eqs[kp.Key].Push(kp.Value);
            }
            return MvcHtmlString.Create("");
        }

        /// <summary>
        /// 在文档流中渲染指定的节(Section)
        /// </summary>
        public static MvcHtmlString RenderSectionEx(this HtmlHelper helper, string section, bool required = false)
        {
            if (helper.ViewContext.HttpContext.Items.Contains("SectionWriter"))
            {
                Dictionary<string, Stack<WebPages.SectionWriter>> qss = (Dictionary<string, Stack<WebPages.SectionWriter>>)helper.ViewContext.HttpContext.Items["SectionWriter"];
                if (qss.ContainsKey(section))
                {
                    var wp = (WebViewPage)helper.ViewDataContainer;
                    var qs = qss[section];
                    while (qs.Count > 0)
                    {
                        var sw = qs.Pop();
                        var os = ((WebViewPage)sw.Target).OutputStack;
                        if (os.Count == 0) os.Push(wp.Output);
                        sw.Invoke();
                    }
                }
                else if (!qss.ContainsKey(section) && required)
                {
                    throw new Exception(string.Format("'{0}' section is not defined.", section));
                }
            }
            return MvcHtmlString.Create("");
        }
    }
}

用法:使用此代码非常简单,它看起来几乎与通常的样式相同。它还支持任何级别的嵌套部分视图。例如,我有一个视图模板链:_ViewStart.cshtml->layout.cshtml->index.cshtml->[head.cshtml,foot.cshtml]->ad.cshtml。

在layout.cshtml中,我们有:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>@ViewBag.Title - @ViewBag.WebSetting.Site.WebName</title>
    <base href="@ViewBag.Template/" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1.0, user-scalable=0,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <meta name="renderer" content="webkit">
    <meta name="author" content="Taro Technology Co.,LTD" />
    <meta name="robots" content="index,follow" />
    <meta name="description" content="" />
    <meta name="keywords" content="" />
    <link rel="alternate icon" type="@ViewBag.WebSetting.Site.WebFavIcon" href="@ViewBag.WebSetting.Site.WebFavIcon">
    @Html.RenderSectionEx("Head")
</head>
<body>
    @RenderBody()
    @Html.RenderSectionEx("Foot")
</body>
</html>

而在 index.cshtml 中,我们有:

@{
    ViewBag.Title = "首页";
}

@Html.Partial("head")
<div class="am-container-1">
    .......
</div>
@Html.Partial("foot")

在 head.cshtml 中,我们将有以下代码:

@section Head{
    <link rel="stylesheet" href="assets/css/amazeui.css" />
    <link rel="stylesheet" href="assets/css/style.css" />
}

<header class="header">
   ......
</header>
@Html.EnsureSection()

在foot.cshtml或ad.cshtml中是一样的,你仍然可以在它们中定义Head或Foot部分,确保在局部视图文件的末尾调用@Html.EnsureSection()一次。这就是你需要做的,以解决asp mvc中的问题。

我只是分享我的代码片段,希望其他人能够使用它。如果你觉得有用,请不要犹豫给我的帖子点赞。:)


0
根据上面贝尔先生和Shimmy的答案,我为Bundle脚本添加了额外的功能。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.Mvc;
namespace ABC.Utility
{
public static  class PartialViewHelper
{
    public static string RequireScript(this HtmlHelper html, string path, int priority = 1)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
        if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = new List<ResourceInclude>();
        if (!requiredScripts.Any(i => i.Path == path)) requiredScripts.Add(new ResourceInclude() { Path = path, Priority = priority });
        return null;
    }

    public static string RequireBundleStyles(this HtmlHelper html, string bundleName)
    {
        var a = System.Web.Optimization.Styles.Render(bundleName);
        var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
        if (requiredStyles == null) HttpContext.Current.Items["RequiredStyles"] = requiredStyles = a;
        return null;
    }

    public static string RequireBundleScripts(this HtmlHelper html, string bundleName)
    {
        var a=System.Web.Optimization.Scripts.Render(bundleName);
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
        if (requiredScripts == null) HttpContext.Current.Items["RequiredScripts"] = requiredScripts = a;
        return null;
    }

    public static HtmlString EmitRequiredBundleStyles(this HtmlHelper html)
    {
        var requiredStyles = HttpContext.Current.Items["RequiredStyles"] as IHtmlString;
        if (requiredStyles == null) return null;
        return MvcHtmlString.Create(requiredStyles.ToHtmlString()) ;
    }

    public static HtmlString EmitRequiredBundleScripts(this HtmlHelper html)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as IHtmlString;
        if (requiredScripts == null) return null;
        return MvcHtmlString.Create(requiredScripts.ToHtmlString());
    }

    public static HtmlString EmitRequiredScripts(this HtmlHelper html)
    {
        var requiredScripts = HttpContext.Current.Items["RequiredScripts"] as List<ResourceInclude>;
        if (requiredScripts == null) return null;
        StringBuilder sb = new StringBuilder();
        foreach (var item in requiredScripts.OrderByDescending(i => i.Priority))
        {
            sb.AppendFormat("<script src=\"{0}\" type=\"text/javascript\"></script>\n", item.Path);
        }
        return new HtmlString(sb.ToString());
    }
    public class ResourceInclude
    {
        public string Path { get; set; }
        public int Priority { get; set; }
    }
}//end class
}// end namespace  

关于PartialView的示例: @Html.RequireBundleStyles("~/bundles/fileupload/bootstrap/BasicPlusUI/css"); @Html.RequireBundleScripts("~/bundles/fileupload/bootstrap/BasicPlusUI/js");

关于MasterPage的示例: @Html.EmitRequiredBundleStyles()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接