将内容从局部视图注入到特定的部分。NET MVC 3与Razor视图引擎

我在_Layout.cshtml中定义了这个section

@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部分中注入一些内容。

我该怎么做呢?

238376 次浏览

我能想到的第一个解决方案是使用ViewBag来存储必须呈现的值。

我从来没有试过,如果这个工作从一个局部的观点,但它应该在我看来。

遵循不引人注目的原则,“_myPartial”并不完全需要将内容直接注入脚本部分。你可以将这些部分视图脚本添加到单独的.js文件中,并从父视图中将它们引用到@scripts部分。

section不能在局部视图中工作,这是设计的。你可以使用一些自定义帮助程序来实现类似的行为,但老实说,包含必要的脚本是视图的责任,而不是部分的责任。我建议使用主视图的@scripts部分来实现这一点,而不用担心脚本的部分。

这是一个很常见的问题,所以我会把我的答案贴出来。

我也遇到过同样的问题,虽然它不是理想的,但我认为它实际上工作得很好,并且不使部分依赖于视图。

我的场景是,一个动作本身是可访问的,但也可以嵌入到一个视图中——一个谷歌映射。

在我的_layout中我有:

@RenderSection("body_scripts", false)

index视图中,我有:

@Html.Partial("Clients")
@section body_scripts
{
@Html.Partial("Clients_Scripts")
}

在我的clients视图中,我有(所有的map和assoc。html):

@section body_scripts
{
@Html.Partial("Clients_Scripts")
}

我的Clients_Scripts视图包含要呈现到页面上的javascript。

通过这种方式,我的脚本是隔离的,并且可以在需要的地方呈现到页面中,而body_scripts标记只在剃须刀视图引擎发现它的第一次出现时呈现。

这让我把所有的东西都分开了——这对我来说是一个很好的解决方案,其他人可能有问题,但它确实修补了“设计”。洞。

我们看待网络的方式有一个根本性的缺陷,尤其是在使用MVC的时候。缺陷在于JavaScript在某种程度上是视图的责任。视图是视图,JavaScript(行为或其他)是JavaScript。在Silverlight和WPF的MVVM模式中,我们面临着“视图优先”或“模型优先”的问题。在MVC中,我们应该总是尝试从模型的角度进行推理,而JavaScript在很多方面都是这个模型的一部分。

我建议使用AMD模式(我自己喜欢RequireJS)。在模块中分离你的JavaScript,定义你的功能,从JavaScript中钩子到你的html,而不是依赖于一个视图来加载JavaScript。这将清理您的代码,分离您的关注点,并使生活变得更简单。

这个线程中的解决方案中,我提出了以下可能过于复杂的解决方案,它可以让你在using块中延迟呈现任何html(脚本)。

使用

创建section

  1. 在局部视图中,无论局部视图在页面中重复了多少次,都只包含一次块:

    @using (Html.Delayed(isOnlyOne: "some unique name for this section")) {
    <script>
    someInlineScript();
    </script>
    }
    
  2. In a partial view, include the block for every time the partial is used:

    @using (Html.Delayed()) {
    <b>show me multiple times, @Model.Whatever</b>
    }
    
  3. In a partial view, only include the block once no matter how many times the partial is repeated, but later render it specifically by name 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>
    }
    

Render the "sections"

(i.e. display the delayed section in a parent view)

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




}

如果你确实需要从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>

你不需要在局部视图中使用section。

包含在部分视图中。 它在jQuery加载后执行函数。

你可以改变你的代码的条件子句
<script type="text/javascript">
var time = setInterval(function () {
if (window.jQuery != undefined) {
window.clearInterval(time);


//Begin
$(document).ready(function () {
//....
});
//End
};
}, 10); </script>

胡里奥·斯派德

有一种方法可以在局部视图中插入部分,尽管它并不漂亮。你需要从父视图中访问两个变量。由于部分视图的部分目的就是创建该section,因此需要这些变量是有意义的。

下面是在局部视图中插入一个section的样子:

@model KeyValuePair<WebPageBase, HtmlHelper>
@{
Model.Key.DefineSection("SectionNameGoesHere", () =>
{
Model.Value.ViewContext.Writer.Write("Test");
});
}

在页面中插入部分视图…

@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))

您还可以使用此技术在任何类中以编程方式定义节的内容。

享受吧!

假设您有一个称为_contact的部分视图。Cshtml,您的联系人可以是合法的(姓名)或物理主体(名,姓)。你的视图应该关心被渲染的内容,这可以用javascript实现。所以延迟渲染和JS内部视图可能需要。

我认为唯一可以忽略它的方法是,当我们创建一种不引人注目的方式来处理这种UI关注点时。

还要注意MVC 6将有一个所谓的视图组件,甚至MVC未来也有一些类似的东西,Telerik也支持这样的东西…

我解决这个完全不同的路线(因为我很着急,不想实现一个新的HtmlHelper):

我用一个大的if-else语句包装了我的Partial View:

