我的目标是创建一个类似于剃刀@section Scripts {...}的语法,它在Views和ViewComponents中同样有效。
如果我将JavaScript转换为Windows字符串,我可以执行此操作via helper methods。然而,这会破坏智能感知,让你进入角色逃避地狱,并且不允许你在渲染之前对脚本进行重复数据删除和排序。
我想以允许Visual Studio编辑器将JavaScript编辑为JavaScript的方式开展此工作。似乎我应能够做到这样的事情:
<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);
}
类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);
}
}
}
我已尝试使用以下内容将脚本呈现为字符串,但它会在.RenderAsync调用中抛出异常,并以503.2错误中止页面:
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();
}
}
答案 0 :(得分:1)
您可以使用tag helpers实现此行为。
假设您创建了一个标记助手InlineScriptConcatenatorTagHelper
,目标是<script>
标记,您基本上从输出中删除其内容,但将其保留在内存中供以后使用:
[HtmlTargetElement("script", Attributes = "inline-bundle-add")]
public class InlineScriptConcatenatorTagHelper: TagHelper
{
private IHttpContextAccessor httpContextAccessor;
public InlineScriptConcatenatorTagHelper(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
[HtmlAttributeName("inline-bundle-add")]
public string BundleName { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
//Get the script contents
var contents = await context.GetChildContentAsync();
var scriptContent = contents.GetContent();
//Save them into the http Context
if (httpContextAccessor.HttpContext.Items.ContainsKey(BundleName))
{
var scripts = httpContextAccessor.HttpContext.Items[BundleName] as ICollection<string>;
scripts.Add(scriptContent);
}
else
{
httpContextAccessor.HttpContext.Items[BundleName] = new List<string> { scriptContent };
}
//suppress any output
output.SuppressOutput();
}
}
然后,您可以创建一个类似的标记帮助器InlineScriptTagHelper
,您将基本上连接并呈现从前一个帮助程序收集的所有内容:
[HtmlTargetElement("script", Attributes = "inline-bundle-render")]
public class InlineScriptTagHelper : TagHelper
{
private IHttpContextAccessor httpContextAccessor;
public InlineScriptTagHelper(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
[HtmlAttributeName("inline-bundle-render")]
public string BundleName { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
//if no scripts were added, suppress the contents
if (!httpContextAccessor.HttpContext.Items.ContainsKey(BundleName))
{
output.SuppressOutput();
return;
}
//Otherwise get all the scripts for the bundle
var scripts = httpContextAccessor.HttpContext.Items[BundleName] as ICollection<string>;
//Concatenate all of them as set them as the contents of this tag
output.Content.SetContentEncoded(String.Join("", scripts));
}
}
有了这个,您可以在视图中添加任意数量的脚本块,并为它们分配一个内联包名称:
<script inline-bundle-add="myInlineBundle">
var title = '@ViewData["Title"]';
var greet = function (message) {
console.log(message);
}
</script>
...
<script inline-bundle-add="myInlineBundle">
greet(title);
</script>
然后在 _Layout.cshtml 中添加一个脚本元素,该元素将呈现具有相同包名称的所有内联脚本的连接输出:
...
<script inline-bundle-render="myInlineBundle"></script>
</body>
渲染输出将包含一个脚本元素,用于连接内联包中包含的所有脚本:
...
<script>
var title = 'Home Page';
var greet = function (message) {
console.log(message);
}
greet(title);
</script>
</body>
不要忘记在 _ViewImports.cshtml 文件中添加@addTagHelper
指令,在程序集中注册标记帮助程序
修改强>
查看@SvdSinner创建的github project。它采用了此处描述的方法,并创建了一个支持重复数据删除和依赖顺序的标记帮助程序。 (旨在支持缩小并提供nuget包)