diff --git a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs index ac56285eefde..456d9facd7e9 100644 --- a/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs +++ b/src/Components/Server/test/ComponentEndpointRouteBuilderExtensionsTest.cs @@ -1,6 +1,7 @@ // 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.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -16,12 +17,7 @@ public class ComponentEndpointRouteBuilderExtensionsTest public void MapBlazorHub_WiresUp_UnderlyingHub() { // Arrange - var applicationBuilder = new ApplicationBuilder( - new ServiceCollection() - .AddLogging() - .AddSingleton(Mock.Of()) - .AddSignalR().Services - .AddServerSideBlazor().Services.BuildServiceProvider()); + var applicationBuilder = CreateAppBuilder(); var called = false; // Act @@ -40,12 +36,7 @@ public void MapBlazorHub_WiresUp_UnderlyingHub() public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub() { // Arrange - var applicationBuilder = new ApplicationBuilder( - new ServiceCollection() - .AddLogging() - .AddSingleton(Mock.Of()) - .AddSignalR().Services - .AddServerSideBlazor().Services.BuildServiceProvider()); + var applicationBuilder = CreateAppBuilder(); var called = false; // Act @@ -59,5 +50,23 @@ public void MapBlazorHub_MostGeneralOverload_MapsUnderlyingHub() // Assert Assert.True(called); } + + private IApplicationBuilder CreateAppBuilder() + { + var services = new ServiceCollection(); + services.AddSingleton(Mock.Of()); + services.AddLogging(); + services.AddOptions(); + var listener = new DiagnosticListener("Microsoft.AspNetCore"); + services.AddSingleton(listener); + services.AddSingleton(listener); + services.AddRouting(); + services.AddSignalR(); + services.AddServerSideBlazor(); + + var serviceProvder = services.BuildServiceProvider(); + + return new ApplicationBuilder(serviceProvder); + } } } diff --git a/src/Http/Routing/src/EndpointRoutingMiddleware.cs b/src/Http/Routing/src/EndpointRoutingMiddleware.cs index b1fcc00a424d..482e86bb6dc0 100644 --- a/src/Http/Routing/src/EndpointRoutingMiddleware.cs +++ b/src/Http/Routing/src/EndpointRoutingMiddleware.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 System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -13,9 +14,12 @@ namespace Microsoft.AspNetCore.Routing { internal sealed class EndpointRoutingMiddleware { + private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched"; + private readonly MatcherFactory _matcherFactory; private readonly ILogger _logger; private readonly EndpointDataSource _endpointDataSource; + private readonly DiagnosticListener _diagnosticListener; private readonly RequestDelegate _next; private Task _initializationTask; @@ -24,31 +28,18 @@ public EndpointRoutingMiddleware( MatcherFactory matcherFactory, ILogger logger, IEndpointRouteBuilder endpointRouteBuilder, + DiagnosticListener diagnosticListener, RequestDelegate next) { - if (matcherFactory == null) - { - throw new ArgumentNullException(nameof(matcherFactory)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - if (endpointRouteBuilder == null) { throw new ArgumentNullException(nameof(endpointRouteBuilder)); } - if (next == null) - { - throw new ArgumentNullException(nameof(next)); - } - - _matcherFactory = matcherFactory; - _logger = logger; - _next = next; + _matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener)); + _next = next ?? throw new ArgumentNullException(nameof(next)); _endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources); } @@ -106,6 +97,13 @@ private Task SetRoutingAndContinue(HttpContext httpContext) } else { + // Raise an event if the route matched + if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey)) + { + // We're just going to send the HttpContext since it has all of the relevant information + _diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext); + } + Log.MatchSuccess(_logger, endpoint); } diff --git a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs index c9dbbd4032fc..7f51f6e9a7cd 100644 --- a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -311,6 +312,9 @@ private IServiceProvider CreateServices(MatcherFactory matcherFactory) services.AddLogging(); services.AddOptions(); services.AddRouting(); + var listener = new DiagnosticListener("Microsoft.AspNetCore"); + services.AddSingleton(listener); + services.AddSingleton(listener); var serviceProvder = services.BuildServiceProvider(); diff --git a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs index 946fd7755f90..48e1157195fd 100644 --- a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs +++ b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -57,16 +59,26 @@ public async Task Invoke_OnCall_WritesToConfiguredLogger() { // Arrange var expectedMessage = "Request matched endpoint 'Test endpoint'"; + bool eventFired = false; var sink = new TestSink( TestSink.EnableWithTypeName, TestSink.EnableWithTypeName); var loggerFactory = new TestLoggerFactory(sink, enabled: true); + var listener = new DiagnosticListener("TestListener"); + + using var subscription = listener.Subscribe(new DelegateObserver(pair => + { + eventFired = true; + + Assert.Equal("Microsoft.AspNetCore.Routing.EndpointMatched", pair.Key); + Assert.IsAssignableFrom(pair.Value); + })); var httpContext = CreateHttpContext(); var logger = new Logger(loggerFactory); - var middleware = CreateMiddleware(logger); + var middleware = CreateMiddleware(logger, listener: listener); // Act await middleware.Invoke(httpContext); @@ -75,6 +87,7 @@ public async Task Invoke_OnCall_WritesToConfiguredLogger() Assert.Empty(sink.Scopes); var write = Assert.Single(sink.Writes); Assert.Equal(expectedMessage, write.State?.ToString()); + Assert.True(eventFired); } [Fact] @@ -159,19 +172,46 @@ private HttpContext CreateHttpContext() private EndpointRoutingMiddleware CreateMiddleware( Logger logger = null, MatcherFactory matcherFactory = null, + DiagnosticListener listener = null, RequestDelegate next = null) { next ??= c => Task.CompletedTask; logger ??= new Logger(NullLoggerFactory.Instance); matcherFactory ??= new TestMatcherFactory(true); + listener ??= new DiagnosticListener("Microsoft.AspNetCore"); var middleware = new EndpointRoutingMiddleware( matcherFactory, logger, new DefaultEndpointRouteBuilder(Mock.Of()), + listener, next); return middleware; } + + private class DelegateObserver : IObserver> + { + private readonly Action> _onNext; + + public DelegateObserver(Action> onNext) + { + _onNext = onNext; + } + public void OnCompleted() + { + + } + + public void OnError(Exception error) + { + + } + + public void OnNext(KeyValuePair value) + { + _onNext(value); + } + } } }