Add ability to add component frame using runtime type object instead of generic param

This commit is contained in:
Steve Sanderson 2018-02-16 08:47:37 +00:00
parent 535b601d55
commit acc5b9461b
4 changed files with 51 additions and 12 deletions

View File

@ -148,16 +148,27 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
/// <typeparam name="TComponent">The type of the child component.</typeparam>
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
public void OpenComponent<TComponent>(int sequence) where TComponent : IComponent
=> OpenComponentUnchecked(sequence, typeof(TComponent));
/// <summary>
/// Appends a frame representing a child component.
/// </summary>
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
/// <param name="componentType">The type of the child component.</param>
public void OpenComponent(int sequence, Type componentType)
{
if (!typeof(IComponent).IsAssignableFrom(componentType))
{
throw new ArgumentException($"The component type must implement {typeof(IComponent).FullName}.");
}
OpenComponentUnchecked(sequence, componentType);
}
private void OpenComponentUnchecked(int sequence, Type componentType)
{
// Currently, child components can't have further grandchildren of their own, so it would
// technically be possible to skip their CloseElement calls and not track them in _openElementIndices.
// However at some point we might want to have the grandchildren frames available at runtime
// (rather than being parsed as attributes at compile time) so that we could have APIs for
// components to query the complete hierarchy of transcluded frames instead of forcing the
// transcluded subtree to be in a particular shape such as representing key/value pairs.
// So it's more flexible if we track open/close frames for components explicitly.
_openElementIndices.Push(_entries.Count);
Append(RenderTreeFrame.ChildComponent<TComponent>(sequence));
Append(RenderTreeFrame.ChildComponent(sequence, componentType));
}
/// <summary>

View File

@ -201,8 +201,8 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
internal static RenderTreeFrame Attribute(int sequence, string name, object value)
=> new RenderTreeFrame(sequence, attributeName: name, attributeValue: value);
internal static RenderTreeFrame ChildComponent<T>(int sequence) where T : IComponent
=> new RenderTreeFrame(sequence, typeof(T), 0);
internal static RenderTreeFrame ChildComponent(int sequence, Type componentType)
=> new RenderTreeFrame(sequence, componentType, 0);
internal static RenderTreeFrame Region(int sequence)
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Arrange
var frames = new[]
{
RenderTreeFrame.ChildComponent<FakeComponent>(0).WithComponentSubtreeLength(1)
RenderTreeFrame.ChildComponent(0, typeof(FakeComponent)).WithComponentSubtreeLength(1)
};
var parameterCollection = new ParameterCollection(frames, 0);

View File

@ -260,7 +260,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
}
[Fact]
public void CanAddChildComponents()
public void CanAddChildComponentsUsingGenericParam()
{
// Arrange
var builder = new RenderTreeBuilder(new TestRenderer());
@ -286,6 +286,34 @@ namespace Microsoft.AspNetCore.Blazor.Test
frame => AssertFrame.Attribute(frame, "child2attribute", "C"));
}
[Fact]
public void CanAddChildComponentsUsingTypeArgument()
{
// Arrange
var builder = new RenderTreeBuilder(new TestRenderer());
// Act
var componentType = typeof(TestComponent);
builder.OpenElement(10, "parent"); // 0: <parent>
builder.OpenComponent(11, componentType); // 1: <testcomponent
builder.AddAttribute(12, "child1attribute1", "A"); // 2: child1attribute1="A"
builder.AddAttribute(13, "child1attribute2", "B"); // 3: child1attribute2="B">
builder.CloseComponent(); // </testcomponent>
builder.OpenComponent(14, componentType); // 4: <testcomponent
builder.AddAttribute(15, "child2attribute", "C"); // 5: child2attribute="C">
builder.CloseComponent(); // </testcomponent>
builder.CloseElement(); // </parent>
// Assert
Assert.Collection(builder.GetFrames(),
frame => AssertFrame.Element(frame, "parent", 6),
frame => AssertFrame.Component<TestComponent>(frame),
frame => AssertFrame.Attribute(frame, "child1attribute1", "A"),
frame => AssertFrame.Attribute(frame, "child1attribute2", "B"),
frame => AssertFrame.Component<TestComponent>(frame),
frame => AssertFrame.Attribute(frame, "child2attribute", "C"));
}
[Fact]
public void CanAddRegions()
{