5

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

[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());
}
}

现在它将脚本放置在标记元素旁边作为其兄弟元素。

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

4

7 回答 7

7

我创建了一对能够解决您的问题的自定义标签助手。

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

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

代码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"那么您可以使用以下命令仅将第一组渲染为某个位置:

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

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

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

  2. 不要忘记使用命令在 _ViewImports.cshtml 文件中引用您的自定义标签助手@addTagHelper "*, YourProjectHere"

抱歉发了这么长的帖子,希望对您有所帮助!

于 2015-08-12T14:36:46.417 回答
4

创建一个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;
        }
    }
}

希望这可以帮助!

于 2015-08-05T17:55:44.030 回答
1

@section scripts {}在 Layout 上呈现一个,@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>
}
于 2015-07-09T17:42:54.310 回答
0

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

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

验证 taghelper 所做的只是用 data- 属性装饰跨度,它不会向页面添加任何脚本,并且如果缺少脚本,则会忽略 data- 属性。

考虑到一个视图可能使用了多个验证 taghelper,我们不希望每个都添加另一个脚本。

在 VS 入门 Web 应用程序模板中,您可以看到验证脚本是由视图底部的部分视图添加的(例如 Login.cshtml)

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

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

但我自己认为,在需要支持使用 taghelper 的地方添加脚本比尝试花哨并自动添加东西更直接。

这个想法只适用于外部 js 文件,不适用于在 taghelper 内动态编写的 js,但最好不要有这样的脚本,并且尽可能只使用外部脚本文件。如果您确实需要在 taghelper 中生成脚本,我认为您只能在 taghelper 正在处理的元素的位置呈现它。

于 2015-07-09T14:47:48.383 回答
0

也许不是最优雅的解决方案,但仍然有效:

将要生成的标签包装在 a 中span,然后将一些 HTML 附加到InnerHtmlthis 中span

 myTag = new TagBuilder("span");
 var mymask = htmlGenerator.GenerateTextBox(...);
 myTag.InnerHtml.AppendHtml(mymask);
 myTag.InnerHtml.AppendHtml(@"<script>...</script>");
于 2019-02-20T14:31:48.363 回答
0

我知道这个线程很旧,但如果有人正在寻找一个简单的修复来运行一些 javascript,这是一种方法。

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

但通常您的标签助手将数据作为与客户端脚本相关的输入。为了能够通过 js 函数运行这些数据,您可以执行以下操作。

TagHelper.cs

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

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

网站.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})`);
});

现在,当脚本准备好后,他们可以查找您的标签助手并使用相关数据执行适当的功能。

于 2017-07-15T13:08:35.067 回答
-1

您可以更进一步,将您的 html(taghelper)与您的 javascript 完全分开,而不是将 javascript 放在页面底部。编写您的 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");
    });
}
于 2016-06-30T01:39:21.037 回答