- 
                Notifications
    You must be signed in to change notification settings 
- Fork 10.5k
Description
Background and Motivation
The RenderTreeBuilder class currently has a method AddAttribute(), which is used to add attributes to the current element frame or parameters to the current component frame. There are multiple overloads of the method accepting different attribute values for well-known types (string, int, bool, EventCallback, etc.), as well as a catch-all overload accepting an object. It should be noted that when the relevant frame is a component, the implementation for each overload does the same thing - box the parameter value and add it to a new render tree frame.
This works well for most component parameter types, but consider if a component had a parameter of the following type:
public class Foo
{
    public string Value { get; set; } = string.Empty;
    public static implicit operator string(Foo foo) => foo.Value;
}We want the AddAttribute(int sequence, string name, object? value) overload to be resolved, but since Foo defines an implicit conversion to string, the AddAttribute(int sequence, string name, string value) overload gets resolved instead. As a result, the framework later tries to set the component parameter of type Foo to a value of type string, and an InvalidCastException is thrown.
Since each AddAttribute() implementation is identical when acting on a component frame, we could introduce an AddComponentParameter(int sequence, string name, object? value) method for component parameters. There is now no ambiguity with which overload to resolve. Also, since the AddAttribute() implementation would end up boxing the provided value anyway, we aren't losing anything by performing the boxing earlier.
Proposed API
namespace Microsoft.AspNetCore.Components.Rendering;
public sealed class RenderTreeBuilder : IDisposable
{
+    public void AddComponentParameter(int sequence, string name, object? value);
}Usage Examples
This method is primarily intended to be used in code generated by the Razor compiler, but it will still be available to developers if they wish to construct a render tree without Razor.
// 'builder' is a RenderTreeBuilder here
builder.OpenComponent<SurveyPrompt>(0);
builder.AddComponentParameter(1, "Title", "How is Blazor working for you?");
builder.CloseComponent();Alternative Designs
Alternatively, we could require the Razor compiler to put an (object) cast in front of every component parameter value passed to AddAttribute(). However, this would eliminate the possibility of adding a generic AddComponentParameter<T>() overload in the future that avoids boxing for parameter types.
Risks
No known risks. This is not a breaking change, since AddAttribute() will maintain its existing behavior for component parameter types.