MVC 6更改视图块呈现的位置

时间:2015-10-26 18:40:42

标签: javascript asp.net-mvc razor asp.net-core-mvc

我的目标是创建一个类似于剃刀@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();
    }
}
  1. 我错过了一个更简单的解决方案吗?有没有更简单的方法来渲染Razor标记的结果并将其传递给html辅助方法?
  2. 如何将@using块内的ViewContext渲染为文本?
  3. 如何防止ViewContext与其余视图一起呈现? (这样我以后可以在页面上呈现它)

1 个答案:

答案 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包)