diff --git a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs index e7b60daa24..b75ca7546e 100644 --- a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs +++ b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering { public partial class WebAssemblyRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer { - public WebAssemblyRenderer(System.IServiceProvider serviceProvider) : base (default(System.IServiceProvider)) { } + public WebAssemblyRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; } public System.Threading.Tasks.Task AddComponentAsync(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; } public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; } diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs index a77d66ce13..8cb5d87561 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs +++ b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyBlazorApplicationBuilder.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Blazor.Rendering; using Microsoft.AspNetCore.Components.Builder; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Blazor.Hosting { @@ -38,7 +40,8 @@ namespace Microsoft.AspNetCore.Blazor.Hosting public async Task CreateRendererAsync() { - var renderer = new WebAssemblyRenderer(Services); + var loggerFactory = (ILoggerFactory)Services.GetService(typeof(ILoggerFactory)); + var renderer = new WebAssemblyRenderer(Services, loggerFactory); for (var i = 0; i < Entries.Count; i++) { var (componentType, domElementSelector) = Entries[i]; diff --git a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs b/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs index efc20a7348..9bf7251ea1 100644 --- a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs +++ b/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Blazor.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Blazor.Rendering { @@ -26,8 +27,9 @@ namespace Microsoft.AspNetCore.Blazor.Rendering /// Constructs an instance of . /// /// The to use when initializing components. - public WebAssemblyRenderer(IServiceProvider serviceProvider) - : base(serviceProvider) + /// The . + public WebAssemblyRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) + : base(serviceProvider, loggerFactory) { // The browser renderer registers and unregisters itself with the static // registry. This works well with the WebAssembly runtime, and is simple for the diff --git a/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs b/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs index f8fa9ed4b2..7e41359917 100644 --- a/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs +++ b/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs @@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Razor; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -441,7 +442,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test protected class TestRenderer : Renderer { - public TestRenderer() : base(new TestServiceProvider(), CreateDefaultDispatcher()) + public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, CreateDefaultDispatcher()) { } diff --git a/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs b/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs index fb49307851..12560fe9c8 100644 --- a/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs +++ b/src/Components/Components/perf/RenderTreeDiffBuilderBenchmark.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Components.Performance { @@ -87,7 +88,7 @@ namespace Microsoft.AspNetCore.Components.Performance private class FakeRenderer : Renderer { public FakeRenderer() - : base(new TestServiceProvider(), new RendererSynchronizationContext()) + : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs index f70009ecfb..c44fe63724 100644 --- a/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs +++ b/src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs @@ -674,7 +674,7 @@ namespace Microsoft.AspNetCore.Components.Rendering } public partial class HtmlRenderer : Microsoft.AspNetCore.Components.Rendering.Renderer { - public HtmlRenderer(System.IServiceProvider serviceProvider, System.Func htmlEncoder, Microsoft.AspNetCore.Components.IDispatcher dispatcher) : base (default(System.IServiceProvider)) { } + public HtmlRenderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Components.IDispatcher dispatcher, System.Func htmlEncoder) : base (default(System.IServiceProvider), default(Microsoft.Extensions.Logging.ILoggerFactory)) { } protected override void HandleException(System.Exception exception) { } [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task RenderComponentAsync(System.Type componentType, Microsoft.AspNetCore.Components.ParameterCollection initialParameters) { throw null; } @@ -692,8 +692,8 @@ namespace Microsoft.AspNetCore.Components.Rendering } public abstract partial class Renderer : System.IDisposable { - public Renderer(System.IServiceProvider serviceProvider) { } - public Renderer(System.IServiceProvider serviceProvider, Microsoft.AspNetCore.Components.IDispatcher dispatcher) { } + public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } + public Renderer(System.IServiceProvider serviceProvider, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Components.IDispatcher dispatcher) { } public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } } protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { } protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; } diff --git a/src/Components/Components/src/Rendering/HtmlRenderer.cs b/src/Components/Components/src/Rendering/HtmlRenderer.cs index 0366d5c1af..d844f7497b 100644 --- a/src/Components/Components/src/Rendering/HtmlRenderer.cs +++ b/src/Components/Components/src/Rendering/HtmlRenderer.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Components.Rendering { @@ -26,10 +27,11 @@ namespace Microsoft.AspNetCore.Components.Rendering /// Initializes a new instance of . /// /// The to use to instantiate components. + /// The . + /// The to be for invoking user actions into the context. /// A that will HTML encode the given string. - /// - public HtmlRenderer(IServiceProvider serviceProvider, Func htmlEncoder, IDispatcher dispatcher) - : base(serviceProvider, dispatcher) + public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher, Func htmlEncoder) + : base(serviceProvider, loggerFactory, dispatcher) { _htmlEncoder = htmlEncoder; } diff --git a/src/Components/Components/src/Rendering/Renderer.Log.cs b/src/Components/Components/src/Rendering/Renderer.Log.cs new file mode 100644 index 0000000000..1a737c21c4 --- /dev/null +++ b/src/Components/Components/src/Rendering/Renderer.Log.cs @@ -0,0 +1,65 @@ +// 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; + +namespace Microsoft.AspNetCore.Components.Rendering +{ + public abstract partial class Renderer + { + internal static class Log + { + private static readonly Action _initializingChildComponent = + LoggerMessage.Define(LogLevel.Debug, new EventId(1, "InitializingChildComponent"), "Initializing component {ComponentId} ({ComponentType}) as child of {ParentComponentId} ({ParentComponentId})"); + + private static readonly Action _initializingRootComponent = + LoggerMessage.Define(LogLevel.Debug, new EventId(2, "InitializingRootComponent"), "Initializing root component {ComponentId} ({ComponentType})"); + + private static readonly Action _renderingComponent = + LoggerMessage.Define(LogLevel.Debug, new EventId(3, "RenderingComponent"), "Rendering component {ComponentId} of type {ComponentType}"); + + private static readonly Action _disposingComponent = + LoggerMessage.Define(LogLevel.Debug, new EventId(4, "DisposingComponent"), "Disposing component {ComponentId} of type {ComponentType}"); + + private static readonly Action _handlingEvent = + LoggerMessage.Define(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'"); + + public static void InitializingComponent(ILogger logger, ComponentState componentState, ComponentState parentComponentState) + { + if (logger.IsEnabled(LogLevel.Debug)) // This is almost always false, so skip the evaluations + { + if (parentComponentState == null) + { + _initializingRootComponent(logger, componentState.ComponentId, componentState.Component.GetType(), null); + } + else + { + _initializingChildComponent(logger, componentState.ComponentId, componentState.Component.GetType(), parentComponentState.ComponentId, parentComponentState.Component.GetType(), null); + } + } + } + + public static void RenderingComponent(ILogger logger, ComponentState componentState) + { + if (logger.IsEnabled(LogLevel.Debug)) // This is almost always false, so skip the evaluations + { + _renderingComponent(logger, componentState.ComponentId, componentState.Component.GetType(), null); + } + } + + internal static void DisposingComponent(ILogger logger, ComponentState componentState) + { + if (logger.IsEnabled(LogLevel.Debug)) // This is almost always false, so skip the evaluations + { + _disposingComponent(logger, componentState.ComponentId, componentState.Component.GetType(), null); + } + } + + internal static void HandlingEvent(ILogger logger, int eventHandlerId, UIEventArgs eventArgs) + { + _handlingEvent(logger, eventHandlerId, eventArgs?.Type ?? "null", null); + } + } + } +} diff --git a/src/Components/Components/src/Rendering/Renderer.cs b/src/Components/Components/src/Rendering/Renderer.cs index d0f3162aff..0ab6ad5b66 100644 --- a/src/Components/Components/src/Rendering/Renderer.cs +++ b/src/Components/Components/src/Rendering/Renderer.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Components.Rendering { @@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// Provides mechanisms for rendering hierarchies of instances, /// dispatching events to them, and notifying when the user interface is being updated. /// - public abstract class Renderer : IDisposable + public abstract partial class Renderer : IDisposable { private readonly ComponentFactory _componentFactory; private readonly Dictionary _componentStateById = new Dictionary(); @@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Components.Rendering private readonly Dictionary _eventBindings = new Dictionary(); private readonly Dictionary _eventHandlerIdReplacements = new Dictionary(); private readonly IDispatcher _dispatcher; + private readonly ILogger _logger; private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it private bool _isBatchInProgress; @@ -55,19 +57,33 @@ namespace Microsoft.AspNetCore.Components.Rendering /// Constructs an instance of . /// /// The to be used when initializing components. - public Renderer(IServiceProvider serviceProvider) + /// The . + public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) { + if (serviceProvider is null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + if (loggerFactory is null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + _componentFactory = new ComponentFactory(serviceProvider); + _logger = loggerFactory.CreateLogger(); } /// /// Constructs an instance of . /// /// The to be used when initializing components. + /// The . /// The to be for invoking user actions into the context. - public Renderer(IServiceProvider serviceProvider, IDispatcher dispatcher) : this(serviceProvider) + public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IDispatcher dispatcher) + : this(serviceProvider, loggerFactory) { - _dispatcher = dispatcher; + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); } /// @@ -189,6 +205,7 @@ namespace Microsoft.AspNetCore.Components.Rendering var componentId = _nextComponentId++; var parentComponentState = GetOptionalComponentState(parentComponentId); var componentState = new ComponentState(this, componentId, component, parentComponentState); + Log.InitializingComponent(_logger, componentState, parentComponentState); _componentStateById.Add(componentId, componentState); component.Configure(new RenderHandle(this, componentId)); return componentState; @@ -220,6 +237,8 @@ namespace Microsoft.AspNetCore.Components.Rendering throw new ArgumentException($"There is no event handler with ID {eventHandlerId}"); } + Log.HandlingEvent(_logger, eventHandlerId, eventArgs); + if (fieldInfo != null) { var latestEquivalentEventHandlerId = FindLatestEventHandlerIdInChain(eventHandlerId); @@ -613,14 +632,17 @@ namespace Microsoft.AspNetCore.Components.Rendering private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry) { - renderQueueEntry.ComponentState - .RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment); + var componentState = renderQueueEntry.ComponentState; + Log.RenderingComponent(_logger, componentState); + componentState.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment); // Process disposal queue now in case it causes further component renders to be enqueued while (_batchBuilder.ComponentDisposalQueue.Count > 0) { var disposeComponentId = _batchBuilder.ComponentDisposalQueue.Dequeue(); - GetRequiredComponentState(disposeComponentId).DisposeInBatch(_batchBuilder); + var disposeComponentState = GetRequiredComponentState(disposeComponentId); + Log.DisposingComponent(_logger, disposeComponentState); + disposeComponentState.DisposeInBatch(_batchBuilder); _componentStateById.Remove(disposeComponentId); _batchBuilder.DisposedComponentIds.Append(disposeComponentId); } @@ -710,6 +732,8 @@ namespace Microsoft.AspNetCore.Components.Rendering { foreach (var componentState in _componentStateById.Values) { + Log.DisposingComponent(_logger, componentState); + if (componentState.Component is IDisposable disposable) { try diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index e8060708f4..45fb9c5c85 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Layouts; using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Components.Routing { @@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Components.Routing string _baseUri; string _locationAbsolute; bool _navigationInterceptionEnabled; + ILogger _logger; [Inject] private IUriHelper UriHelper { get; set; } @@ -29,6 +31,8 @@ namespace Microsoft.AspNetCore.Components.Routing [Inject] private IComponentContext ComponentContext { get; set; } + [Inject] private ILoggerFactory LoggerFactory { get; set; } + /// /// Gets or sets the assembly that should be searched, along with its referenced /// assemblies, for components matching the URI. @@ -55,6 +59,7 @@ namespace Microsoft.AspNetCore.Components.Routing /// public void Configure(RenderHandle renderHandle) { + _logger = LoggerFactory.CreateLogger(); _renderHandle = renderHandle; _baseUri = UriHelper.GetBaseUri(); _locationAbsolute = UriHelper.GetAbsoluteUri(); @@ -111,12 +116,16 @@ namespace Microsoft.AspNetCore.Components.Routing $"does not implement {typeof(IComponent).FullName}."); } + Log.NavigatingToComponent(_logger, context.Handler, locationPath, _baseUri); + _renderHandle.Render(builder => Render(builder, context.Handler, context.Parameters)); } else { if (!isNavigationIntercepted && NotFoundContent != null) { + Log.DisplayingNotFoundContent(_logger, locationPath, _baseUri); + // We did not find a Component that matches the route. // Only show the NotFoundContent if the application developer programatically got us here i.e we did not // intercept the navigation. In all other cases, force a browser navigation since this could be non-Blazor content. @@ -124,6 +133,7 @@ namespace Microsoft.AspNetCore.Components.Routing } else { + Log.NavigatingToExternalUri(_logger, _locationAbsolute, locationPath, _baseUri); UriHelper.NavigateTo(_locationAbsolute, forceLoad: true); } } @@ -148,5 +158,32 @@ namespace Microsoft.AspNetCore.Components.Routing return Task.CompletedTask; } + + private static class Log + { + private static readonly Action _displayingNotFoundContent = + LoggerMessage.Define(LogLevel.Debug, new EventId(1, "DisplayingNotFoundContent"), $"Displaying {nameof(NotFoundContent)} because path '{{Path}}' with base URI '{{BaseUri}}' does not match any component route"); + + private static readonly Action _navigatingToComponent = + LoggerMessage.Define(LogLevel.Debug, new EventId(2, "NavigatingToComponent"), "Navigating to component {ComponentType} in response to path '{Path}' with base URI '{BaseUri}'"); + + private static readonly Action _navigatingToExternalUri = + LoggerMessage.Define(LogLevel.Debug, new EventId(3, "NavigatingToExternalUri"), "Navigating to non-component URI '{ExternalUri}' in response to path '{Path}' with base URI '{BaseUri}'"); + + internal static void DisplayingNotFoundContent(ILogger logger, string path, string baseUri) + { + _displayingNotFoundContent(logger, path, baseUri, null); + } + + internal static void NavigatingToComponent(ILogger logger, Type componentType, string path, string baseUri) + { + _navigatingToComponent(logger, componentType, path, baseUri, null); + } + + internal static void NavigatingToExternalUri(ILogger logger, string externalUri, string path, string baseUri) + { + _navigatingToExternalUri(logger, externalUri, path, baseUri, null); + } + } } } diff --git a/src/Components/Components/test/RenderTreeBuilderTest.cs b/src/Components/Components/test/RenderTreeBuilderTest.cs index bfd73e040d..91c1d3b318 100644 --- a/src/Components/Components/test/RenderTreeBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeBuilderTest.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; +using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -1794,7 +1795,7 @@ namespace Microsoft.AspNetCore.Components.Test private class TestRenderer : Renderer { - public TestRenderer() : base(new TestServiceProvider(), new RendererSynchronizationContext()) + public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs index b1dff64477..b640247e4d 100644 --- a/src/Components/Components/test/RenderTreeDiffBuilderTest.cs +++ b/src/Components/Components/test/RenderTreeDiffBuilderTest.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.Components.Test @@ -2207,7 +2208,7 @@ namespace Microsoft.AspNetCore.Components.Test private class FakeRenderer : Renderer { - public FakeRenderer() : base(new TestServiceProvider(), new RendererSynchronizationContext()) + public FakeRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index 054d5511bf..1cd41792bf 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Test.Helpers; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.Components.Test @@ -3372,7 +3373,7 @@ namespace Microsoft.AspNetCore.Components.Test private class NoOpRenderer : Renderer { - public NoOpRenderer() : base(new TestServiceProvider(), new RendererSynchronizationContext()) + public NoOpRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Components/Components/test/Rendering/HtmlRendererTests.cs b/src/Components/Components/test/Rendering/HtmlRendererTests.cs index 45d7eaf4d2..072ee563d5 100644 --- a/src/Components/Components/test/Rendering/HtmlRendererTests.cs +++ b/src/Components/Components/test/Rendering/HtmlRendererTests.cs @@ -2,6 +2,7 @@ // 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 { @@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Rendering { protected override HtmlRenderer GetHtmlRenderer(IServiceProvider serviceProvider) { - return new HtmlRenderer(serviceProvider, _encoder, Dispatcher); + return new HtmlRenderer(serviceProvider, NullLoggerFactory.Instance, Dispatcher, _encoder); } } } diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index f57e7ce141..5ee9520582 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -351,7 +351,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits "Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}"); _disposingCircuit = LoggerMessage.Define( - LogLevel.Trace, + LogLevel.Debug, EventIds.DisposingCircuit, "Disposing circuit with identifier {CircuitId}"); diff --git a/src/Components/Server/src/Circuits/CircuitRegistry.cs b/src/Components/Server/src/Circuits/CircuitRegistry.cs index d061721255..d1bac2c6d7 100644 --- a/src/Components/Server/src/Circuits/CircuitRegistry.cs +++ b/src/Components/Server/src/Circuits/CircuitRegistry.cs @@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits "Unhandled exception disposing circuit host: {Message}"); _unhandledExceptionDisposingTokenSource = LoggerMessage.Define( - LogLevel.Trace, + LogLevel.Debug, EventIds.ExceptionDisposingTokenSource, "Exception thrown when disposing token source: {Message}"); diff --git a/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs b/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs index 0bb5d5f111..343a191326 100644 --- a/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs +++ b/src/Components/Server/src/Circuits/DefaultCircuitFactory.cs @@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { private readonly IServiceScopeFactory _scopeFactory; private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; private readonly CircuitIdFactory _circuitIdFactory; public DefaultCircuitFactory( @@ -30,6 +31,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); _loggerFactory = loggerFactory; + _logger = _loggerFactory.CreateLogger(); _circuitIdFactory = circuitIdFactory ?? throw new ArgumentNullException(nameof(circuitIdFactory)); } @@ -72,6 +74,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits var dispatcher = Renderer.CreateDefaultDispatcher(); var renderer = new RemoteRenderer( scope.ServiceProvider, + _loggerFactory, rendererRegistry, jsRuntime, client, @@ -94,6 +97,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits jsRuntime, circuitHandlers, _loggerFactory.CreateLogger()); + Log.CreatedCircuit(_logger, circuitHost); // Initialize per - circuit data that services need (circuitHost.Services.GetRequiredService() as DefaultCircuitAccessor).Circuit = circuitHost.Circuit; @@ -124,5 +128,26 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits return componentsMetadata; } } + + private static class Log + { + private static readonly Action _createdConnectedCircuit = + LoggerMessage.Define(LogLevel.Debug, new EventId(1, "CreatedConnectedCircuit"), "Created circuit {CircuitId} for connection {ConnectionId}"); + + private static readonly Action _createdDisconnectedCircuit = + LoggerMessage.Define(LogLevel.Debug, new EventId(2, "CreatedDisconnectedCircuit"), "Created circuit {CircuitId} for disconnected client"); + + internal static void CreatedCircuit(ILogger logger, CircuitHost circuitHost) + { + if (circuitHost.Client.Connected) + { + _createdConnectedCircuit(logger, circuitHost.CircuitId, circuitHost.Client.ConnectionId, null); + } + else + { + _createdDisconnectedCircuit(logger, circuitHost.CircuitId, null); + } + } + } } } diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index 0d3225d0f2..6003340eec 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -37,13 +37,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering /// public RemoteRenderer( IServiceProvider serviceProvider, + ILoggerFactory loggerFactory, RendererRegistry rendererRegistry, IJSRuntime jsRuntime, CircuitClientProxy client, IDispatcher dispatcher, HtmlEncoder encoder, ILogger logger) - : base(serviceProvider, encoder.Encode, dispatcher) + : base(serviceProvider, loggerFactory, dispatcher, encoder.Encode) { _rendererRegistry = rendererRegistry; _jsRuntime = jsRuntime; @@ -179,7 +180,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering return; } - Log.BeginUpdateDisplayAsync(_logger, _client.ConnectionId); + Log.BeginUpdateDisplayAsync(_logger, _client.ConnectionId, pending.BatchId, pending.Data.Length); await _client.SendAsync("JS.RenderBatch", Id, pending.BatchId, pending.Data); } catch (Exception e) @@ -212,10 +213,15 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering } else { - var message = $"Completing batch {entry.BatchId} " + - (errorMessageOrNull == null ? "without error." : "with error."); + if (errorMessageOrNull == null) + { + Log.CompletingBatchWithoutError(_logger, entry.BatchId); + } + else + { + Log.CompletingBatchWithError(_logger, entry.BatchId, errorMessageOrNull); + } - _logger.LogDebug(message); CompleteRender(entry.CompletionSource, errorMessageOrNull); } } @@ -260,9 +266,11 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering private static class Log { private static readonly Action _unhandledExceptionRenderingComponent; - private static readonly Action _beginUpdateDisplayAsync; + private static readonly Action _beginUpdateDisplayAsync; private static readonly Action _bufferingRenderDisconnectedClient; private static readonly Action _sendBatchDataFailed; + private static readonly Action _completingBatchWithError; + private static readonly Action _completingBatchWithoutError; private static class EventIds { @@ -270,6 +278,8 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering public static readonly EventId BeginUpdateDisplayAsync = new EventId(101, "BeginUpdateDisplayAsync"); public static readonly EventId SkipUpdateDisplayAsync = new EventId(102, "SkipUpdateDisplayAsync"); public static readonly EventId SendBatchDataFailed = new EventId(103, "SendBatchDataFailed"); + public static readonly EventId CompletingBatchWithError = new EventId(104, "CompletingBatchWithError"); + public static readonly EventId CompletingBatchWithoutError = new EventId(105, "CompletingBatchWithoutError"); } static Log() @@ -279,13 +289,13 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering EventIds.UnhandledExceptionRenderingComponent, "Unhandled exception rendering component: {Message}"); - _beginUpdateDisplayAsync = LoggerMessage.Define( - LogLevel.Trace, + _beginUpdateDisplayAsync = LoggerMessage.Define( + LogLevel.Debug, EventIds.BeginUpdateDisplayAsync, - "Begin remote rendering of components on client {ConnectionId}."); + "Sending render batch {BatchId} of size {DataLength} bytes to client {ConnectionId}."); _bufferingRenderDisconnectedClient = LoggerMessage.Define( - LogLevel.Trace, + LogLevel.Debug, EventIds.SkipUpdateDisplayAsync, "Buffering remote render because the client on connection {ConnectionId} is disconnected."); @@ -293,6 +303,16 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering LogLevel.Information, EventIds.SendBatchDataFailed, "Sending data for batch failed: {Message}"); + + _completingBatchWithError = LoggerMessage.Define( + LogLevel.Debug, + EventIds.CompletingBatchWithError, + "Completing batch {BatchId} with error: {ErrorMessage}"); + + _completingBatchWithoutError = LoggerMessage.Define( + LogLevel.Debug, + EventIds.CompletingBatchWithoutError, + "Completing batch {BatchId} without error"); } public static void SendBatchDataFailed(ILogger logger, Exception exception) @@ -308,10 +328,12 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering exception); } - public static void BeginUpdateDisplayAsync(ILogger logger, string connectionId) + public static void BeginUpdateDisplayAsync(ILogger logger, string connectionId, long batchId, int dataLength) { _beginUpdateDisplayAsync( logger, + batchId, + dataLength, connectionId, null); } @@ -323,6 +345,23 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering connectionId, null); } + + public static void CompletingBatchWithError(ILogger logger, long batchId, string errorMessage) + { + _completingBatchWithError( + logger, + batchId, + errorMessage, + null); + } + + public static void CompletingBatchWithoutError(ILogger logger, long batchId) + { + _completingBatchWithoutError( + logger, + batchId, + null); + } } } } diff --git a/src/Components/Server/src/Circuits/RemoteUriHelper.cs b/src/Components/Server/src/Circuits/RemoteUriHelper.cs index 62f8877735..f89e917db0 100644 --- a/src/Components/Server/src/Circuits/RemoteUriHelper.cs +++ b/src/Components/Server/src/Circuits/RemoteUriHelper.cs @@ -59,8 +59,6 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits Interop.ListenForNavigationEvents, typeof(RemoteUriHelper).Assembly.GetName().Name, nameof(NotifyLocationChanged)); - - _logger.LogDebug($"{nameof(RemoteUriHelper)} initialized."); } /// @@ -75,18 +73,18 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits var message = $"{nameof(NotifyLocationChanged)} called without a circuit."; throw new InvalidOperationException(message); } + var uriHelper = (RemoteUriHelper)circuit.Services.GetRequiredService(); + Log.ReceivedLocationChangedNotification(uriHelper._logger, uriAbsolute, isInterceptedLink); uriHelper.SetAbsoluteUri(uriAbsolute); - - uriHelper._logger.LogDebug($"Location changed to '{uriAbsolute}'."); uriHelper.TriggerOnLocationChanged(isInterceptedLink); } /// protected override void NavigateToCore(string uri, bool forceLoad) { - _logger.LogDebug($"{uri} force load {forceLoad}."); + Log.RequestingNavigation(_logger, uri, forceLoad); if (_jsRuntime == null) { @@ -94,5 +92,24 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits } _jsRuntime.InvokeAsync(Interop.NavigateTo, uri, forceLoad); } + + private static class Log + { + private static readonly Action _requestingNavigation = + LoggerMessage.Define(LogLevel.Debug, new EventId(1, "RequestingNavigation"), "Requesting navigation to URI {Uri} with forceLoad={ForceLoad}"); + + private static readonly Action _receivedLocationChangedNotification = + LoggerMessage.Define(LogLevel.Debug, new EventId(2, "ReceivedLocationChangedNotification"), "Received notification that the URI has changed to {Uri} with isIntercepted={IsIntercepted}"); + + public static void RequestingNavigation(ILogger logger, string uri, bool forceLoad) + { + _requestingNavigation(logger, uri, forceLoad, null); + } + + public static void ReceivedLocationChangedNotification(ILogger logger, string uri, bool isIntercepted) + { + _receivedLocationChangedNotification(logger, uri, isIntercepted, null); + } + } } } diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index 4dcb8873af..4357527b5f 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Components.Server var endpointFeature = Context.GetHttpContext().Features.Get(); var endpoint = endpointFeature?.Endpoint; - _logger.LogDebug($"No components registered in the current endpoint '{endpoint.DisplayName}'."); + Log.NoComponentsRegisteredInEndpoint(_logger, endpoint.DisplayName); // No components preregistered so return. This is totally normal if the components were prerendered. return null; @@ -131,16 +131,18 @@ namespace Microsoft.AspNetCore.Components.Server /// public void OnRenderCompleted(long renderId, string errorMessageOrNull) { - _logger.LogDebug($"Received confirmation for batch {renderId}."); + Log.ReceivedConfirmationForBatch(_logger, renderId); EnsureCircuitHost().Renderer.OnRenderCompleted(renderId, errorMessageOrNull); } private async void CircuitHost_UnhandledException(object sender, UnhandledExceptionEventArgs e) { var circuitHost = (CircuitHost)sender; + var circuitId = circuitHost?.CircuitId; + try { - _logger.LogWarning((Exception)e.ExceptionObject, "Unhandled Server-Side exception"); + Log.UnhandledExceptionInCircuit(_logger, circuitId, (Exception)e.ExceptionObject); await circuitHost.Client.SendAsync("JS.Error", e.ExceptionObject); // We generally can't abort the connection here since this is an async @@ -149,7 +151,7 @@ namespace Microsoft.AspNetCore.Components.Server } catch (Exception ex) { - _logger.LogError(ex, "Failed to transmit exception to client"); + Log.FailedToTransmitException(_logger, circuitId, ex); } } @@ -164,5 +166,40 @@ namespace Microsoft.AspNetCore.Components.Server return circuitHost; } + + private static class Log + { + private static readonly Action _noComponentsRegisteredInEndpoint = + LoggerMessage.Define(LogLevel.Debug, new EventId(1, "NoComponentsRegisteredInEndpoint"), "No components registered in the current endpoint '{Endpoint}'"); + + private static readonly Action _receivedConfirmationForBatch = + LoggerMessage.Define(LogLevel.Debug, new EventId(2, "ReceivedConfirmationForBatch"), "Received confirmation for batch {BatchId}"); + + private static readonly Action _unhandledExceptionInCircuit = + LoggerMessage.Define(LogLevel.Warning, new EventId(3, "UnhandledExceptionInCircuit"), "Unhandled exception in circuit {CircuitId}"); + + private static readonly Action _failedToTransmitException = + LoggerMessage.Define(LogLevel.Debug, new EventId(4, "FailedToTransmitException"), "Failed to transmit exception to client in circuit {CircuitId}"); + + public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName) + { + _noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null); + } + + public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) + { + _receivedConfirmationForBatch(logger, batchId, null); + } + + public static void UnhandledExceptionInCircuit(ILogger logger, string circuitId, Exception exception) + { + _unhandledExceptionInCircuit(logger, circuitId, exception); + } + + public static void FailedToTransmitException(ILogger logger, string circuitId, Exception transmissionException) + { + _failedToTransmitException(logger, circuitId, transmissionException); + } + } } } diff --git a/src/Components/Server/test/Circuits/CircuitHostTest.cs b/src/Components/Server/test/Circuits/CircuitHostTest.cs index 65605e17eb..36096524f6 100644 --- a/src/Components/Server/test/Circuits/CircuitHostTest.cs +++ b/src/Components/Server/test/Circuits/CircuitHostTest.cs @@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private class TestRemoteRenderer : RemoteRenderer { public TestRemoteRenderer(IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IDispatcher dispatcher, IJSRuntime jsRuntime, IClientProxy client) - : base(serviceProvider, rendererRegistry, jsRuntime, new CircuitClientProxy(client, "connection"), dispatcher, HtmlEncoder.Default, NullLogger.Instance) + : base(serviceProvider, NullLoggerFactory.Instance, rendererRegistry, jsRuntime, new CircuitClientProxy(client, "connection"), dispatcher, HtmlEncoder.Default, NullLogger.Instance) { } diff --git a/src/Components/Server/test/Circuits/RemoteRendererTest.cs b/src/Components/Server/test/Circuits/RemoteRendererTest.cs index 48b85230af..966419f420 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -264,6 +264,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering return new RemoteRenderer( serviceProvider, + NullLoggerFactory.Instance, new RendererRegistry(), jsRuntime.Object, circuitClientProxy, diff --git a/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs b/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs index ceea972a10..4c187b02fa 100644 --- a/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs +++ b/src/Components/Server/test/Circuits/RenderBatchWriterTest.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using System; using System.Collections.Generic; using System.IO; @@ -375,7 +376,7 @@ namespace Microsoft.AspNetCore.Components.Server class FakeRenderer : Renderer { public FakeRenderer() - : base(new ServiceCollection().BuildServiceProvider(), new RendererSynchronizationContext()) + : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Components/Server/test/Circuits/TestCircuitHost.cs b/src/Components/Server/test/Circuits/TestCircuitHost.cs index afb279dd0e..89ab0c2679 100644 --- a/src/Components/Server/test/Circuits/TestCircuitHost.cs +++ b/src/Components/Server/test/Circuits/TestCircuitHost.cs @@ -46,6 +46,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { remoteRenderer = new RemoteRenderer( serviceScope.ServiceProvider ?? Mock.Of(), + NullLoggerFactory.Instance, new RendererRegistry(), jsRuntime, clientProxy, diff --git a/src/Components/Shared/test/TestRenderer.cs b/src/Components/Shared/test/TestRenderer.cs index 0e08894749..d2832e7473 100644 --- a/src/Components/Shared/test/TestRenderer.cs +++ b/src/Components/Shared/test/TestRenderer.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Microsoft.AspNetCore.Components.Test.Helpers @@ -17,11 +18,11 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers { } - public TestRenderer(IDispatcher dispatcher) : base(new TestServiceProvider(), dispatcher) + public TestRenderer(IDispatcher dispatcher) : base(new TestServiceProvider(), NullLoggerFactory.Instance, dispatcher) { } - public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider, new RendererSynchronizationContext()) + public TestRenderer(IServiceProvider serviceProvider) : base(serviceProvider, NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs b/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs index f12204916a..a4f0dafa56 100644 --- a/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs +++ b/src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs @@ -6,11 +6,13 @@ using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; +using Castle.Core.Logging; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Ignitor @@ -333,7 +335,7 @@ namespace Ignitor class FakeRenderer : Renderer { public FakeRenderer() - : base(new ServiceCollection().BuildServiceProvider(), new RendererSynchronizationContext()) + : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Components/test/testassets/Ignitor/RenderBatchReader.cs b/src/Components/test/testassets/Ignitor/RenderBatchReader.cs index 3a001dce82..bfe28e74cb 100644 --- a/src/Components/test/testassets/Ignitor/RenderBatchReader.cs +++ b/src/Components/test/testassets/Ignitor/RenderBatchReader.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; namespace Ignitor { @@ -288,7 +289,7 @@ namespace Ignitor public class FakeRenderer : Renderer { public FakeRenderer() - : base(new ServiceCollection().BuildServiceProvider(), new RendererSynchronizationContext()) + : base(new ServiceCollection().BuildServiceProvider(), NullLoggerFactory.Instance, new RendererSynchronizationContext()) { } diff --git a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs index f3084ad5be..2b415d05fc 100644 --- a/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs +++ b/src/Mvc/Mvc.ViewFeatures/src/RazorComponents/StaticComponentRenderer.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents { @@ -32,7 +33,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents var dispatcher = Renderer.CreateDefaultDispatcher(); InitializeUriHelper(httpContext); - using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, _encoder.Encode, dispatcher)) + var loggerFactory = (ILoggerFactory)httpContext.RequestServices.GetService(typeof (ILoggerFactory)); + using (var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, loggerFactory, dispatcher, _encoder.Encode)) { ComponentRenderedText result = default; try diff --git a/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs b/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs index 639fa4e66c..1034200ddd 100644 --- a/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs +++ b/src/Mvc/Mvc.ViewFeatures/test/HtmlHelperComponentExtensionsTests.cs @@ -13,6 +13,8 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures.RazorComponents; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.JSInterop; using Microsoft.Net.Http.Headers; using Moq; @@ -224,6 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); configureServices?.Invoke(services);