HTML helpers are still a thing in ASP.NET Core. Just because tag helpers are the new and generally more flexible solution to render custom HTML, that does not mean that HTML helpers are gone or that they have no use left. The built-in tag helpers are actually based on the HTML helpers and will use the same internal backend to generate the output. So it’s just a different interface for the same thing.
That being said, due to how ASP.NET Core renders views, capturing the content inside a using
block is a bit more difficult compared to how it works in tag helpers (where it’s a very general feature).
I’ve been sitting on this for a while now and came up with the following. This works by temporarily replacing the view writer to a StringWriter
for as long as the block is open. Note that this might be a super terrible idea. But it works…
public static class ScriptHtmlHelper
{
private const string ScriptsKey = "__ScriptHtmlHelper_Scripts";
public static ScriptBlock BeginScripts(this IHtmlHelper helper)
{
return new ScriptBlock(helper.ViewContext);
}
public static IHtmlContent PageScripts(this IHtmlHelper helper)
{
if (helper.ViewContext.HttpContext.Items.TryGetValue(ScriptsKey, out var scriptsData) && scriptsData is List<object> scripts)
return new HtmlContentBuilder(scripts);
return HtmlString.Empty;
}
public class ScriptBlock : IDisposable
{
private ViewContext _viewContext;
private TextWriter _originalWriter;
private StringWriter _scriptWriter;
private bool _disposed;
public ScriptBlock(ViewContext viewContext)
{
_viewContext = viewContext;
_originalWriter = viewContext.Writer;
// replace writer
viewContext.Writer = _scriptWriter = new StringWriter();
}
public void Dispose()
{
if (_disposed)
return;
try
{
List<object> scripts = null;
if (_viewContext.HttpContext.Items.TryGetValue(ScriptsKey, out var scriptsData))
scripts = scriptsData as List<object>;
if (scripts == null)
_viewContext.HttpContext.Items[ScriptsKey] = scripts = new List<object>();
scripts.Add(new HtmlString(_scriptWriter.ToString()));
}
finally
{
// restore the original writer
_viewContext.Writer = _originalWriter;
_disposed = true;
}
}
}
}
Usage will be like this:
@using (Html.BeginScripts()) {
<script>console.log('foo');</script>
<script>console.log('bar');</script>
}
@using (Html.BeginScripts()) {
<script>console.log('baz');</script>
}
And then to render everything:
@Html.PageScripts()