如何将在TagHelper流程方法中生成的脚本渲染到页面底部而不是标记元素旁边?

时间:2015-07-09 09:31:49

标签: asp.net-core tag-helpers

我在TagHelper类的处理方法中生成脚本如下

[TargetElement("MyTag")]
    public Class MYClass: TagHelper{
      public override void Process(TagHelperContext context, TagHelperOutput output)
        {
StringBuilder builder = new StringBuilder();

                builder.Append("<script>");
                builder.Append("//some javascript codes here);
                builder.Append("</script>");
                output.Content.Append(builder.ToString());
}
}

现在它将脚本放在标签元素的旁边,作为它的兄弟。

我需要将脚本放在正文部分的末尾。

7 个答案:

答案 0 :(得分:4)

我创建了一对能够解决问题的自定义标记帮助程序。

第一个是<storecontent>,它只是将包含在其中的html内容存储在TempData字典中。它不提供直接输出。内容可以是内联脚本或任何其他html。许多这种标签帮助器可以放置在不同的位置,例如在部分视图中。

第二个标记助手是<renderstoredcontent>,它将所有先前存储的内容呈现在所需位置,例如在body元素的末尾。

StoreContentTagHelper.cs的代码:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;


namespace YourProjectHere.TagHelpers
{
    [TargetElement("storecontent", Attributes = KeyAttributeName)]
    public class StoreContentTagHelper : TagHelper
    {
        private const string KeyAttributeName = "asp-key";
        private const string _storageKey = "storecontent";
        private const string _defaultListKey = "DefaultKey";

        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }

        [HtmlAttributeName(KeyAttributeName)]
        public string Key { get; set; }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            output.SuppressOutput();
            TagHelperContent childContent = await context.GetChildContentAsync();

            var storageProvider = ViewContext.TempData;
            Dictionary<string, List<HtmlString>> storage;
            List<HtmlString> defaultList;

            if (!storageProvider.ContainsKey(_storageKey) || !(storageProvider[_storageKey] is Dictionary<string,List<HtmlString>>))
            {
                storage = new Dictionary<string, List<HtmlString>>();
                storageProvider[_storageKey] = storage;
                defaultList = new List<HtmlString>();
                storage.Add(_defaultListKey, defaultList);
            }
            else
            {
                storage = ViewContext.TempData[_storageKey] as Dictionary<string, List<HtmlString>>;
                if (storage.ContainsKey(_defaultListKey))
                {
                    defaultList = storage[_defaultListKey];

                }
                else
                {
                    defaultList = new List<HtmlString>();
                    storage.Add(_defaultListKey, defaultList);
                }
            }

            if (String.IsNullOrEmpty(Key))
            {
                defaultList.Add(new HtmlString(childContent.GetContent()));
            }
            else
            {
                if(storage.ContainsKey(Key))
                {
                    storage[Key].Add(new HtmlString(childContent.GetContent()));
                }
                else
                {
                    storage.Add(Key, new List<HtmlString>() { new HtmlString(childContent.GetContent()) });
                }
            }
        }
    } 
} 

RenderStoredContentTagHelper.cs的代码:

using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;


namespace YourProjectHere.TagHelpers
{
    [TargetElement("renderstoredcontent", Attributes = KeyAttributeName)]
    public class RenderStoredContentTagHelper : TagHelper
    {
        private const string KeyAttributeName = "asp-key";
        private const string _storageKey = "storecontent";

        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }

        [HtmlAttributeName(KeyAttributeName)]
        public string Key { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.TagName = String.Empty;

            var storageProvider = ViewContext.TempData;
            Dictionary<string, List<HtmlString>> storage;

            if (!storageProvider.ContainsKey(_storageKey) || !(storageProvider[_storageKey] is Dictionary<string, List<HtmlString>>))
            {
                return;
            }

            storage = storageProvider[_storageKey] as Dictionary<string, List<HtmlString>>;
            string html = "";

            if (String.IsNullOrEmpty(Key))
            {
                html = String.Join("", storage.Values.SelectMany(x => x).ToList());
            }
            else
            {
                if (!storage.ContainsKey(Key)) return;
                html = String.Join("", storage[Key]);
            }

            TagBuilder tagBuilder = new TagBuilder("dummy");
            tagBuilder.InnerHtml = html;
            output.Content.SetContent(tagBuilder.InnerHtml);
        }
    } 
} 

