title | description | type | page_title | slug | position | tags | ticketid | res_type |
---|---|---|---|---|---|---|---|---|
RenderFragment Parameter Causes Exception |
Having a RenderFragment Parameter used as a property causes Delegate to an instance method cannot have null 'this'. |
troubleshooting |
RenderFragment Parameter Causes Exception |
common-kb-render-fragment-parameter-null |
1449470 |
kb |
Product | UI for Blazor |
When I use a RenderFragment
that comes as a parameter (for example, because I am wrapping a component that already uses a RenderFragment
in another component), I get an exception.
Error: ArgumentException: Delegate to an instance method cannot have null 'this'.
at System.MulticastDelegate.ThrowNullThisInDelegateToInstance()
at System.MulticastDelegate.CtorClosed(Object target, IntPtr methodPtr)
at YourCustomComponentNameHere`2.BuildRenderTree(RenderTreeBuilder __builder)
RenderFragment
parameters are null
by default and there is no really suitable place to make a check when passing the parameter as a property. You can read more here: dotnet/aspnetcore#10270
So, for example, with a Telerik component such as the ComboBox, such templates (fragments) will throw exceptions if you pass them as a property.
The following sample used as a component will cause exceptions:
````RAZOR typeparam TItem @typeparam TValuecaption RenderFragment elements used as properties cause exceptions
<TelerikComboBox Data="@Data" Value="@Value" ValueChanged="@ValueChanged" ValueExpression="@ValueExpression" OnChange="@OnChange" TItem="TItem" TValue="TValue" ValueField="@ValueField" TextField="@TextField"
HeaderTemplate="@HeaderTemplate" FooterTemplate="@FooterTemplate" ItemTemplate="@ItemTemplate">
@code { [Parameter] public IEnumerable Data { get; set; } [Parameter] public TValue Value { get; set; } [Parameter] public string ValueField { get; set; } [Parameter] public string TextField { get; set; } [Parameter] public EventCallback ValueChanged { get; set; } [Parameter] public System.Linq.Expressions.Expression<System.Func> ValueExpression { get; set; } [Parameter] public EventCallback OnChange { get; set; } [Parameter] public RenderFragment FooterTemplate { get; set; } [Parameter] public RenderFragment HeaderTemplate { get; set; } [Parameter] public RenderFragment ItemTemplate { get; set; } }
>caption Simple usage of the problematic component that causes an error
<div class="skip-repl"></div>
````RAZOR
<CustomCombo Data="@myDdlData" TItem="@MyDdlModel" TValue="int"
TextField="MyTextField" ValueField="MyValueField" @bind-Value="@selectedValue">
</CustomCombo>
@* If you define the three templates, you will not get exceptions, however *@
@code {
public class MyDdlModel
{
public int MyValueField { get; set; }
public string MyTextField { get; set; }
}
IEnumerable<MyDdlModel> myDdlData = Enumerable.Range(1, 20).Select(x => new MyDdlModel { MyTextField = "item " + x, MyValueField = x });
int selectedValue { get; set; } = 3;
}
The solution is to implement checks in the custom component so that you can render only when something is actually present, and to render it in a tag. Here is an example:
caption Custom Component wrapping a component that uses a RenderFragment
@typeparam TItem
@typeparam TValue
@using System.Reflection
<TelerikComboBox Data="@Data" Value="@Value" ValueChanged="@ValueChanged" ValueExpression="@ValueExpression" OnChange="@OnChange"
TItem="TItem" TValue="TValue" ValueField="@ValueField" TextField="@TextField">
<HeaderTemplate>
@HeaderTemplate
</HeaderTemplate>
<ItemTemplate>
@if (ItemTemplate != null)
{
@ItemTemplate((TItem)context)
}
else
{
object itm = context;
string toRender = "";
PropertyInfo propertyInfo = itm.GetType().GetProperty(TextField);
if (propertyInfo != null)
{
toRender = propertyInfo.GetValue(itm).ToString();
}
@toRender
}
</ItemTemplate>
<FooterTemplate>
@FooterTemplate
</FooterTemplate>
</TelerikComboBox>
@code {
[Parameter] public IEnumerable<TItem> Data { get; set; }
[Parameter] public TValue Value { get; set; }
[Parameter] public string ValueField { get; set; }
[Parameter] public string TextField { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public System.Linq.Expressions.Expression<System.Func<TValue>> ValueExpression { get; set; }
[Parameter] public EventCallback<Object> OnChange { get; set; }
[Parameter] public RenderFragment FooterTemplate { get; set; }
[Parameter] public RenderFragment HeaderTemplate { get; set; }
[Parameter] public RenderFragment<TItem> ItemTemplate { get; set; }
}
````RAZOR @selectedValuecaption How to consume that component - like usual
<CustomCombo Data="@myDdlData" TItem="@MyDdlModel" TValue="int" TextField="MyTextField" ValueField="MyValueField" @bind-Value="@selectedValue">
test @( (context as MyDdlModel).MyTextField ) The footer@* This also works now if you don not pass any templates, which would throw exceptions if the render fragments were properties *@
<CustomCombo Data="@myDdlData" TItem="@MyDdlModel" TValue="int" TextField="MyTextField" ValueField="MyValueField" @bind-Value="@selectedValue">
@code { public class MyDdlModel { public int MyValueField { get; set; } public string MyTextField { get; set; } }
IEnumerable<MyDdlModel> myDdlData = Enumerable.Range(1, 20).Select(x => new MyDdlModel { MyTextField = "item " + x, MyValueField = x });
int selectedValue { get; set; } = 3;
}