diff --git a/src/Components/Components.sln b/src/Components/Components.sln
index 0a909ab6e3..6d1065b2b6 100644
--- a/src/Components/Components.sln
+++ b/src/Components/Components.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2010
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28315.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F5FDD4E5-6A52-4A86-BE5E-5E42CB1DC8DA}"
EndProject
diff --git a/src/Components/build/dependencies.props b/src/Components/build/dependencies.props
index d7586f1dda..8cf890eb21 100644
--- a/src/Components/build/dependencies.props
+++ b/src/Components/build/dependencies.props
@@ -9,7 +9,9 @@
3.0.0-alpha1-20181011.3
2.2.0-preview1-34576
3.0.0-alpha1-10605
+ 3.0.0-alpha1-10605
3.0.0-alpha1-10605
+ 3.0.0-alpha1-10605
3.0.0-preview1-26907-05
3.0.0-alpha1-10605
0.8.0-preview1-20181122.3
diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Rendering/ComponentState.cs b/src/Components/src/Microsoft.AspNetCore.Components/Rendering/ComponentState.cs
index a0ecb672a2..b7d38c6cd4 100644
--- a/src/Components/src/Microsoft.AspNetCore.Components/Rendering/ComponentState.cs
+++ b/src/Components/src/Microsoft.AspNetCore.Components/Rendering/ComponentState.cs
@@ -29,6 +29,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
public int ComponentId => _componentId;
public IComponent Component => _component;
public ComponentState ParentComponentState => _parentComponentState;
+ public RenderTreeBuilder CurrrentRenderTree => _renderTreeBuilderCurrent;
///
/// Constructs an instance of .
diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Rendering/HtmlRenderer.cs b/src/Components/src/Microsoft.AspNetCore.Components/Rendering/HtmlRenderer.cs
new file mode 100644
index 0000000000..1918e85b44
--- /dev/null
+++ b/src/Components/src/Microsoft.AspNetCore.Components/Rendering/HtmlRenderer.cs
@@ -0,0 +1,234 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.RenderTree;
+
+namespace Microsoft.AspNetCore.Components.Rendering
+{
+ ///
+ /// A that produces HTML.
+ ///
+ public class HtmlRenderer : Renderer
+ {
+ private static readonly HashSet SelfClosingElements = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"
+ };
+
+ private readonly Func _htmlEncoder;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The to use to instantiate components.
+ /// A that will HTML encode the given string.
+ public HtmlRenderer(IServiceProvider serviceProvider, Func htmlEncoder) : base(serviceProvider)
+ {
+ _htmlEncoder = htmlEncoder;
+ }
+
+ ///
+ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Renders a component into a sequence of fragments that represent the textual representation
+ /// of the HTML produced by the component.
+ ///
+ /// The type of the .
+ /// A with the initial parameters to render the component.
+ /// A sequence of fragments that represent the HTML text of the component.
+ public IEnumerable RenderComponent(ParameterCollection initialParameters) where T : IComponent
+ {
+ return RenderComponent(typeof(T), initialParameters);
+ }
+
+ ///
+ /// Renders a component into a sequence of fragments that represent the textual representation
+ /// of the HTML produced by the component.
+ ///
+ /// The type of the .
+ /// A with the initial parameters to render the component.
+ /// A sequence of fragments that represent the HTML text of the component.
+ private IEnumerable RenderComponent(Type componentType, ParameterCollection initialParameters)
+ {
+ var frames = CreateInitialRender(componentType, initialParameters);
+
+ if (frames.Count == 0)
+ {
+ return Array.Empty();
+ }
+ else
+ {
+ var result = new List();
+ var newPosition = RenderFrames(result, frames, 0, frames.Count);
+ Debug.Assert(newPosition == frames.Count);
+ return result;
+ }
+ }
+
+ private int RenderFrames(List result, ArrayRange frames, int position, int maxElements)
+ {
+ var nextPosition = position;
+ var endPosition = position + maxElements;
+ while (position < endPosition)
+ {
+ nextPosition = RenderCore(result, frames, position, maxElements);
+ if (position == nextPosition)
+ {
+ throw new InvalidOperationException("We didn't consume any input.");
+ }
+ position = nextPosition;
+ }
+
+ return nextPosition;
+ }
+
+ private int RenderCore(
+ List result,
+ ArrayRange frames,
+ int position,
+ int length)
+ {
+ ref var frame = ref frames.Array[position];
+ switch (frame.FrameType)
+ {
+ case RenderTreeFrameType.Element:
+ return RenderElement(result, frames, position);
+ case RenderTreeFrameType.Attribute:
+ return RenderAttributes(result, frames, position, 1);
+ case RenderTreeFrameType.Text:
+ result.Add(_htmlEncoder(frame.TextContent));
+ return ++position;
+ case RenderTreeFrameType.Markup:
+ result.Add(frame.MarkupContent);
+ return ++position;
+ case RenderTreeFrameType.Component:
+ return RenderChildComponent(result, frames, position);
+ case RenderTreeFrameType.Region:
+ return RenderFrames(result, frames, position + 1, frame.RegionSubtreeLength - 1);
+ case RenderTreeFrameType.ElementReferenceCapture:
+ case RenderTreeFrameType.ComponentReferenceCapture:
+ return ++position;
+ default:
+ throw new InvalidOperationException($"Invalid element frame type '{frame.FrameType}'.");
+ }
+ }
+
+ private int RenderChildComponent(
+ List result,
+ ArrayRange frames,
+ int position)
+ {
+ ref var frame = ref frames.Array[position];
+ var childFrames = GetCurrentRenderTreeFrames(frame.ComponentId);
+ RenderFrames(result, childFrames, 0, childFrames.Count);
+ return position + frame.ComponentSubtreeLength;
+ }
+
+ private int RenderElement(
+ List result,
+ ArrayRange frames,
+ int position)
+ {
+ ref var frame = ref frames.Array[position];
+ result.Add("<");
+ result.Add(frame.ElementName);
+ var afterAttributes = RenderAttributes(result, frames, position + 1, frame.ElementSubtreeLength - 1);
+ var remainingElements = frame.ElementSubtreeLength + position - afterAttributes;
+ if (remainingElements > 0)
+ {
+ result.Add(">");
+ var afterElement = RenderChildren(result, frames, afterAttributes, remainingElements);
+ result.Add("");
+ result.Add(frame.ElementName);
+ result.Add(">");
+ Debug.Assert(afterElement == position + frame.ElementSubtreeLength);
+ return afterElement;
+ }
+ else
+ {
+ if (SelfClosingElements.Contains(frame.ElementName))
+ {
+ result.Add(" />");
+ }
+ else
+ {
+ result.Add(">");
+ result.Add("");
+ result.Add(frame.ElementName);
+ result.Add(">");
+ }
+ Debug.Assert(afterAttributes == position + frame.ElementSubtreeLength);
+ return afterAttributes;
+ }
+ }
+
+ private int RenderChildren(List result, ArrayRange frames, int position, int maxElements)
+ {
+ if (maxElements == 0)
+ {
+ return position;
+ }
+
+ return RenderFrames(result, frames, position, maxElements);
+ }
+
+ private int RenderAttributes(
+ List result,
+ ArrayRange frames, int position, int maxElements)
+ {
+ if (maxElements == 0)
+ {
+ return position;
+ }
+
+ for (var i = 0; i < maxElements; i++)
+ {
+ var candidateIndex = position + i;
+ ref var frame = ref frames.Array[candidateIndex];
+ if (frame.FrameType != RenderTreeFrameType.Attribute)
+ {
+ return candidateIndex;
+ }
+
+ switch (frame.AttributeValue)
+ {
+ case bool flag when flag:
+ result.Add(" ");
+ result.Add(frame.AttributeName);
+ break;
+ case string value:
+ result.Add(" ");
+ result.Add(frame.AttributeName);
+ result.Add("=");
+ result.Add("\"");
+ result.Add(_htmlEncoder(value));
+ result.Add("\"");
+ break;
+ default:
+ break;
+ }
+ }
+
+ return position + maxElements;
+ }
+
+ private ArrayRange CreateInitialRender(Type componentType, ParameterCollection initialParameters)
+ {
+ var component = InstantiateComponent(componentType);
+ var componentId = AssignRootComponentId(component);
+
+ RenderRootComponent(componentId, initialParameters);
+
+ return GetCurrentRenderTreeFrames(componentId);
+ }
+ }
+}
+
diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Rendering/Renderer.cs b/src/Components/src/Microsoft.AspNetCore.Components/Rendering/Renderer.cs
index 8498611925..cbdca363e7 100644
--- a/src/Components/src/Microsoft.AspNetCore.Components/Rendering/Renderer.cs
+++ b/src/Components/src/Microsoft.AspNetCore.Components/Rendering/Renderer.cs
@@ -53,6 +53,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
protected int AssignRootComponentId(IComponent component)
=> AttachAndInitComponent(component, -1).ComponentId;
+ ///
+ /// Gets the current render tree for a given component.
+ ///
+ /// The id for the component.
+ /// The representing the current render tree.
+ private protected ArrayRange GetCurrentRenderTreeFrames(int componentId) => GetRequiredComponentState(componentId).CurrrentRenderTree.GetFrames();
+
///
/// Performs the first render for a root component. After this, the root component
/// makes its own decisions about when to re-render, so there is no need to call
@@ -65,6 +72,19 @@ namespace Microsoft.AspNetCore.Components.Rendering
.SetDirectParameters(ParameterCollection.Empty);
}
+ ///
+ /// Performs the first render for a root component. After this, the root component
+ /// makes its own decisions about when to re-render, so there is no need to call
+ /// this more than once.
+ ///
+ /// The ID returned by .
+ /// The with the initial parameters to use for rendering.
+ protected void RenderRootComponent(int componentId, ParameterCollection initialParameters)
+ {
+ GetRequiredComponentState(componentId)
+ .SetDirectParameters(initialParameters);
+ }
+
private ComponentState AttachAndInitComponent(IComponent component, int parentComponentId)
{
var componentId = _nextComponentId++;
diff --git a/src/Components/test/Microsoft.AspNetCore.Components.Test/Microsoft.AspNetCore.Components.Test.csproj b/src/Components/test/Microsoft.AspNetCore.Components.Test/Microsoft.AspNetCore.Components.Test.csproj
index 8497a58081..269922dc98 100644
--- a/src/Components/test/Microsoft.AspNetCore.Components.Test/Microsoft.AspNetCore.Components.Test.csproj
+++ b/src/Components/test/Microsoft.AspNetCore.Components.Test/Microsoft.AspNetCore.Components.Test.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp3.0
@@ -11,6 +11,8 @@
+
+
diff --git a/src/Components/test/Microsoft.AspNetCore.Components.Test/Rendering/HtmlRendererTests.cs b/src/Components/test/Microsoft.AspNetCore.Components.Test/Rendering/HtmlRendererTests.cs
new file mode 100644
index 0000000000..040d0cb414
--- /dev/null
+++ b/src/Components/test/Microsoft.AspNetCore.Components.Test/Rendering/HtmlRendererTests.cs
@@ -0,0 +1,447 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Components.RenderTree;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Components.Rendering
+{
+ public class HtmlRendererTests
+ {
+ private static readonly Func _encoder = (string t) => HtmlEncoder.Default.Encode(t);
+
+ [Fact]
+ public void RenderComponent_CanRenderEmptyElement()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", ">", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_CanRenderSimpleComponent()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", ">", "Hello world!", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.AddContent(1, "Hello world!");
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_HtmlEncodesContent()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", ">", "<Hello world!>", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.AddContent(1, "");
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+
+ [Fact]
+ public void RenderComponent_DoesNotEncodeMarkup()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", ">", "Hello world!", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.AddMarkupContent(1, "Hello world!");
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+
+ [Fact]
+ public void RenderComponent_CanRenderWithAttributes()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", " ", "class", "=", "\"", "lead", "\"", ">", "Hello world!", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.AddAttribute(1, "class", "lead");
+ rtb.AddContent(2, "Hello world!");
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_HtmlEncodesAttributeValues()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", " ", "class", "=", "\"", "<lead", "\"", ">", "Hello world!", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.AddAttribute(1, "class", "(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_CanRenderBooleanAttributes()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "input", " ", "disabled", " />" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "input");
+ rtb.AddAttribute(1, "disabled", true);
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_DoesNotRenderBooleanAttributesWhenValueIsFalse()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "input", " />" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "input");
+ rtb.AddAttribute(1, "disabled", false);
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_CanRenderWithChildren()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", ">", "<", "span", ">", "Hello world!", "", "span", ">", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.OpenElement(1, "span");
+ rtb.AddContent(2, "Hello world!");
+ rtb.CloseElement();
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_CanRenderWithMultipleChildren()
+ {
+ // Arrange
+ var expectedHtml = new[] { "<", "p", ">",
+ "<", "span", ">", "Hello world!", "", "span", ">",
+ "<", "span", ">", "Bye Bye world!", "", "span", ">",
+ "", "p", ">"
+ };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.OpenElement(1, "span");
+ rtb.AddContent(2, "Hello world!");
+ rtb.CloseElement();
+ rtb.OpenElement(3, "span");
+ rtb.AddContent(4, "Bye Bye world!");
+ rtb.CloseElement();
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_CanRenderComponentWithChildrenComponents()
+ {
+ // Arrange
+ var expectedHtml = new[] {
+ "<", "p", ">", "<", "span", ">", "Hello world!", "", "span", ">", "", "p", ">",
+ "<", "span", ">", "Child content!", "", "span", ">"
+ };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.OpenElement(1, "span");
+ rtb.AddContent(2, "Hello world!");
+ rtb.CloseElement();
+ rtb.CloseElement();
+ rtb.OpenComponent(3, typeof(ChildComponent));
+ rtb.AddAttribute(4, "Value", "Child content!");
+ rtb.CloseComponent();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_ComponentReferenceNoops()
+ {
+ // Arrange
+ var expectedHtml = new[] {
+ "<", "p", ">", "<", "span", ">", "Hello world!", "", "span", ">", "", "p", ">",
+ "<", "span", ">", "Child content!", "", "span", ">"
+ };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.OpenElement(1, "span");
+ rtb.AddContent(2, "Hello world!");
+ rtb.CloseElement();
+ rtb.CloseElement();
+ rtb.OpenComponent(3, typeof(ChildComponent));
+ rtb.AddAttribute(4, "Value", "Child content!");
+ rtb.AddComponentReferenceCapture(5, cr => { });
+ rtb.CloseComponent();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_CanPassParameters()
+ {
+ // Arrange
+ var expectedHtml = new[] {
+ "<", "p", ">", "<", "input", " ", "value", "=", "\"", "5", "\"", " />", "", "p", ">" };
+
+ RenderFragment Content(ParameterCollection pc) => new RenderFragment((RenderTreeBuilder rtb) =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.OpenElement(1, "input");
+ rtb.AddAttribute(2, "change", pc.GetValueOrDefault>("update"));
+ rtb.AddAttribute(3, "value", pc.GetValueOrDefault("value"));
+ rtb.CloseElement();
+ rtb.CloseElement();
+ });
+
+ var serviceProvider = new ServiceCollection()
+ .AddSingleton(new Func(Content))
+ .BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+ Action change = (UIChangeEventArgs changeArgs) => throw new InvalidOperationException();
+
+ // Act
+ var result = htmlRenderer.RenderComponent(
+ new ParameterCollection(new[] {
+ RenderTreeFrame.Element(0,string.Empty),
+ RenderTreeFrame.Attribute(1,"update",change),
+ RenderTreeFrame.Attribute(2,"value",5)
+ }, 0));
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_CanRenderComponentWithRenderFragmentContent()
+ {
+ // Arrange
+ var expectedHtml = new[] {
+ "<", "p", ">", "<", "span", ">", "Hello world!", "", "span", ">", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.OpenElement(1, "span");
+ rtb.AddContent(2,
+ // This internally creates a region frame.
+ rf => rf.AddContent(0, "Hello world!"));
+ rtb.CloseElement();
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ [Fact]
+ public void RenderComponent_ElementRefsNoops()
+ {
+ // Arrange
+ var expectedHtml = new[] {
+ "<", "p", ">", "<", "span", ">", "Hello world!", "", "span", ">", "", "p", ">" };
+ var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
+ {
+ rtb.OpenElement(0, "p");
+ rtb.AddElementReferenceCapture(1, er => { });
+ rtb.OpenElement(2, "span");
+ rtb.AddContent(3,
+ // This internally creates a region frame.
+ rf => rf.AddContent(0, "Hello world!"));
+ rtb.CloseElement();
+ rtb.CloseElement();
+ })).BuildServiceProvider();
+
+ var htmlRenderer = new HtmlRenderer(serviceProvider, _encoder);
+
+ // Act
+ var result = htmlRenderer.RenderComponent(ParameterCollection.Empty);
+
+ // Assert
+ Assert.Equal(expectedHtml, result);
+ }
+
+ private class ComponentWithParameters : IComponent
+ {
+ public RenderHandle RenderHandle { get; private set; }
+
+ public void Init(RenderHandle renderHandle)
+ {
+ RenderHandle = renderHandle;
+ }
+
+ [Inject]
+ Func CreateRenderFragment { get; set; }
+
+ public void SetParameters(ParameterCollection parameters)
+ {
+ RenderHandle.Render(CreateRenderFragment(parameters));
+ }
+ }
+
+ private class ChildComponent : IComponent
+ {
+ private RenderHandle _renderHandle;
+
+ public void Init(RenderHandle renderHandle)
+ {
+ _renderHandle = renderHandle;
+ }
+
+ public void SetParameters(ParameterCollection parameters)
+ {
+ _renderHandle.Render(CreateRenderFragment(parameters));
+ }
+
+ private RenderFragment CreateRenderFragment(ParameterCollection parameters)
+ {
+ return RenderFragment;
+
+ void RenderFragment(RenderTreeBuilder rtb)
+ {
+ rtb.OpenElement(1, "span");
+ rtb.AddContent(2, parameters.GetValueOrDefault("Value"));
+ rtb.CloseElement();
+ }
+ }
+ }
+
+ private class TestComponent : IComponent
+ {
+ private RenderHandle _renderHandle;
+
+ [Inject]
+ public RenderFragment Fragment { get; set; }
+
+ public void Init(RenderHandle renderHandle)
+ {
+ _renderHandle = renderHandle;
+ }
+
+ public void SetParameters(ParameterCollection parameters)
+ {
+ _renderHandle.Render(Fragment);
+ }
+ }
+ }
+}