Prerender select elements with value; move HtmlRenderer into Mvc.ViewFeatures (#12996)
This commit is contained in:
parent
826ed7504b
commit
6b2d9f23f8
|
|
@ -363,30 +363,12 @@ namespace Microsoft.AspNetCore.Components.CompilerServices
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
|
||||||
public readonly partial struct ComponentRenderedText
|
|
||||||
{
|
|
||||||
private readonly object _dummy;
|
|
||||||
private readonly int _dummyPrimitive;
|
|
||||||
public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
public System.Collections.Generic.IEnumerable<string> Tokens { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
}
|
|
||||||
public partial class EventFieldInfo
|
public partial class EventFieldInfo
|
||||||
{
|
{
|
||||||
public EventFieldInfo() { }
|
public EventFieldInfo() { }
|
||||||
public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
}
|
}
|
||||||
public partial class HtmlRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer
|
|
||||||
{
|
|
||||||
public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Func<string, string> htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { }
|
|
||||||
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
protected override void HandleException(System.Exception exception) { }
|
|
||||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
|
||||||
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> RenderComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterView initialParameters) { throw null; }
|
|
||||||
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> RenderComponentAsync<TComponent>(Microsoft.AspNetCore.Components.ParameterView initialParameters) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
|
|
||||||
protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch renderBatch) { throw null; }
|
|
||||||
}
|
|
||||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||||
public readonly partial struct RenderBatch
|
public readonly partial struct RenderBatch
|
||||||
{
|
{
|
||||||
|
|
@ -405,6 +387,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, System.EventArgs eventArgs) { throw null; }
|
public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, System.EventArgs eventArgs) { throw null; }
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
protected virtual void Dispose(bool disposing) { }
|
protected virtual void Dispose(bool disposing) { }
|
||||||
|
protected Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId) { throw null; }
|
||||||
protected abstract void HandleException(System.Exception exception);
|
protected abstract void HandleException(System.Exception exception);
|
||||||
protected Microsoft.AspNetCore.Components.IComponent InstantiateComponent(System.Type componentType) { throw null; }
|
protected Microsoft.AspNetCore.Components.IComponent InstantiateComponent(System.Type componentType) { throw null; }
|
||||||
protected virtual void ProcessPendingRender() { }
|
protected virtual void ProcessPendingRender() { }
|
||||||
|
|
|
||||||
|
|
@ -363,30 +363,12 @@ namespace Microsoft.AspNetCore.Components.CompilerServices
|
||||||
}
|
}
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
|
||||||
public readonly partial struct ComponentRenderedText
|
|
||||||
{
|
|
||||||
private readonly object _dummy;
|
|
||||||
private readonly int _dummyPrimitive;
|
|
||||||
public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
public System.Collections.Generic.IEnumerable<string> Tokens { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
}
|
|
||||||
public partial class EventFieldInfo
|
public partial class EventFieldInfo
|
||||||
{
|
{
|
||||||
public EventFieldInfo() { }
|
public EventFieldInfo() { }
|
||||||
public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public int ComponentId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
public object FieldValue { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
|
||||||
}
|
}
|
||||||
public partial class HtmlRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer
|
|
||||||
{
|
|
||||||
public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Func<string, string> htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { }
|
|
||||||
public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
|
||||||
protected override void HandleException(System.Exception exception) { }
|
|
||||||
[System.Diagnostics.DebuggerStepThroughAttribute]
|
|
||||||
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> RenderComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterView initialParameters) { throw null; }
|
|
||||||
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Rendering.ComponentRenderedText> RenderComponentAsync<TComponent>(Microsoft.AspNetCore.Components.ParameterView initialParameters) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
|
|
||||||
protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch renderBatch) { throw null; }
|
|
||||||
}
|
|
||||||
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||||
public readonly partial struct RenderBatch
|
public readonly partial struct RenderBatch
|
||||||
{
|
{
|
||||||
|
|
@ -405,6 +387,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, System.EventArgs eventArgs) { throw null; }
|
public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, System.EventArgs eventArgs) { throw null; }
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
protected virtual void Dispose(bool disposing) { }
|
protected virtual void Dispose(bool disposing) { }
|
||||||
|
protected Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId) { throw null; }
|
||||||
protected abstract void HandleException(System.Exception exception);
|
protected abstract void HandleException(System.Exception exception);
|
||||||
protected Microsoft.AspNetCore.Components.IComponent InstantiateComponent(System.Type componentType) { throw null; }
|
protected Microsoft.AspNetCore.Components.IComponent InstantiateComponent(System.Type componentType) { throw null; }
|
||||||
protected virtual void ProcessPendingRender() { }
|
protected virtual void ProcessPendingRender() { }
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// 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.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the result of rendering a component into static html.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct ComponentRenderedText
|
|
||||||
{
|
|
||||||
internal ComponentRenderedText(int componentId, IEnumerable<string> tokens)
|
|
||||||
{
|
|
||||||
ComponentId = componentId;
|
|
||||||
Tokens = tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the id associated with the component.
|
|
||||||
/// </summary>
|
|
||||||
public int ComponentId { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the sequence of tokens that when concatenated represent the html for the rendered component.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> Tokens { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="componentId">The id for the component.</param>
|
/// <param name="componentId">The id for the component.</param>
|
||||||
/// <returns>The <see cref="RenderTreeBuilder"/> representing the current render tree.</returns>
|
/// <returns>The <see cref="RenderTreeBuilder"/> representing the current render tree.</returns>
|
||||||
private protected ArrayRange<RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId) => GetRequiredComponentState(componentId).CurrentRenderTree.GetFrames();
|
protected ArrayRange<RenderTreeFrame> GetCurrentRenderTreeFrames(int componentId) => GetRequiredComponentState(componentId).CurrentRenderTree.GetFrames();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs the first render for a root component, waiting for this component and all
|
/// Performs the first render for a root component, waiting for this component and all
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// 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 Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
|
||||||
{
|
|
||||||
public class HtmlRendererTests : HtmlRendererTestBase
|
|
||||||
{
|
|
||||||
protected override HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, _encoder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -50,7 +50,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
var components = ResolveComponentMetadata(httpContext);
|
var components = ResolveComponentMetadata(httpContext);
|
||||||
|
|
||||||
var scope = _scopeFactory.CreateScope();
|
var scope = _scopeFactory.CreateScope();
|
||||||
var encoder = scope.ServiceProvider.GetRequiredService<HtmlEncoder>();
|
|
||||||
var jsRuntime = (RemoteJSRuntime)scope.ServiceProvider.GetRequiredService<IJSRuntime>();
|
var jsRuntime = (RemoteJSRuntime)scope.ServiceProvider.GetRequiredService<IJSRuntime>();
|
||||||
var componentContext = (RemoteComponentContext)scope.ServiceProvider.GetRequiredService<IComponentContext>();
|
var componentContext = (RemoteComponentContext)scope.ServiceProvider.GetRequiredService<IComponentContext>();
|
||||||
jsRuntime.Initialize(client);
|
jsRuntime.Initialize(client);
|
||||||
|
|
@ -76,7 +75,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
_options,
|
_options,
|
||||||
jsRuntime,
|
jsRuntime,
|
||||||
client,
|
client,
|
||||||
encoder,
|
|
||||||
_loggerFactory.CreateLogger<RemoteRenderer>());
|
_loggerFactory.CreateLogger<RemoteRenderer>());
|
||||||
|
|
||||||
var circuitHandlers = scope.ServiceProvider.GetServices<CircuitHandler>()
|
var circuitHandlers = scope.ServiceProvider.GetServices<CircuitHandler>()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
|
@ -17,7 +16,7 @@ using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Rendering
|
namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
{
|
{
|
||||||
internal class RemoteRenderer : HtmlRenderer
|
internal class RemoteRenderer : Renderer
|
||||||
{
|
{
|
||||||
private static readonly Task CanceledTask = Task.FromCanceled(new CancellationToken(canceled: true));
|
private static readonly Task CanceledTask = Task.FromCanceled(new CancellationToken(canceled: true));
|
||||||
|
|
||||||
|
|
@ -43,9 +42,8 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
CircuitOptions options,
|
CircuitOptions options,
|
||||||
IJSRuntime jsRuntime,
|
IJSRuntime jsRuntime,
|
||||||
CircuitClientProxy client,
|
CircuitClientProxy client,
|
||||||
HtmlEncoder encoder,
|
|
||||||
ILogger logger)
|
ILogger logger)
|
||||||
: base(serviceProvider, loggerFactory, encoder.Encode)
|
: base(serviceProvider, loggerFactory)
|
||||||
{
|
{
|
||||||
_jsRuntime = jsRuntime;
|
_jsRuntime = jsRuntime;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
|
||||||
using Microsoft.AspNetCore.Components.Web.Rendering;
|
using Microsoft.AspNetCore.Components.Web.Rendering;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -238,7 +236,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
private class TestRemoteRenderer : RemoteRenderer
|
private class TestRemoteRenderer : RemoteRenderer
|
||||||
{
|
{
|
||||||
public TestRemoteRenderer(IServiceProvider serviceProvider, IJSRuntime jsRuntime, IClientProxy client)
|
public TestRemoteRenderer(IServiceProvider serviceProvider, IJSRuntime jsRuntime, IClientProxy client)
|
||||||
: base(serviceProvider, NullLoggerFactory.Instance, new CircuitOptions(), jsRuntime, new CircuitClientProxy(client, "connection"), HtmlEncoder.Default, NullLogger.Instance)
|
: base(serviceProvider, NullLoggerFactory.Instance, new CircuitOptions(), jsRuntime, new CircuitClientProxy(client, "connection"), NullLogger.Instance)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
|
||||||
using Microsoft.AspNetCore.Components.Server;
|
using Microsoft.AspNetCore.Components.Server;
|
||||||
using Microsoft.AspNetCore.Components.Server.Circuits;
|
using Microsoft.AspNetCore.Components.Server.Circuits;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
@ -20,23 +18,18 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Rendering
|
namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
{
|
{
|
||||||
public class RemoteRendererTest : HtmlRendererTestBase
|
public class RemoteRendererTest
|
||||||
{
|
{
|
||||||
// Nothing should exceed the timeout in a successful run of the the tests, this is just here to catch
|
// Nothing should exceed the timeout in a successful run of the the tests, this is just here to catch
|
||||||
// failures.
|
// failures.
|
||||||
private static readonly TimeSpan Timeout = Debugger.IsAttached ? System.Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(10);
|
private static readonly TimeSpan Timeout = Debugger.IsAttached ? System.Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
protected override HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
return GetRemoteRenderer(serviceProvider, new CircuitClientProxy());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void WritesAreBufferedWhenTheClientIsOffline()
|
public void WritesAreBufferedWhenTheClientIsOffline()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
||||||
var renderer = (RemoteRenderer)GetHtmlRenderer(serviceProvider);
|
var renderer = GetRemoteRenderer(serviceProvider);
|
||||||
var component = new TestComponent(builder =>
|
var component = new TestComponent(builder =>
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "my element");
|
builder.OpenElement(0, "my element");
|
||||||
|
|
@ -57,7 +50,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
public void NotAcknowledgingRenders_ProducesBatches_UpToTheLimit()
|
public void NotAcknowledgingRenders_ProducesBatches_UpToTheLimit()
|
||||||
{
|
{
|
||||||
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
||||||
var renderer = (RemoteRenderer)GetHtmlRenderer(serviceProvider);
|
var renderer = GetRemoteRenderer(serviceProvider);
|
||||||
var component = new TestComponent(builder =>
|
var component = new TestComponent(builder =>
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "my element");
|
builder.OpenElement(0, "my element");
|
||||||
|
|
@ -81,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
public async Task NoNewBatchesAreCreated_WhenThereAreNoPendingRenderRequestsFromComponents()
|
public async Task NoNewBatchesAreCreated_WhenThereAreNoPendingRenderRequestsFromComponents()
|
||||||
{
|
{
|
||||||
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
||||||
var renderer = (RemoteRenderer)GetHtmlRenderer(serviceProvider);
|
var renderer = GetRemoteRenderer(serviceProvider);
|
||||||
var component = new TestComponent(builder =>
|
var component = new TestComponent(builder =>
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "my element");
|
builder.OpenElement(0, "my element");
|
||||||
|
|
@ -107,7 +100,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
public async Task ProducesNewBatch_WhenABatchGetsAcknowledged()
|
public async Task ProducesNewBatch_WhenABatchGetsAcknowledged()
|
||||||
{
|
{
|
||||||
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
||||||
var renderer = (RemoteRenderer)GetHtmlRenderer(serviceProvider);
|
var renderer = GetRemoteRenderer(serviceProvider);
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var component = new TestComponent(builder =>
|
var component = new TestComponent(builder =>
|
||||||
{
|
{
|
||||||
|
|
@ -215,7 +208,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
.Returns<string, object[], CancellationToken>((n, v, t) => (long)v[1] == 2 ? firstBatchTCS.Task : secondBatchTCS.Task);
|
.Returns<string, object[], CancellationToken>((n, v, t) => (long)v[1] == 2 ? firstBatchTCS.Task : secondBatchTCS.Task);
|
||||||
|
|
||||||
// This produces the initial batch (id = 2)
|
// This produces the initial batch (id = 2)
|
||||||
var result = await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
||||||
ParameterView.FromDictionary(new Dictionary<string, object>
|
ParameterView.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
||||||
|
|
@ -278,7 +271,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
.Returns<string, object[], CancellationToken>((n, v, t) => (long)v[1] == 2 ? firstBatchTCS.Task : secondBatchTCS.Task);
|
.Returns<string, object[], CancellationToken>((n, v, t) => (long)v[1] == 2 ? firstBatchTCS.Task : secondBatchTCS.Task);
|
||||||
|
|
||||||
// This produces the initial batch (id = 2)
|
// This produces the initial batch (id = 2)
|
||||||
var result = await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
||||||
ParameterView.FromDictionary(new Dictionary<string, object>
|
ParameterView.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
||||||
|
|
@ -341,7 +334,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
var trigger = new Trigger();
|
var trigger = new Trigger();
|
||||||
|
|
||||||
// This produces the initial batch (id = 2)
|
// This produces the initial batch (id = 2)
|
||||||
var result = await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
||||||
ParameterView.FromDictionary(new Dictionary<string, object>
|
ParameterView.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
||||||
|
|
@ -398,7 +391,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
var trigger = new Trigger();
|
var trigger = new Trigger();
|
||||||
|
|
||||||
// This produces the initial batch (id = 2)
|
// This produces the initial batch (id = 2)
|
||||||
var result = await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
await renderer.RenderComponentAsync<AutoParameterTestComponent>(
|
||||||
ParameterView.FromDictionary(new Dictionary<string, object>
|
ParameterView.FromDictionary(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
[nameof(AutoParameterTestComponent.Content)] = initialContent,
|
||||||
|
|
@ -432,27 +425,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
exception.Message);
|
exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
private TestRemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, CircuitClientProxy circuitClient = null)
|
||||||
public async Task PrerendersMultipleComponentsSuccessfully()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var serviceProvider = new ServiceCollection().BuildServiceProvider();
|
|
||||||
|
|
||||||
var renderer = GetRemoteRenderer(
|
|
||||||
serviceProvider,
|
|
||||||
new CircuitClientProxy());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var first = await renderer.RenderComponentAsync<TestComponent>(ParameterView.Empty);
|
|
||||||
var second = await renderer.RenderComponentAsync<TestComponent>(ParameterView.Empty);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(0, first.ComponentId);
|
|
||||||
Assert.Equal(1, second.ComponentId);
|
|
||||||
Assert.Equal(2, renderer._unacknowledgedRenderBatches.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, CircuitClientProxy circuitClientProxy)
|
|
||||||
{
|
{
|
||||||
var jsRuntime = new Mock<IJSRuntime>();
|
var jsRuntime = new Mock<IJSRuntime>();
|
||||||
jsRuntime.Setup(r => r.InvokeAsync<object>(
|
jsRuntime.Setup(r => r.InvokeAsync<object>(
|
||||||
|
|
@ -462,16 +435,30 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
|
||||||
It.IsAny<int>()))
|
It.IsAny<int>()))
|
||||||
.ReturnsAsync(Task.FromResult<object>(null));
|
.ReturnsAsync(Task.FromResult<object>(null));
|
||||||
|
|
||||||
return new RemoteRenderer(
|
return new TestRemoteRenderer(
|
||||||
serviceProvider,
|
serviceProvider,
|
||||||
NullLoggerFactory.Instance,
|
NullLoggerFactory.Instance,
|
||||||
new CircuitOptions(),
|
new CircuitOptions(),
|
||||||
jsRuntime.Object,
|
jsRuntime.Object,
|
||||||
circuitClientProxy,
|
circuitClient ?? new CircuitClientProxy(),
|
||||||
HtmlEncoder.Default,
|
|
||||||
NullLogger.Instance);
|
NullLogger.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestRemoteRenderer : RemoteRenderer
|
||||||
|
{
|
||||||
|
public TestRemoteRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, IJSRuntime jsRuntime, CircuitClientProxy client, ILogger logger)
|
||||||
|
: base(serviceProvider, loggerFactory, options, jsRuntime, client, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RenderComponentAsync<TComponent>(ParameterView initialParameters)
|
||||||
|
{
|
||||||
|
var component = InstantiateComponent(typeof(TComponent));
|
||||||
|
var componentId = AssignRootComponentId(component);
|
||||||
|
await RenderRootComponentAsync(componentId, initialParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TestComponent : IComponent, IHandleAfterRender
|
private class TestComponent : IComponent, IHandleAfterRender
|
||||||
{
|
{
|
||||||
private RenderHandle _renderHandle;
|
private RenderHandle _renderHandle;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Encodings.Web;
|
|
||||||
using Microsoft.AspNetCore.Components.Web.Rendering;
|
using Microsoft.AspNetCore.Components.Web.Rendering;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -40,7 +39,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
|
||||||
new CircuitOptions(),
|
new CircuitOptions(),
|
||||||
jsRuntime,
|
jsRuntime,
|
||||||
clientProxy,
|
clientProxy,
|
||||||
HtmlEncoder.Default,
|
|
||||||
NullLogger.Instance);
|
NullLogger.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\..\Components\test\Rendering\HtmlRendererTestBase.cs" />
|
|
||||||
<Compile Include="$(SignalRTestBase)HubMessageHelpers.cs" LinkBase="BlazorPack" />
|
<Compile Include="$(SignalRTestBase)HubMessageHelpers.cs" LinkBase="BlazorPack" />
|
||||||
<Compile Include="$(SignalRTestBase)MessagePackHubProtocolTestBase.cs" LinkBase="BlazorPack" />
|
<Compile Include="$(SignalRTestBase)MessagePackHubProtocolTestBase.cs" LinkBase="BlazorPack" />
|
||||||
<Compile Include="$(SignalRTestBase)TestBinder.cs" LinkBase="BlazorPack" />
|
<Compile Include="$(SignalRTestBase)TestBinder.cs" LinkBase="BlazorPack" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
{
|
||||||
|
internal readonly struct ComponentRenderedText
|
||||||
|
{
|
||||||
|
public ComponentRenderedText(int componentId, IEnumerable<string> tokens)
|
||||||
|
{
|
||||||
|
ComponentId = componentId;
|
||||||
|
Tokens = tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ComponentId { get; }
|
||||||
|
|
||||||
|
public IEnumerable<string> Tokens { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,10 +12,7 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
/// <summary>
|
internal class HtmlRenderer : Renderer
|
||||||
/// A <see cref="Renderer"/> that produces HTML.
|
|
||||||
/// </summary>
|
|
||||||
public class HtmlRenderer : Renderer
|
|
||||||
{
|
{
|
||||||
private static readonly HashSet<string> SelfClosingElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> SelfClosingElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
|
|
@ -26,12 +23,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
private readonly Func<string, string> _htmlEncoder;
|
private readonly Func<string, string> _htmlEncoder;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of <see cref="HtmlRenderer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use to instantiate components.</param>
|
|
||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
|
||||||
/// <param name="htmlEncoder">A <see cref="Func{T, TResult}"/> that will HTML encode the given string.</param>
|
|
||||||
public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, Func<string, string> htmlEncoder)
|
public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, Func<string, string> htmlEncoder)
|
||||||
: base(serviceProvider, loggerFactory)
|
: base(serviceProvider, loggerFactory)
|
||||||
{
|
{
|
||||||
|
|
@ -58,30 +49,16 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
return CanceledRenderTask;
|
return CanceledRenderTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Renders a component into a sequence of <see cref="string"/> fragments that represent the textual representation
|
|
||||||
/// of the HTML produced by the component.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="componentType">The type of the <see cref="IComponent"/>.</param>
|
|
||||||
/// <param name="initialParameters">A <see cref="ParameterView"/> with the initial parameters to render the component.</param>
|
|
||||||
/// <returns>A <see cref="Task"/> that on completion returns a sequence of <see cref="string"/> fragments that represent the HTML text of the component.</returns>
|
|
||||||
public async Task<ComponentRenderedText> RenderComponentAsync(Type componentType, ParameterView initialParameters)
|
public async Task<ComponentRenderedText> RenderComponentAsync(Type componentType, ParameterView initialParameters)
|
||||||
{
|
{
|
||||||
var (componentId, frames) = await CreateInitialRenderAsync(componentType, initialParameters);
|
var (componentId, frames) = await CreateInitialRenderAsync(componentType, initialParameters);
|
||||||
|
|
||||||
var result = new List<string>();
|
var context = new HtmlRenderingContext();
|
||||||
var newPosition = RenderFrames(result, frames, 0, frames.Count);
|
var newPosition = RenderFrames(context, frames, 0, frames.Count);
|
||||||
Debug.Assert(newPosition == frames.Count);
|
Debug.Assert(newPosition == frames.Count);
|
||||||
return new ComponentRenderedText(componentId, result);
|
return new ComponentRenderedText(componentId, context.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Renders a component into a sequence of <see cref="string"/> fragments that represent the textual representation
|
|
||||||
/// of the HTML produced by the component.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TComponent">The type of the <see cref="IComponent"/>.</typeparam>
|
|
||||||
/// <param name="initialParameters">A <see cref="ParameterView"/> with the initial parameters to render the component.</param>
|
|
||||||
/// <returns>A <see cref="Task"/> that on completion returns a sequence of <see cref="string"/> fragments that represent the HTML text of the component.</returns>
|
|
||||||
public Task<ComponentRenderedText> RenderComponentAsync<TComponent>(ParameterView initialParameters) where TComponent : IComponent
|
public Task<ComponentRenderedText> RenderComponentAsync<TComponent>(ParameterView initialParameters) where TComponent : IComponent
|
||||||
{
|
{
|
||||||
return RenderComponentAsync(typeof(TComponent), initialParameters);
|
return RenderComponentAsync(typeof(TComponent), initialParameters);
|
||||||
|
|
@ -91,13 +68,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
protected override void HandleException(Exception exception)
|
protected override void HandleException(Exception exception)
|
||||||
=> ExceptionDispatchInfo.Capture(exception).Throw();
|
=> ExceptionDispatchInfo.Capture(exception).Throw();
|
||||||
|
|
||||||
private int RenderFrames(List<string> result, ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
private int RenderFrames(HtmlRenderingContext context, ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
||||||
{
|
{
|
||||||
var nextPosition = position;
|
var nextPosition = position;
|
||||||
var endPosition = position + maxElements;
|
var endPosition = position + maxElements;
|
||||||
while (position < endPosition)
|
while (position < endPosition)
|
||||||
{
|
{
|
||||||
nextPosition = RenderCore(result, frames, position, maxElements);
|
nextPosition = RenderCore(context, frames, position);
|
||||||
if (position == nextPosition)
|
if (position == nextPosition)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("We didn't consume any input.");
|
throw new InvalidOperationException("We didn't consume any input.");
|
||||||
|
|
@ -109,28 +86,27 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
private int RenderCore(
|
private int RenderCore(
|
||||||
List<string> result,
|
HtmlRenderingContext context,
|
||||||
ArrayRange<RenderTreeFrame> frames,
|
ArrayRange<RenderTreeFrame> frames,
|
||||||
int position,
|
int position)
|
||||||
int length)
|
|
||||||
{
|
{
|
||||||
ref var frame = ref frames.Array[position];
|
ref var frame = ref frames.Array[position];
|
||||||
switch (frame.FrameType)
|
switch (frame.FrameType)
|
||||||
{
|
{
|
||||||
case RenderTreeFrameType.Element:
|
case RenderTreeFrameType.Element:
|
||||||
return RenderElement(result, frames, position);
|
return RenderElement(context, frames, position);
|
||||||
case RenderTreeFrameType.Attribute:
|
case RenderTreeFrameType.Attribute:
|
||||||
return RenderAttributes(result, frames, position, 1);
|
throw new InvalidOperationException($"Attributes should only be encountered within {nameof(RenderElement)}");
|
||||||
case RenderTreeFrameType.Text:
|
case RenderTreeFrameType.Text:
|
||||||
result.Add(_htmlEncoder(frame.TextContent));
|
context.Result.Add(_htmlEncoder(frame.TextContent));
|
||||||
return ++position;
|
return ++position;
|
||||||
case RenderTreeFrameType.Markup:
|
case RenderTreeFrameType.Markup:
|
||||||
result.Add(frame.MarkupContent);
|
context.Result.Add(frame.MarkupContent);
|
||||||
return ++position;
|
return ++position;
|
||||||
case RenderTreeFrameType.Component:
|
case RenderTreeFrameType.Component:
|
||||||
return RenderChildComponent(result, frames, position);
|
return RenderChildComponent(context, frames, position);
|
||||||
case RenderTreeFrameType.Region:
|
case RenderTreeFrameType.Region:
|
||||||
return RenderFrames(result, frames, position + 1, frame.RegionSubtreeLength - 1);
|
return RenderFrames(context, frames, position + 1, frame.RegionSubtreeLength - 1);
|
||||||
case RenderTreeFrameType.ElementReferenceCapture:
|
case RenderTreeFrameType.ElementReferenceCapture:
|
||||||
case RenderTreeFrameType.ComponentReferenceCapture:
|
case RenderTreeFrameType.ComponentReferenceCapture:
|
||||||
return ++position;
|
return ++position;
|
||||||
|
|
@ -140,30 +116,57 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
private int RenderChildComponent(
|
private int RenderChildComponent(
|
||||||
List<string> result,
|
HtmlRenderingContext context,
|
||||||
ArrayRange<RenderTreeFrame> frames,
|
ArrayRange<RenderTreeFrame> frames,
|
||||||
int position)
|
int position)
|
||||||
{
|
{
|
||||||
ref var frame = ref frames.Array[position];
|
ref var frame = ref frames.Array[position];
|
||||||
var childFrames = GetCurrentRenderTreeFrames(frame.ComponentId);
|
var childFrames = GetCurrentRenderTreeFrames(frame.ComponentId);
|
||||||
RenderFrames(result, childFrames, 0, childFrames.Count);
|
RenderFrames(context, childFrames, 0, childFrames.Count);
|
||||||
return position + frame.ComponentSubtreeLength;
|
return position + frame.ComponentSubtreeLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int RenderElement(
|
private int RenderElement(
|
||||||
List<string> result,
|
HtmlRenderingContext context,
|
||||||
ArrayRange<RenderTreeFrame> frames,
|
ArrayRange<RenderTreeFrame> frames,
|
||||||
int position)
|
int position)
|
||||||
{
|
{
|
||||||
ref var frame = ref frames.Array[position];
|
ref var frame = ref frames.Array[position];
|
||||||
|
var result = context.Result;
|
||||||
result.Add("<");
|
result.Add("<");
|
||||||
result.Add(frame.ElementName);
|
result.Add(frame.ElementName);
|
||||||
var afterAttributes = RenderAttributes(result, frames, position + 1, frame.ElementSubtreeLength - 1);
|
var afterAttributes = RenderAttributes(context, frames, position + 1, frame.ElementSubtreeLength - 1, out var capturedValueAttribute);
|
||||||
|
|
||||||
|
// When we see an <option> as a descendant of a <select>, and the option's "value" attribute matches the
|
||||||
|
// "value" attribute on the <select>, then we auto-add the "selected" attribute to that option. This is
|
||||||
|
// a way of converting Blazor's select binding feature to regular static HTML.
|
||||||
|
if (context.ClosestSelectValueAsString != null
|
||||||
|
&& string.Equals(frame.ElementName, "option", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& string.Equals(capturedValueAttribute, context.ClosestSelectValueAsString, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
result.Add(" selected");
|
||||||
|
}
|
||||||
|
|
||||||
var remainingElements = frame.ElementSubtreeLength + position - afterAttributes;
|
var remainingElements = frame.ElementSubtreeLength + position - afterAttributes;
|
||||||
if (remainingElements > 0)
|
if (remainingElements > 0)
|
||||||
{
|
{
|
||||||
result.Add(">");
|
result.Add(">");
|
||||||
var afterElement = RenderChildren(result, frames, afterAttributes, remainingElements);
|
|
||||||
|
var isSelect = string.Equals(frame.ElementName, "select", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (isSelect)
|
||||||
|
{
|
||||||
|
context.ClosestSelectValueAsString = capturedValueAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
var afterElement = RenderChildren(context, frames, afterAttributes, remainingElements);
|
||||||
|
|
||||||
|
if (isSelect)
|
||||||
|
{
|
||||||
|
// There's no concept of nested <select> elements, so as soon as we're exiting one of them,
|
||||||
|
// we can safely say there is no longer any value for this
|
||||||
|
context.ClosestSelectValueAsString = null;
|
||||||
|
}
|
||||||
|
|
||||||
result.Add("</");
|
result.Add("</");
|
||||||
result.Add(frame.ElementName);
|
result.Add(frame.ElementName);
|
||||||
result.Add(">");
|
result.Add(">");
|
||||||
|
|
@ -188,25 +191,29 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int RenderChildren(List<string> result, ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
private int RenderChildren(HtmlRenderingContext context, ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
||||||
{
|
{
|
||||||
if (maxElements == 0)
|
if (maxElements == 0)
|
||||||
{
|
{
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
return RenderFrames(result, frames, position, maxElements);
|
return RenderFrames(context, frames, position, maxElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int RenderAttributes(
|
private int RenderAttributes(
|
||||||
List<string> result,
|
HtmlRenderingContext context,
|
||||||
ArrayRange<RenderTreeFrame> frames, int position, int maxElements)
|
ArrayRange<RenderTreeFrame> frames, int position, int maxElements, out string capturedValueAttribute)
|
||||||
{
|
{
|
||||||
|
capturedValueAttribute = null;
|
||||||
|
|
||||||
if (maxElements == 0)
|
if (maxElements == 0)
|
||||||
{
|
{
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var result = context.Result;
|
||||||
|
|
||||||
for (var i = 0; i < maxElements; i++)
|
for (var i = 0; i < maxElements; i++)
|
||||||
{
|
{
|
||||||
var candidateIndex = position + i;
|
var candidateIndex = position + i;
|
||||||
|
|
@ -216,6 +223,11 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
return candidateIndex;
|
return candidateIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frame.AttributeName.Equals("value", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
capturedValueAttribute = frame.AttributeValue as string;
|
||||||
|
}
|
||||||
|
|
||||||
switch (frame.AttributeValue)
|
switch (frame.AttributeValue)
|
||||||
{
|
{
|
||||||
case bool flag when flag:
|
case bool flag when flag:
|
||||||
|
|
@ -247,6 +259,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
return (componentId, GetCurrentRenderTreeFrames(componentId));
|
return (componentId, GetCurrentRenderTreeFrames(componentId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class HtmlRenderingContext
|
||||||
|
{
|
||||||
|
public List<string> Result { get; } = new List<string>();
|
||||||
|
|
||||||
|
public string ClosestSelectValueAsString { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6,23 +6,22 @@ using System.Collections.Generic;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
namespace Microsoft.AspNetCore.Mvc.RazorComponents
|
||||||
{
|
{
|
||||||
public abstract class HtmlRendererTestBase
|
public class HtmlRendererTest
|
||||||
{
|
{
|
||||||
protected readonly Func<string, string> _encoder = (string t) => HtmlEncoder.Default.Encode(t);
|
protected readonly Func<string, string> _encoder = (string t) => HtmlEncoder.Default.Encode(t);
|
||||||
|
|
||||||
protected abstract HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider);
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RenderComponentAsync_CanRenderEmptyElement()
|
public void RenderComponentAsync_CanRenderEmptyElement()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
||||||
var expectedHtml = new[] { "<", "p", ">", "</", "p", ">" };
|
var expectedHtml = new[] { "<", "p", ">", "</", "p", ">" };
|
||||||
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||||
{
|
{
|
||||||
|
|
@ -99,7 +98,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
Assert.Equal(expectedHtml, result);
|
Assert.Equal(expectedHtml, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RenderComponentAsync_CanRenderWithAttributes()
|
public void RenderComponentAsync_CanRenderWithAttributes()
|
||||||
{
|
{
|
||||||
|
|
@ -272,6 +270,91 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedHtml, result);
|
Assert.Equal(expectedHtml, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RenderComponentAsync_MarksSelectedOptionsAsSelected()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedHtml = "<p>" +
|
||||||
|
@"<select unrelated-attribute-before=""a"" value=""b"" unrelated-attribute-after=""c"">" +
|
||||||
|
@"<option unrelated-attribute=""a"" value=""a"">Pick value a</option>" +
|
||||||
|
@"<option unrelated-attribute=""a"" value=""b"" selected>Pick value b</option>" +
|
||||||
|
@"<option unrelated-attribute=""a"" value=""c"">Pick value c</option>" +
|
||||||
|
"</select>" +
|
||||||
|
@"<option value=""b"">unrelated option</option>" +
|
||||||
|
"</p>";
|
||||||
|
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||||
|
{
|
||||||
|
rtb.OpenElement(0, "p");
|
||||||
|
rtb.OpenElement(1, "select");
|
||||||
|
rtb.AddAttribute(2, "unrelated-attribute-before", "a");
|
||||||
|
rtb.AddAttribute(3, "value", "b");
|
||||||
|
rtb.AddAttribute(4, "unrelated-attribute-after", "c");
|
||||||
|
|
||||||
|
foreach (var optionValue in new[] { "a", "b", "c"})
|
||||||
|
{
|
||||||
|
rtb.OpenElement(5, "option");
|
||||||
|
rtb.AddAttribute(6, "unrelated-attribute", "a");
|
||||||
|
rtb.AddAttribute(7, "value", optionValue);
|
||||||
|
rtb.AddContent(8, $"Pick value {optionValue}");
|
||||||
|
rtb.CloseElement(); // option
|
||||||
|
}
|
||||||
|
|
||||||
|
rtb.CloseElement(); // select
|
||||||
|
|
||||||
|
rtb.OpenElement(9, "option"); // To show other value-matching options don't get marked as selected
|
||||||
|
rtb.AddAttribute(10, "value", "b");
|
||||||
|
rtb.AddContent(11, "unrelated option");
|
||||||
|
rtb.CloseElement(); // option
|
||||||
|
|
||||||
|
rtb.CloseElement(); // p
|
||||||
|
})).BuildServiceProvider();
|
||||||
|
|
||||||
|
var htmlRenderer = GetHtmlRenderer(serviceProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterView.Empty)));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedHtml, string.Concat(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RenderComponentAsync_MarksSelectedOptionsAsSelected_WithOptGroups()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedHtml =
|
||||||
|
@"<select value=""beta"">" +
|
||||||
|
@"<optgroup><option value=""alpha"">alpha</option></optgroup>" +
|
||||||
|
@"<optgroup><option value=""beta"" selected>beta</option></optgroup>" +
|
||||||
|
@"<optgroup><option value=""gamma"">gamma</option></optgroup>" +
|
||||||
|
"</select>";
|
||||||
|
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||||
|
{
|
||||||
|
rtb.OpenElement(0, "select");
|
||||||
|
rtb.AddAttribute(1, "value", "beta");
|
||||||
|
|
||||||
|
foreach (var optionValue in new[] { "alpha", "beta", "gamma" })
|
||||||
|
{
|
||||||
|
rtb.OpenElement(2, "optgroup");
|
||||||
|
rtb.OpenElement(3, "option");
|
||||||
|
rtb.AddAttribute(4, "value", optionValue);
|
||||||
|
rtb.AddContent(5, optionValue);
|
||||||
|
rtb.CloseElement(); // option
|
||||||
|
rtb.CloseElement(); // optgroup
|
||||||
|
}
|
||||||
|
|
||||||
|
rtb.CloseElement(); // select
|
||||||
|
})).BuildServiceProvider();
|
||||||
|
|
||||||
|
var htmlRenderer = GetHtmlRenderer(serviceProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<TestComponent>(ParameterView.Empty)));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedHtml, string.Concat(result));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RenderComponentAsync_CanRenderComponentAsyncWithChildrenComponents()
|
public void RenderComponentAsync_CanRenderComponentAsyncWithChildrenComponents()
|
||||||
|
|
@ -358,11 +441,11 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<ComponentWithParameters>(
|
var result = GetResult(htmlRenderer.Dispatcher.InvokeAsync(() => htmlRenderer.RenderComponentAsync<ComponentWithParameters>(
|
||||||
new ParameterView(new[] {
|
ParameterView.FromDictionary(new Dictionary<string, object>
|
||||||
RenderTreeFrame.Element(0,string.Empty),
|
{
|
||||||
RenderTreeFrame.Attribute(1,"update",change),
|
{ "update", change },
|
||||||
RenderTreeFrame.Attribute(2,"value",5)
|
{ "value", 5 }
|
||||||
}, 0))));
|
}))));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedHtml, result);
|
Assert.Equal(expectedHtml, result);
|
||||||
|
|
@ -499,6 +582,31 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
Assert.Equal(expectedHtml, result.Tokens);
|
Assert.Equal(expectedHtml, result.Tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PrerendersMultipleComponentsSuccessfully()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var serviceProvider = new ServiceCollection().AddSingleton(new RenderFragment(rtb =>
|
||||||
|
{
|
||||||
|
rtb.OpenElement(0, "p");
|
||||||
|
rtb.AddMarkupContent(1, "<span>Hello world!</span>");
|
||||||
|
rtb.CloseElement();
|
||||||
|
})).BuildServiceProvider();
|
||||||
|
var renderer = GetHtmlRenderer(serviceProvider);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var first = await renderer.Dispatcher.InvokeAsync(() => renderer.RenderComponentAsync<TestComponent>(ParameterView.Empty));
|
||||||
|
var second = await renderer.Dispatcher.InvokeAsync(() => renderer.RenderComponentAsync<TestComponent>(ParameterView.Empty));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(0, first.ComponentId);
|
||||||
|
Assert.Equal(1, second.ComponentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, _encoder);
|
||||||
|
}
|
||||||
|
|
||||||
private class NestedAsyncComponent : ComponentBase
|
private class NestedAsyncComponent : ComponentBase
|
||||||
{
|
{
|
||||||
Loading…
Reference in New Issue