首页 > 解决方案 > 添加一个简单的 TagHelper 会导致此错误:“尚未为页面调用 RenderBody”

问题描述

此处提供代码

在 ASP.NET Core 3.0 Web 应用程序中,我添加了以下简单的标签助手:

[HtmlTargetElement("submit-button")]
public class SubmitButtonTagHelper : TagHelper
{
    public string Title { get; set; } = "Submit";
    public string Classes { get; set; }
    public string Id { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.Content.SetHtmlContent(
            $@"<span class=""btn btn-primary {Classes}"" id=""{Id}"">{Title}</span>");
    }
}

我打算这样使用:

<submit-button></submit-button>

但是,添加 SubmitButtonTagHelper 甚至不尝试使用它的行为会导致以下运行时异常:

InvalidOperationException:尚未为“/Views/Shared/_Layout.cshtml”处的页面调用 RenderBody。忽略调用 IgnoreBody()。

我已通过将此行添加到 Pages/_ViewImports.cshtml 文件中来导入标签助手:

@addTagHelper *, Web

我的 _Layout.cshtml 页面如下所示:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><vc:product-name></vc:product-name> @ViewData["Title"]</title>

    @RenderSection("Styles", required: false)

    <partial name="_FrameworkStyles" />
</head>
<body class="top-navigation">

    <div id="wrapper">
        <div id="page-wrapper" class="gray-bg">
            <div class="">
                <vc:Navigation></vc:Navigation>

                @RenderBody()

                <div class="footer"></div>
            </div>
        </div>
    </div>


    <partial name="_FrameworkScripts" />

    @RenderSection("Scripts", required: false)
</body>
</html>

我怎么会在这里出错?在调试时,我可以看到 SubmitButtonTagHelper 中的断点被击中,但我还没有在任何地方引用它?我的理解是 [HtmlTargetElement] 属性意味着它只适用于元素标签是“提交按钮”的地方。这不正确吗?

我的项目中有另一个标签助手,我还注意到该类中的断点也在我没有引用它的地方被命中。

我肯定在做一些愚蠢的事情,但是什么?

标签: c#asp.net-coretag-helpers

解决方案


ASP.NET Core MVC 有Tag Helper Components的概念:

Tag Helper 组件是一个 Tag Helper,它允许您从服务器端代码有条件地修改或添加 HTML 元素。

...

Tag Helper 组件不需要在_ViewImports.cshtml中注册应用程序。

...

Tag Helper 组件的两个常见用例包括:

  1. 将 <link> 注入到 <head> 中。
  2. 将 <script> 注入到 <body> 中。

同样来自文档,这里是标签助手组件的实现:

public class AddressStyleTagHelperComponent : TagHelperComponent
{
    private readonly string _style = 
        @"<link rel=""stylesheet"" href=""/css/address.css"" />";

    public override int Order => 1;

    public override Task ProcessAsync(TagHelperContext context,
                                      TagHelperOutput output)
    {
        if (string.Equals(context.TagName, "head", 
                          StringComparison.OrdinalIgnoreCase))
        {
            output.PostContent.AppendHtml(_style);
        }

        return Task.CompletedTask;
    }
}

一旦注册(如下所示),AddressStyleTagHelperComponent将运行两次:一次用于head元素;一次为body元素。以下是它在 DI 中的注册方式:

services.AddTransient<ITagHelperComponent, AddressScriptTagHelperComponent>();

在这一点上(或者甚至更早),你可能认为我疯了。这有什么关系SubmitButtonTagHelper

通过它的继承树,SubmitButtonTagHelper最终实现ITagHelperComponent. 如果您要添加以下 DI 注册,SubmitButtonTagHelper将作为 Tag Helper 组件运行,一次为head一次,一次为body

services.AddTransient<ITagHelperComponent, SubmitButtonTagHelper>();

SubmitButtonTagHelper是破坏性的,替换它操作的元素的全部内容。如果它要替换元素的内容body,那么主体当然会失去其RenderBody指令。

因此,这是对如果注册为标记帮助程序组件可能发生的情况的详细解释。SubmitButtonTagHelper这正是您上传到 GitHub(源代码)的示例中发生的事情,这不足为奇:

private static void WebRegistration(ContainerBuilder builder)
{
    builder.RegisterAutowiredAssemblyInterfaces(Assembly.Load(Web));
    builder.RegisterAutowiredAssemblyTypes(Assembly.Load(Web));
}

我对 Autofac 了解不多,但很明显,上面显示的调用会针对其所有接口RegisterAutowiredAssemblyInterfaces找到并注册它,包括.SubmitButtonTagHelperITagHelperComponent

同样,我对 Autofac 了解不多,但最终,不打算作为标签助手组件运行的标签助手应该被排除在这个自动注册过程之外。这是关于如何进行此过滤的建议,尽管我认为这是一种糟糕的 Autofac 方法:

builder.RegisterAutowiredAssemblyInterfaces(Assembly.Load(Web))
    .Where(x => !x.Name.EndsWith("TagHelper"));

推荐阅读