@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 ***{
@RenderSection("****", required: false)
}

我想这是一种很好的注射方式。

您可以使用这些扩展方法:(另存为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);
}
}
}
}
}

像这样使用:

示例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");}

我有一个类似的问题,我有一个母版页如下:

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

好吧,我猜其他海报已经为你提供了一种直接在你的部分中包含@section的方法(通过使用第三方html助手)。

但是,我认为,如果你的脚本与你的partial紧密耦合,只需将javascript直接放入内联<script>标记中即可在你的partial中并完成它(只是要小心脚本复制,如果你打算在一个视图中多次使用partial);

你可以选择使用你的文件夹/索引。CSHTML作为母版,然后添加节脚本。然后,在你的布局中,你有:

@RenderSection("scripts", required: false)

和index.cshtml:

@section scripts{
@Scripts.Render("~/Scripts/file.js")
}

它会在所有的partialviews上工作。这对我很有用

普路托的想法比较好:

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)

这对我来说很有用,允许我在同一个文件中同时定位javascript和html的部分视图。帮助思维过程中看到html和相关部分在同一部分视图文件。


In View使用了分部视图,叫做_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">
}

使用Mvc核心,你可以创建一个整洁的TagHelper scripts,如下所示。这可以很容易地转换为section标记,在那里您也可以给它一个名称(或者名称取自派生类型)。注意,需要为IHttpContextAccessor设置依赖注入。

当添加脚本时(例如在部分中)

<scripts>
<script type="text/javascript">
//anything here
</script>
</scripts>

当输出脚本时(例如在一个布局文件中)

<scripts render="true"></scripts>

代码

public class ScriptsTagHelper : TagHelper
{
private static readonly object ITEMSKEY = new Object();


private IDictionary<object, object> _items => _httpContextAccessor?.HttpContext?.Items;


private IHttpContextAccessor _httpContextAccessor;


public ScriptsTagHelper(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}


public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var attribute = (TagHelperAttribute)null;
context.AllAttributes.TryGetAttribute("render",out attribute);


var render = false;


if(attribute != null)
{
render = Convert.ToBoolean(attribute.Value.ToString());
}


if (render)
{
if (_items.ContainsKey(ITEMSKEY))
{
var scripts = _items[ITEMSKEY] as List<HtmlString>;


var content = String.Concat(scripts);


output.Content.SetHtmlContent(content);
}
}
else
{
List<HtmlString> list = null;


if (!_items.ContainsKey(ITEMSKEY))
{
list = new List<HtmlString>();
_items[ITEMSKEY] = list;
}


list = _items[ITEMSKEY] as List<HtmlString>;


var content = await output.GetChildContentAsync();


list.Add(new HtmlString(content.GetContent()));
}
}
}

我刚刚在我的部分视图上添加了这段代码,解决了这个问题,虽然不是很干净,但它是有效的。你必须确保你渲染的对象的id。

<script>
$(document).ready(function () {
$("#Profile_ProfileID").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#TitleID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#CityID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#GenderID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#PackageID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
});
</script>

OP的目标是将内联脚本定义到他的Partial View中,我假设这个脚本仅特定于该Partial View,并将该块包含到他的脚本部分中。

我明白他想要局部视图是独立的。这个想法类似于使用Angular时的组件。

我的方法是将脚本原样保存在Partial View中。现在的问题是,当调用Partial View时,它可能会在所有其他脚本(通常被添加到布局页面的底部)之前执行脚本。在这种情况下,您只需让Partial View脚本等待其他脚本。有几种方法可以做到这一点。我之前使用过的最简单的方法是在body上使用事件。

在我的布局中,我会在底部有这样的东西:

// global scripts
<script src="js/jquery.min.js"></script>
// view scripts
@RenderSection("scripts", false)
// then finally trigger partial view scripts
<script>
(function(){
document.querySelector('body').dispatchEvent(new Event('scriptsLoaded'));
})();
</script>

然后在我的局部视图(在底部):

<script>
(function(){
document.querySelector('body').addEventListener('scriptsLoaded', function() {


// .. do your thing here


});
})();
</script>

另一种解决方案是使用堆栈来推送所有脚本,并在最后调用每个脚本。其他解决方案,如前所述,是RequireJS/AMD模式,它也非常有效。

我今天遇到了这个问题。我将添加一个工作区,使用<script defer>,因为我没有看到其他答案提到它。

//on a JS file somewhere (i.e partial-view-caller.js)
(() => <your partial view script>)();


//in your Partial View
<script src="~/partial-view-caller.js" defer></script>


//you can actually just straight call your partial view script living in an external file - I just prefer having an initialization method :)

上面的代码摘自我针对这个问题所做的快速发布

我的解决方案是从布局页面加载脚本。然后在javacript中,检查局部视图中是否存在一个元素。如果元素存在,javascript就知道该部分已被包含。

$(document).ready(function () {
var joinButton = $("#join");
if (joinButton.length != 0) {
// the partial is present
// execute the relevant code
}
});