视图组件中的Javascript

我有一个View组件,在Razor(.cshtml)文件中包含一些jQuery。 脚本本身非常特定于视图(处理一些第三方库的配置),因此我想将脚本和HTML保留在同一个文件中,以便组织起见。

问题是脚本没有在_Layout Scripts部分中呈现。 显然,这就是MVC处理与View组件有关的脚本的方式。

我可以通过在Razor文件中使用脚本来解决它, 不能在Scripts部分内部

但后来我遇到依赖问题 – 因为在引用库之前使用了jQuery(对库的引用位于_Layout文件的底部附近)。

除了将jQuery的引用作为Razor代码的一部分包含在内之外,还有什么聪明的解决方案(这会阻碍HTML呈现放置组件的位置)吗?

我目前不在代码的前面,但是如果有机会,如果有人需要查看它以更好地理解它,我当然可以提供它。

我决定做的是编写一个提供“OnContentLoaded”属性的ScriptTagHelper。 如果为true,那么我将使用Javascript函数包装脚本标记的内部内容,以便在文档准备好后执行。 这避免了当ViewComponent的脚本触发时jQuery库尚未加载的问题。

[HtmlTargetElement("script", Attributes = "on-content-loaded")] public class ScriptTagHelper : TagHelper { ///  /// Execute script only once document is loaded. ///  public bool OnContentLoaded { get; set; } = false; public override void Process(TagHelperContext context, TagHelperOutput output) { if (!OnContentLoaded) { base.Process(context, output); } else { var content = output.GetChildContentAsync().Result; var javascript = content.GetContent(); var sb = new StringBuilder(); sb.Append("document.addEventListener('DOMContentLoaded',"); sb.Append("function() {"); sb.Append(javascript); sb.Append("});"); output.Content.SetHtmlContent(sb.ToString()); } } } 

ViewComponent中的示例用法:

  

可能有一个比这更好的方法,但到目前为止这对我有用。

仅在视图中工作的节不适用于部分视图视图组件Default.cshtml独立于主ViewResult执行,默认情况下其Layout值为null 。)这是设计的。

您可以使用它来render sections partial viewview component's视图声明的Layoutpartial view 。 但是,在partials和view组件中定义的节不会回流到渲染视图或它的Layout。 如果要在局部视图或视图组件中使用jQuery或其他库引用,则可以在“ Layout页面中将Layouthead而不是body

例:

查看组件:

 public class ContactViewComponent : ViewComponent { public IViewComponentResult Invoke() { return View(); } } 

ViewComponent的位置:

 /Views/[CurrentController]/Components/[NameOfComponent]/Default.cshtml /Views/Shared/Components/[NameOfComponent]/Default.cshtml 

Default.cshtml:

 @model ViewComponentTest.ViewModels.Contact.ContactViewModel 
Contact Email:
Name:
Message:

_Layout.cshtml

     ... @RenderSection("styles", required: false)    @RenderBody() ... //script move to head @RenderSection("scripts", required: false)   

View.cshtml:

 @{ Layout = "~/Views/Shared/_Layout.cshtml"; } .................. @{Html.RenderPartial("YourPartialView");} .................. @await Component.InvokeAsync("Contact") .................. @section Scripts {  } 

由grahamehorner查看组件:(您可以在此处获取更多信息)

@section脚本不在ViewComponent中呈现,视图组件应该能够在@section中包含不同于partital视图的脚本,因为组件可以在许多视图中重用,组件负责它自己的function。

这是我正在使用的解决方案。 它支持外部脚本加载和自定义内联脚本。 虽然一些设置代码需要放在您的布局文件中(例如_Layout.cshtml),但所有内容都是在View Component中编排的。

布局文件:

在页面顶部附近添加(在标签内部是一个好地方):

  

将其添加到页面底部附近(就在关闭标记之前):

  

查看组件:

现在,您的代码中的任何位置(包括View Component)都可以执行此操作,并在页面底部执行脚本:

  

使用外部脚本依赖项

但有时这还不够。 有时您需要加载外部JavaScript文件,并且只在加载外部文件后才执行自定义脚本。 您还希望在View Component中编排所有内容以进行完全封装。 为此,我在布局页面(或布局页面加载的外部JavaScript文件)中添加了一些设置代码,以延迟加载外部脚本文件。 看起来像这样:

布局文件:

延迟加载脚本

  

查看组件:

现在,您可以在View Component中(或其他任何地方)执行此操作:

  

为了清理一下,这里是View Component的标签帮助器(灵感来自@JakeShakesworth的回答):

 [HtmlTargetElement("script", Attributes = "delay")] public class ScriptDelayTagHelper : TagHelper { ///  /// Delay script execution until the end of the page ///  public bool Delay { get; set; } ///  /// Execute only after this external script file is loaded ///  [HtmlAttributeName("after")] public string After { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { if (!Delay) base.Process(context, output); var content = output.GetChildContentAsync().Result; var javascript = content.GetContent(); var sb = new StringBuilder(); sb.Append("if (!MYCOMPANY || !MYCOMPANY.delay) throw 'MYCOMPANY.delay missing.';"); sb.Append("MYCOMPANY.delay(function() {"); sb.Append(LoadFile(javascript)); sb.Append("});"); output.Content.SetHtmlContent(sb.ToString()); } string LoadFile(string javascript) { if (After == NullOrWhiteSpace) return javascript; var sb = new StringBuilder(); sb.Append("if (!MYCOMPANY || !MYCOMPANY.loadScript) throw 'MYCOMPANY.loadScript missing.';"); sb.Append("MYCOMPANY.loadScript("); sb.Append($"'{After}',"); sb.Append("function() {"); sb.Append(javascript); sb.Append("});"); return sb.ToString(); } } 

现在在我的View组件中,我可以这样做:

  

如果View Component没有外部文件依赖关系,您也可以省略after属性。