将 Blazor RenderFragment 参数转发给子组件会引发异常
Posted
技术标签:
【中文标题】将 Blazor RenderFragment 参数转发给子组件会引发异常【英文标题】:Forwarding Blazor RenderFragment parameter to child components throws exception 【发布时间】:2021-05-29 11:30:12 【问题描述】:更新:Microsoft 已确认此问题。 (Github issue),并且可能会在 .Net 6 (github issue) 中修复/解决。
我可以使用 RenderFragment 将(子)内容传递给组件。这可以是可选的。采取:
@*Baz.razor*@
@if (Bar is null)
<div>nobar</div>
else
<div>Render bar: @Bar</div>
@code
[Parameter]
public RenderFragment Bar get; set;
我可以的
@*FooPage.razor*@
@page "/foopage"
<Baz />
呈现“nobar”,或
@*FooPage.razor*@
@page "/foopage"
<Baz Bar=@bar/>
@code
private RenderFragment bar = (builder) => builder.AddMarkupContent(0, "<div>bar</div>"); ;
呈现“Render bar: bar”(由于 div 带有换行符)
所以,现在我遇到了一个新情况,其中有一个介于两者之间的组件,称为 Foo
。
@*Foo.razor*@
<Baz Bar=@Bar />
@code
[Parameter]
public RenderFragment Bar get; set;
我将 FooPage.razor 修改为
@*FooPage.razor*@
@page "/foopage"
<Foo />
即我正在将Bar
参数从Foo
转发到Baz
... 这不起作用并且给了我一个相当长的错误(在这个问题的底部)。 (注意:当我在 FooPage 中分配 Bar
时,它确实有效。)
这对我来说很奇怪。我可以检查Bar
是否为空,并使用它。但是,我不能将Bar
显式设置为null,或者传递一个null 引用。
我也直接尝试过这样做:
@*FooPage.razor*@
@page "/foopage"
<Foo Bar=@nullrf />
@code
private RenderFragment nullrf;
这也会引发同样的异常。
即如果你把它排除在外,它就是空的,但是如果你将它分配为空,它就会中断。这是一个错误吗?
ArgumentException: Delegate to an instance method cannot have null 'this'.
System.MulticastDelegate.ThrowNullThisInDelegateToInstance()
System.MulticastDelegate.CtorClosed(object target, IntPtr methodPtr)
BlazorServerDefault.Pages.Foo.BuildRenderTree(RenderTreeBuilder __builder)
Microsoft.AspNetCore.Components.ComponentBase.<.ctor>b__6_0(RenderTreeBuilder builder)
Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
Microsoft.AspNetCore.Components.Rendering.htmlRenderer.HandleException(Exception exception)
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(int componentId, RenderFragment renderFragment)
Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)
Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext+<>c__11<TResult>+<<InvokeAsync>b__11_0>d.MoveNext()
Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType)
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, object parameters)
Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count)
BlazorServerDefault.Pages.Pages__Host.<ExecuteAsync>b__14_1() in _Host.cshtml
+
<component type="typeof(App)" render-mode="ServerPrerendered" />
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
BlazorServerDefault.Pages.Pages__Host.ExecuteAsync() in _Host.cshtml
+
Layout = null;
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
【问题讨论】:
> 这对我来说很奇怪。对我来说不是......根据我自己的经验,如果你想创建一个带有可选页脚模板的模板化组件,你不会为参数属性分配空值,而是跳过添加它,然后检查是否参数属性是否为空。当您检查参数属性的值是否为 null 时,您实际上是在检查一个值的存在,而不是它的内在价值,无论它是什么。 @enet,是的,但似乎没有办法有条件地添加参数。基于某种状态,我希望它存在或不存在。 SO上还有一些类似的问题,但没有好的答案。 仍然无法转发参数。请注意,在现实生活中,有许多参数,因此为每个参数制作条件语句将增加 O(N!)。 真正应该问的问题是为什么要转发一个空值的参数。检查条件是程序员的日常工作。您是否在触发事件处理程序委托之前做出条件语句?那是一样的。同样,为什么要传递一个空值的委托?你不能用它做任何事情,所以不要通过它。 @enet 我不想要传递一个空值的委托。但是,Blazor 的设计者选择以这种方式实现 RenderFragment。我想要做的是模板子内容并传递该内容。 RenderFragment 似乎是要走的路,我在很多页面上都找到了。 【参考方案1】:当您尝试将Bar
作为属性传递时,Razor 编译器将尝试将其呈现为 Foo 组件的一部分,这不是您想要的,也是当 Bar 为空时它会失败的原因。
如果实际用例适合这一点,您可以通过属性喷溅来实现您的目标。
@*Foo.razor*@
<Baz @attributes=PassThrough/>
@code
[Parameter]
public RenderFragment Bar get; set;
Dictionary<string,object> PassThrough;
protected override void OnInitialized()
PassThrough = new Dictionary<string,object>();
PassThrough.Add(nameof(Baz.Bar),this.Bar);
编辑:在这里测试:Blazor Repl
【讨论】:
谢谢。我希望这不是唯一/最好的解决方案。我给出的例子被大大简化了。在我们想要移植(从表单)的实际项目中,会有一个完整的组件层次结构,每个组件都有几个子组件。必须分别跟踪每个组件的所有“传递”参数将使代码变脏且难以维护...... 我想我已经对“当您尝试将 Bar 作为属性传递时,Razor 编译器将尝试将其渲染为 Foo 组件的一部分”......第一件事:它不是一个属性,它是一个参数,对吧? “属性”是一个 HTML 的东西。但是 Blazor 组件实际上是 C# 类。Bar
是一个属性,它具有 ParameterAtrribute
。 Sow当我说<Baz Bar=@Bar />
时,我希望编译器生成var barObj = new Baz Bar=Bar ;
。如果你通过它,为什么它会尝试渲染它?这没有任何意义。
好吧,我不是在这里争论属性,这是一个模糊的领域——但参数的值是通过 razor 和渲染树中的属性设置的。我相信总有其他方法可以做事,你绝对可以称这是一个错误并记录一个问题,因为很容易争论以这种方式传递的渲染片段应该只是通过。
我自己的经验是这样的——一旦你开始遇到这样的问题,这是一个很好的迹象,表明你的设计有问题——我并不是说肯定有,但现在是采取退一步检查一下——像这样直接传递渲染片段感觉像是设计中的一个潜在错误——但我完全接受我没有足够的关于你的项目的详细信息来确定:)
谢谢。我目前在一个大项目中,该项目已经过时并且有很多意大利面条代码。他们使用某种“表单生成器”项目,该项目提供了大量的组件(不同类型然后是剃刀)模板。这些被转换为数据库条目,每次更新都需要重新生成。它很难维护,也很容易搞砸。我试图向我的团队展示我们可以使用 Blazor/Razor 组件替换它,这将使生活变得更加轻松。但是如果我们不能有层次结构,传递像 RenderFragments 这样的东西,我将很难卖掉它......【参考方案2】:
这不是真正的答案,但我需要在某个地方放置一些比注释更好的代码。
问题在于在为 Razor 组件构建适当的类时,将 razor 代码预编译到 BuildRenderTree
方法中的方式。责怪组件制造商。您需要以 Razor 期望的方式声明 RenderFragments。
您的编码 - 使用属性方法:
<Baz Bar="@Bar"></Baz>
Razor 构建器将其变成:
public partial class Foo2 : Microsoft.AspNetCore.Components.ComponentBase
#pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
__builder.OpenComponent<TestBlazorServer.Components.Bar>(0);
__builder.AddAttribute(1, "Bar",
#nullable restore
Bar
#line default
#line hidden
#nullable disable
);
__builder.CloseComponent();
#pragma warning restore 1998
#nullable restore
#line 3
[Parameter]
public RenderFragment Bar get; set;
请注意,Bar
是作为属性添加的,如上面的答案中所述。
现在如果你这样声明:
<Baz>
<Bar>@Bar</Bar>
</Baz>
Bar
被正确添加为 RenderFragment。
public partial class Foo : Microsoft.AspNetCore.Components.ComponentBase
#pragma warning disable 1998
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
__builder.OpenComponent<TestBlazorServer.Components.Bar>(0);
__builder.AddAttribute(1, "Baz", (Microsoft.AspNetCore.Components.RenderFragment)((__builder2) =>
__builder2.AddContent(2,
#nullable restore
#line 5
Bar
#line default
#line hidden
#nullable disable
);
));
__builder.CloseComponent();
#pragma warning restore 1998
#nullable restore
#line 9
[Parameter]
public RenderFragment Bar get; set;
这是我几个月前在代码中构建组件的后端。无论如何,我不确定你是否可以用这种方式构建你的东西,但它解释了你所看到的。
【讨论】:
谢谢!我会检查这个__builder.OpenComponent<TestBlazorServer.Components.Bar>(0);
这是一个错字吗?即应该是__builder.OpenComponent<TestBlazorServer.Components.Baz>(0);
(甚至是Foo
?)没有组件Bar
?
另外,第一个代码实际上似乎可以正常工作,as I discovered
我有一个名为 Bar 的组件:当我在测试站点中构建组件时,组件名称中有错字!我假设您知道在哪里可以找到剃刀生成的代码?
不,我没有,而且在谷歌上似乎不太容易找到。你能指点我(或一些文档)吗?
在项目obj
文件夹中探索。 ..\SPA\obj\Debug\net5.0\Razor 是我的 SPA 项目库的 razor 构建类文件的路径。每个文件都是 xxx.razor.g.cs。以上是关于将 Blazor RenderFragment 参数转发给子组件会引发异常的主要内容,如果未能解决你的问题,请参考以下文章
如何在 RenderFragment Antd Blazor 中触发 @onclick
Blazor 服务端组件 Render, RenderFragment ,RenderTreeBuilder, CascadingValue/CascadingParameter
blazor return RenderFragment '__builder' 在当前上下文中不存在
Blazor University (18)使用 RenderFragments 模板化组件 —— 创建 TabControl