My goal is to create an analog to the razor @section Scripts {...} syntax that will work equally well in Views and ViewComponents.
I can do this via helper methods if I convert the JavaScript to a windows string. However, this destroys intellisense, puts you into character escaping hell and doesn't allow you to de-dup and order the scripts just prior to rendering.
I'd like to make this work in a way that allows the Visual Studio Editor to edit the JavaScript as JavaScript. It seems like I should be able to do something like this:
<div class="StatsDisplay">
label id="@labelId">@Model.DisplayFormat</label>
</div>
@using (Html.BeginNamedScript($"StatDisplay{Model.UniqueId}"))
{
<script>
$.ajax({
url: "@Model.ResultUrl",
method:"POST"
})
.done(function (value) {
var statText = "@Model.DisplayFormat".replace(/\{\s * 0\s *\}/, value);
$("#@labelId").text(statText);
});
</script>
}
HtmlHelperExtension:
public static NamedScript BeginNamedScript(this IHtmlHelper htmlHelper, string name, params string[] dependancies)
{
return new NamedScript(htmlHelper.ViewContext, name, htmlHelper, dependancies);
}
And class NamedScript:
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewFeatures;
namespace WebUIB8.Helpers
{
public class NamedScript : IDisposable
{
private bool _disposed;
private readonly FormContext _originalFormContext;
private readonly ViewContext _viewContext;
private readonly TextWriter _writer;
private readonly string _name;
private readonly HtmlHelper _helper;
private readonly string[] _dependsOn;
public NamedScript(ViewContext viewContext, string name, params string[] dependsOn):this(viewContext, name, null, dependsOn)
{
}
internal NamedScript(ViewContext viewContext, string name, IHtmlHelper helper, params string[] dependsOn)
{
if (viewContext == null)
{
throw new ArgumentNullException(nameof(viewContext));
}
_name = name;
_dependsOn = dependsOn;
_helper = helper as HtmlHelper;
_viewContext = viewContext;
_writer = viewContext.Writer;
Debug.WriteLine("Beginning:\r\n" + _viewContext);
_originalFormContext = viewContext.FormContext;
viewContext.FormContext = new FormContext();
Begin();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Begin()
{
//No beginning action needed
}
private void End()
{
Debug.WriteLine("Ending:\r\n" + _writer);
//NOTE: This chunk doesn't work
//This is supposed to render the script to a string and
// pass it to the helper method that accumulates them, orders
// them, dedups them, and renders them at the proper location
// in the _Layout file so JavaScript loads last, and in dependancy order.
_helper?.AddJavaScript(_name, _writer.ToString(), _dependsOn);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
End();
if (_viewContext != null)
//NOTE: This chunk doesn't work either.
//This is supposed to prevent the code from rendering here.
_viewContext.FormContext = _originalFormContext;
}
}
public void EndForm()
{
Dispose(true);
}
}
}
I've tried the below to render the script to string, but it throws an exception inside the .RenderAsync call and aborts the page with a 503.2 error:
private async Task<string> RenderView(ViewContext viewContext)
{
using (var sw = new StringWriter())
{
var newViewContext = new ViewContext(viewContext, viewContext.View, viewContext.ViewData, sw);
var razorView = newViewContext.View as RazorView;
razorView.RenderAsync(newViewContext).Wait();
sw.Flush();
return sw.ToString();
}
}
- Am I missing a simpler solution? Is there an easier way to render the result of Razor markup and pass it into an html helper method?
- How can I render the ViewContext of inside the @using block into text?
- How can I prevent that ViewContext from rendering with the rest of it's view? (So that I can render it later on the page)