This answer is for .netcore 2.1. Short Answer, inject an instance of Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager that takes the javascript needed for the components UI functionality as the last node of the body element.
Long answer how I got here and what works for me.
I have many widgets that my app uses that have the same HTML structure, style, and UI functionality & logic but get fed with different data sets.
My app uses identity claims (roles) to control navigation dynamically. Those roles also determine the data sets that feed the widgets that can be combined to provide different views of that data on a single page.
I use Areas to keep that code separated. In order to facilitate further separation of concerns my app needs to keep the widget code (data retrieval, markup, styles, and functionality) separate I chose to go with ViewComponents.
I had the same problem in that my functionality depends on javascript libraries that I want to make available to the entire app inside of the shared/_layout file.
My scripts render is the last element on the _layout file before the body tag. My body render is several nodes up the structure, so my need was to somehow get the javascript I need appended to the collection of child nodes in the body element. Otherwise, my components would fail to create the necessary javascript objects to provide the needed functionality.
At the root of this solutions is the TagHelperComponent and the TagHelperComponentManager. I created a class that derives from the abstract base class implementation of the ITagHelperComponent this file and placed it in ./Pages/Shared/
Here is my ScriptTagHelper
public class ScriptTagHelper : TagHelperComponent
{
private readonly string _javascript;
public ScriptTagHelper(string javascript = "") {
_javascript = javascript;
}
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (string.Equals(context.TagName, "body", StringComparison.Ordinal))
{
output.PostContent.AppendHtml($"<script type='text/javascript'>{_javascript}</script>");
}
return Task.CompletedTask;
}
}
The code takes the javascript string as a constructor argument to the tag helper. The ProcessAsync task is override to find the body tag and append the javascript.
The javascript string is wrapped in the Html script tag, and that complete Html string is appended to the end of whatever content is already in the body. (that is any script sections inserted on the page by render script, and any script on the layout page).
The way this tag helper is used is via dependency injection inside of the ViewComponent. Here is an example of a widget wired up to create an instance of the ScriptTagHelper.
This is my Data Usage Distribution widget. It will eventually display the distribution of data usage in a pie chart, that when floating on a particular wedge of the chart will also show more data highlights of that particular data segment. For now, its just the template that all of my widgets will use to help me set up for assigning tasks to the team to start building out the resources.
This file is ./Pages/Shared/Components/DataDistribution/Default.cshtml
@model dynamic;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager tagManager;
<div class="col-md-3 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<p class="card-title text-md-center text-xl-left">Data Usage Distribution</p>
<div class="d-flex flex-wrap justify-content-between justify-content-md-center justify-content-xl-between align-items-center">
<h3 class="mb-0 mb-md-2 mb-xl-0 order-md-1 order-xl-0">40016</h3>
<i class="ti-agenda icon-md text-muted mb-0 mb-md-3 mb-xl-0"></i>
</div>
<p class="mb-0 mt-2 text-success">10.00%<span class="text-body ml-1"><small>(30 days)</small></span></p>
</div>
</div>
</div>
@{
Layout = null;
string scriptOptions = Newtonsoft.Json.JsonConvert.SerializeObject(Model);
string script = @"";
script += $"console.log({scriptOptions});";
tagManager.Components.Add(new ScriptTagHelper(javascript: script));
}
By injecting the default implementation of the ITagHelperComponentManager, we get access to the collection of tag helper components and can add an implementation of our own tag helper. This tag helpers job is to put a script tag containing our javascript for this view component at the bottom of our body tag.
In this simple code stub, I can now safely and reliably expect all of my supporting UI libraries to be loaded before I start rendering the script that will use those libraries to modify my UI. Here I am just console logging my serialized model returned from my view component. So running this code won't actually prove it works, but, anyone can change the script variable to console log the jquery version if you want to make sure jquery is loaded in your layout file.
Hope this helps someone. I have heard that changes are coming to support sections in .netcore 3, but those problems people are having are already solved natively and rather elegantly in 2.1, as I hope to have shown here.