From acc5b9461bcf1ea7181c1bdcd9dbdd7baa7679ee Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 16 Feb 2018 08:47:37 +0000 Subject: [PATCH] Add ability to add component frame using runtime type object instead of generic param --- .../RenderTree/RenderTreeBuilder.cs | 27 ++++++++++++----- .../RenderTree/RenderTreeFrame.cs | 4 +-- .../ParameterCollectionTest.cs | 2 +- .../RenderTreeBuilderTest.cs | 30 ++++++++++++++++++- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs index 548e760cff..f2f4e52f4e 100644 --- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs +++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs @@ -148,16 +148,27 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree /// The type of the child component. /// An integer that represents the position of the instruction in the source code. public void OpenComponent(int sequence) where TComponent : IComponent + => OpenComponentUnchecked(sequence, typeof(TComponent)); + + /// + /// Appends a frame representing a child component. + /// + /// An integer that represents the position of the instruction in the source code. + /// The type of the child component. + 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(sequence)); + Append(RenderTreeFrame.ChildComponent(sequence, componentType)); } /// diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs index 81b45ed5bc..7ee103ebc9 100644 --- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs +++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs @@ -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(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); diff --git a/test/Microsoft.AspNetCore.Blazor.Test/ParameterCollectionTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/ParameterCollectionTest.cs index 924dd04f94..e405b1f0e2 100644 --- a/test/Microsoft.AspNetCore.Blazor.Test/ParameterCollectionTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Test/ParameterCollectionTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Blazor.Test // Arrange var frames = new[] { - RenderTreeFrame.ChildComponent(0).WithComponentSubtreeLength(1) + RenderTreeFrame.ChildComponent(0, typeof(FakeComponent)).WithComponentSubtreeLength(1) }; var parameterCollection = new ParameterCollection(frames, 0); diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs index 68c7bc5d6a..7ed104b134 100644 --- a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeBuilderTest.cs @@ -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: + builder.OpenComponent(11, componentType); // 1: + builder.CloseComponent(); // + builder.OpenComponent(14, componentType); // 4: + builder.CloseComponent(); // + builder.CloseElement(); // + + // Assert + Assert.Collection(builder.GetFrames(), + frame => AssertFrame.Element(frame, "parent", 6), + frame => AssertFrame.Component(frame), + frame => AssertFrame.Attribute(frame, "child1attribute1", "A"), + frame => AssertFrame.Attribute(frame, "child1attribute2", "B"), + frame => AssertFrame.Component(frame), + frame => AssertFrame.Attribute(frame, "child2attribute", "C")); + } + [Fact] public void CanAddRegions() {