基本用法:

在某些视图或部分视图中:

<storecontent asp-key="">
  <script>
    your inline script...
  </script>
</storecontent>

在另一个地方:

<storecontent asp-key="">
  <script src="..."></script>
</storecontent>

最后在应该呈现两个脚本的所需位置:

<renderstoredcontent asp-key=""></renderstoredcontent>

就是这样。

一些注意事项:

  1. 可以有任意数量的<storecontent>代码。 asp-key属性是必需的,至少为空“”。如果为此属性指定特定值,则可以对存储的内容进行分组,并在不同位置呈现特定组。例如。如果您使用asp-key="scripts"指定某些内容,而使用asp-key="footnotes"指定其他内容,则可以使用以下方式仅将第一个群组渲染为某个位置:
  2. <renderstoredcontent asp-key="scripts"></renderstoredcontent>

    另一组“脚注”可以在其他位置呈现。

    1. 必须在应用<storecontent>之前定义<renderstoredcontent>。在ASP.NET中,响应以反向层次顺序生成,首先生成最内部的部分视图,然后是父部分视图,然后是主视图,最后是布局页面。因此,您可以轻松地使用这些标记帮助程序在局部视图中定义脚本,然后在布局页面的正文部分的末尾渲染脚本。

    2. 不要忘记使用@addTagHelper "*, YourProjectHere"

    3. 命令在_ViewImports.cshtml文件中引用自定义标记助手

      对不起,很长的帖子,我希望它有所帮助!

答案 1 :(得分:3)

创建BodyTagHelper,将值插入TagHelperContext.Items,然后在自定义TagHelper中设置。

完整的代码:

public class BodyContext
{
    public bool AddCustomScriptTag { get; set; }
}

public class BodyTagHelper : TagHelper
{
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var builder = new StringBuilder();

        var bodyContext = new BodyContext();

        context.Items["BodyContext"] = bodyContext;

        // Execute children, they can read the BodyContext
        await context.GetChildContentAsync();

        if (bodyContext.AddCustomScriptTag)
        {
            // Add script tags after the body content but before end tag.
            output
                .PostContent
                .Append("<script>")
                .Append("//some javascript codes here")
                .Append("</script>");
        }
    }
}

[TargetElement("MyTag")]
public class MYClass : TagHelper
{
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        // Do whatever you want

        object bodyContextObj;
        if (context.Items.TryGetValue("BodyContext", out bodyContextObj))
        {
            // Notify parent that we need the script tag
            ((BodyContext)bodyContextObj).AddCustomScriptTag = true;
        }
    }
}

希望这有帮助!

答案 2 :(得分:1)

使用@section scripts {}在布局上呈现@RenderSection("scripts"),并将标记帮助器放在脚本部分中。渲染时,它将放置在Layout(在html底部)上定义的位置。

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <div>
        <p>some html ... bla bla bla</p>
        @RenderBody()
    </div>
    @RenderSection("scripts", required: false)
</body>
</html>

然后在任何其他cshtml文件上,

<p>Some page</p>
@section scripts {
    <mytaghelper>foo</mytaghelper>
}

答案 3 :(得分:0)

我不相信tagHelper内部可以在底部或其他任何地方添加脚本,但taghelper正在呈现的标记位置。我认为如果taghelper依赖于某些外部js文件,那么添加脚本不应该是taghelper本身的责任。例如,内置验证标记符如:

<span asp-validation-for="Email" class="text-danger"></span>

所有验证taghelper都是用数据属性装饰span,它不会向页面添加任何脚本,如果脚本丢失,数据属性将被忽略。

