Populate a Razor Section From a Partial

2019-01-05 09:44发布

My main motivation for trying to do this is to get Javascript that is only required by a partial at the bottom of the page with the rest of the Javascript and not in the middle of the page where the partial is rendered.

Here's a simplified example of what I'm trying to do:

Here is the layout with a Scripts section right before the body.

<!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>

Here's an example view using this layout.

<h2>This is the view</h2>

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

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

And here's the partial being rendered from the view.

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

In this example, the markup specified in the view is placed into the section, but the markup from the partial is not. Is it possible to populate a section from a partial view with Razor? If not, what are some other methods of getting Javascript that's only needed by partials at the bottom of the page without including it globally?

11条回答
女痞
2楼-- · 2019-01-05 10:33

You can create a new Layout page and wrap the PartialView inside of a Full View that is responsible for rendering the contents and also any library sections.

For example, let's say I have the following code:

HomeController.cs

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

When the Full Page view is rendered, it's typically renderedby merging two files:

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 (or whatever is specified in _ViewStart or overridden in the page)

<!DOCTYPE html>

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

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

Now, suppose you wanted to Render About.cshtml as a Partial View, perhaps as modal window in response to AJAX call. The goal here being to return only the content specified in the about page, scripts and all, without all the bloat included in the _Layout.cshtml master layout (like a full <html> document).

You might try it like this, but it won't come with any of the section blocks:

return PartialView("About", vm);

Instead, add a simpler layout page like this:

_PartialLayout.cshtml

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

Or to support a modal window like this:

_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>

Then you can specify a custom Master View in this controller or any other handler that you want to render the contents and scripts of a view simultaneously

[HttpGet]
public ActionResult About()
{
    var vm = new AboutViewModel();
    return !Request.IsAjaxRequest()
              ? View("About", vm)
              : View("About", "~/Views/Shared/_ModalLayout.cshtml", vm);
}
查看更多
啃猪蹄的小仙女
3楼-- · 2019-01-05 10:39

The way I dealt with this is to write a couple extension methods to the HtmlHelper class. That allows partials views to say that they require a script, and then in the layout view that writes the tag I call to my helper method to emit the required scripts

Here are the helper methods:

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

Once you have that in place your partial view just needs to call @Html.RequireScript("/Path/To/Script").

And in the layout view's head section you call @Html.EmitRequiredScripts().

An added bonus of this is that it allows you to weed out duplicate script requests. If you have multiple views/partial views that need a given script you can safely assume that you will only output it once

查看更多
做个烂人
4楼-- · 2019-01-05 10:40

Install the Forloop.HtmlHelpers nuget package - it adds some helpers for managing scripts in partial views and editor templates.

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

@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.

  1. Layout
  2. Partials and Templates (in the order in which they appear in the view, top to bottom)
查看更多
神经病院院长
5楼-- · 2019-01-05 10:42

Partial views cannot participate in their parent views' sections.

查看更多
我只想做你的唯一
6楼-- · 2019-01-05 10:44

Base on the answer from Mr Bell And Shimmy above, I add on extra function for Bundle script.

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  

Sample on PartialView :- @Html.RequireBundleStyles("~/bundles/fileupload/bootstrap/BasicPlusUI/css"); @Html.RequireBundleScripts("~/bundles/fileupload/bootstrap/BasicPlusUI/js");

Sample on MasterPage :- @Html.EmitRequiredBundleStyles()

查看更多
登录 后发表回答