考虑一个视图可能有多个验证标记使用,我们不希望每个添加另一个脚本。

在VS starter web app模板中,您可以看到验证脚本是由视图底部的部分视图添加的(例如Login.cshtml)

@{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }

自动化脚本包含的一种可能策略是你的tagHelper可以在其构造函数中使用IHttpContextAccessor,因此它将由DI注入,然后你可以访问HttpContext.Items集合并添加一个变量来指示需要一个脚本,然后在添加脚本的局部视图,您可以检测添加的变量,以决定要包含哪些脚本。

但是我自己认为只需在需要的地方添加脚本来支持taghelper的使用,而不是试图自动添加并自动添加内容。

这个想法只适用于外部js文件而不适用于在taghelper中动态编写的js,但最好不要使用这样的脚本,并且只在可能的情况下才使用外部脚本文件。如果你真的需要在taghelper中生成脚本,我想你只能在taghelper正在处理的元素的位置呈现它。

答案 4 :(得分:0)

不要将javascript放在页面底部,而是可以更进一步,从你的javascript中完全分离你的html(taghelper)。编写您的Javascript,以便找到您的taghelper并初始化自己。

这里的示例是我使用的Taghelper / Javascript,它采用UTC日期时间并在用户本地时间显示,格式化为日期时间,时间或日期。

标签助手

[HtmlTargetElement("datetime", Attributes = "format,value")]
public class DateTimeTagHelper : TagHelper {

    [HtmlAttributeName("format")]
    public DateTimeFormat Format { get; set; }

    [HtmlAttributeName("value")]
    public DateTime Value { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output) {

        output.TagName = "span";
        output.TagMode = TagMode.StartTagAndEndTag;

        output.Attributes.Add("class", "datetime_init");
        output.Attributes.Add("format", Format);
        output.Attributes.Add("value", Value.ToString("u"));

    }
}

Javascript(需要moment.js但与概念无关)

$(document).ready(function () {
    DateTime_Init();
}

function DateTime_Init() {
    $(".datetime_init").each(function () {
        var utctime = $(this).attr("value");
        var localTime = moment.utc(utctime).toDate();

        switch($(this).attr("format")) {
            case "Date":
                $(this).html(moment(localTime).format('DD/MM/YYYY'));
                break;
            case "Time":
                $(this).html(moment(localTime).format('HH:mm'));
                break;
            default:
                $(this).html(moment(localTime).format('DD/MM/YYYY HH:mm'));
                break;
        }

        //Ensure this code only runs once
        $(this).removeClass("datetime_init");
    });
}

答案 5 :(得分:0)

我知道这个帖子已经过时了,但如果有人正在寻找一个简单的方法来运行一些javascript,这是一种方式。

首先,ViewComponents渲染服务器端,所以当然客户端脚本不会准备就绪。正如其他人指出的那样,你可以在需要的地方渲染一些部分脚本来解释你的标签帮助器,这对于解耦非常有用,你只需要在需要的地方包含脚本。

但是,您的标记帮助程序通常将数据作为与客户端脚本相关的输入。为了能够通过js函数运行这些数据,你可以做这样的事情。

<强> TagHelper.cs

var data= $@"
        '{Id}', 
        '{Title}', 
        {JsonConvert.SerializeObject(MyList)}";

output.Attributes.SetAttribute("data-eval", data);

<强> site.js

$(".tag-helper-class").each((i, e) => {
    const jq = $(e);

    const data= jq.data("eval");

    if (!data) {
        return;
    }
    jq.attr("data-eval", "");
    eval(`myJsFunction(${data})`);
});

现在,当脚本准备就绪时,他们可以查找你的标签助手并使用相关数据执行正确的功能。

答案 6 :(得分:0)

也许不是最优雅的解决方案,但仍然可以使用:

将要生成的标签包装在span内部,然后将一些HTML附加到此InnerHtml的{​​{1}}中:

span