diff --git a/src/Mvc/Mvc.Core/src/Infrastructure/DefaultActionDescriptorCollectionProvider.cs b/src/Mvc/Mvc.Core/src/Infrastructure/DefaultActionDescriptorCollectionProvider.cs index a2aecb14a346..28747cdad1be 100644 --- a/src/Mvc/Mvc.Core/src/Infrastructure/DefaultActionDescriptorCollectionProvider.cs +++ b/src/Mvc/Mvc.Core/src/Infrastructure/DefaultActionDescriptorCollectionProvider.cs @@ -7,14 +7,16 @@ using System.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.Infrastructure; -internal class DefaultActionDescriptorCollectionProvider : ActionDescriptorCollectionProvider +internal partial class DefaultActionDescriptorCollectionProvider : ActionDescriptorCollectionProvider { private readonly IActionDescriptorProvider[] _actionDescriptorProviders; private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; + private readonly ILogger _logger; // The lock is used to protect WRITES to the following (do not need to protect reads once initialized). private readonly object _lock; @@ -25,7 +27,8 @@ internal class DefaultActionDescriptorCollectionProvider : ActionDescriptorColle public DefaultActionDescriptorCollectionProvider( IEnumerable actionDescriptorProviders, - IEnumerable actionDescriptorChangeProviders) + IEnumerable actionDescriptorChangeProviders, + ILogger logger) { _actionDescriptorProviders = actionDescriptorProviders .OrderBy(p => p.Order) @@ -35,6 +38,8 @@ public DefaultActionDescriptorCollectionProvider( _lock = new object(); + _logger = logger; + // IMPORTANT: this needs to be the last thing we do in the constructor. Change notifications can happen immediately! ChangeToken.OnChange( GetCompositeChangeToken, @@ -124,6 +129,13 @@ private void UpdateCollection() _actionDescriptorProviders[i].OnProvidersExecuted(context); } + if (context.Results.Count == 0) + { + // Emit a log message if after all providers still no action + // descriptors detected in the context. + Log.NoActionDescriptors(_logger); + } + // The sequence for an update is important because we don't want anyone to obtain // the new change token but the old action descriptor collection. // 1. Obtain the old cancellation token source (don't trigger it yet) @@ -156,4 +168,14 @@ private void UpdateCollection() oldCancellationTokenSource?.Cancel(); } } + + public static partial class Log + { + [LoggerMessage( + EventId = 1, + EventName = "NoActionDescriptors", + Level = LogLevel.Information, + Message = "No action descriptors found. This may indicate an incorrectly configured application or missing application parts. To learn more, visit https://aka.ms/aspnet/mvc/app-parts")] + public static partial void NoActionDescriptors(ILogger logger); + } } diff --git a/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs b/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs index 8f4be55d8afa..53ecf1a827f7 100644 --- a/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs +++ b/src/Mvc/Mvc.Core/src/MvcCoreLoggerExtensions.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc; -internal static class MvcCoreLoggerExtensions +internal static partial class MvcCoreLoggerExtensions { public const string ActionFilter = "Action Filter"; private static readonly string[] _noFilters = new[] { "None" }; diff --git a/src/Mvc/Mvc.Core/test/ActionConstraints/ActionConstraintCacheTest.cs b/src/Mvc/Mvc.Core/test/ActionConstraints/ActionConstraintCacheTest.cs index f10c212b5987..3585ee1c5909 100644 --- a/src/Mvc/Mvc.Core/test/ActionConstraints/ActionConstraintCacheTest.cs +++ b/src/Mvc/Mvc.Core/test/ActionConstraints/ActionConstraintCacheTest.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Mvc.ActionConstraints; @@ -157,7 +158,8 @@ private static ActionConstraintCache CreateCache(params IActionConstraintProvide { var descriptorProvider = new DefaultActionDescriptorCollectionProvider( Enumerable.Empty(), - Enumerable.Empty()); + Enumerable.Empty(), + NullLogger.Instance); return new ActionConstraintCache(descriptorProvider, providers); } } diff --git a/src/Mvc/Mvc.Core/test/Infrastructure/ActionSelectorTest.cs b/src/Mvc/Mvc.Core/test/Infrastructure/ActionSelectorTest.cs index e93bb14b0467..6b387a408f93 100644 --- a/src/Mvc/Mvc.Core/test/Infrastructure/ActionSelectorTest.cs +++ b/src/Mvc/Mvc.Core/test/Infrastructure/ActionSelectorTest.cs @@ -975,7 +975,8 @@ private ControllerActionDescriptor InvokeActionSelector(RouteContext context) var actionDescriptorProvider = GetActionDescriptorProvider(); var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider }, - Enumerable.Empty()); + Enumerable.Empty(), + NullLogger.Instance); var actionConstraintProviders = new[] { @@ -1134,7 +1135,8 @@ private static ActionConstraintCache GetActionConstraintCache(IActionConstraintP { var descriptorProvider = new DefaultActionDescriptorCollectionProvider( Enumerable.Empty(), - Enumerable.Empty()); + Enumerable.Empty(), + NullLogger.Instance); return new ActionConstraintCache(descriptorProvider, actionConstraintProviders.AsEnumerable() ?? new List()); } diff --git a/src/Mvc/Mvc.Core/test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs b/src/Mvc/Mvc.Core/test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs index 9edec00f999e..d6bfa5c36144 100644 --- a/src/Mvc/Mvc.Core/test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs +++ b/src/Mvc/Mvc.Core/test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Primitives; using Moq; @@ -22,7 +23,8 @@ public void ActionDescriptors_ReadsDescriptorsFromActionDescriptorProviders() var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider1, actionDescriptorProvider2 }, - Enumerable.Empty()); + Enumerable.Empty(), + NullLogger.Instance); // Act var collection = actionDescriptorCollectionProvider.ActionDescriptors; @@ -44,7 +46,8 @@ public void ActionDescriptors_CachesValuesByDefault() var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider }, - Enumerable.Empty()); + Enumerable.Empty(), + NullLogger.Instance); // Act - 1 var collection1 = actionDescriptorCollectionProvider.ActionDescriptors; @@ -93,7 +96,8 @@ public void ActionDescriptors_UpdatesAndResubscribes_WhenChangeTokenTriggers() var changeProvider = new TestChangeProvider(); var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider.Object }, - new[] { changeProvider }); + new[] { changeProvider }, + NullLogger.Instance); // Act - 1 var changeToken1 = actionDescriptorCollectionProvider.GetChangeToken(); diff --git a/src/Mvc/Mvc.Core/test/Routing/ActionEndpointDataSourceBaseTest.cs b/src/Mvc/Mvc.Core/test/Routing/ActionEndpointDataSourceBaseTest.cs index 32dee5425271..8cfbca80b433 100644 --- a/src/Mvc/Mvc.Core/test/Routing/ActionEndpointDataSourceBaseTest.cs +++ b/src/Mvc/Mvc.Core/test/Routing/ActionEndpointDataSourceBaseTest.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Primitives; using Moq; @@ -129,7 +130,8 @@ private protected ActionEndpointDataSourceBase CreateDataSource(IActionDescripto { actions = new DefaultActionDescriptorCollectionProvider( Array.Empty(), - Array.Empty()); + Array.Empty(), + NullLogger.Instance); } var services = new ServiceCollection(); diff --git a/src/Mvc/Mvc.Core/test/Routing/KnownRouteValueConstraintTests.cs b/src/Mvc/Mvc.Core/test/Routing/KnownRouteValueConstraintTests.cs index 2d104a26815c..e396931414bf 100644 --- a/src/Mvc/Mvc.Core/test/Routing/KnownRouteValueConstraintTests.cs +++ b/src/Mvc/Mvc.Core/test/Routing/KnownRouteValueConstraintTests.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using Moq; namespace Microsoft.AspNetCore.Mvc.Routing; @@ -285,7 +286,8 @@ private static IActionDescriptorCollectionProvider CreateActionDescriptorCollect var descriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionProvider.Object }, - Enumerable.Empty()); + Enumerable.Empty(), + NullLogger.Instance); return descriptorCollectionProvider; } diff --git a/src/Mvc/Mvc/test/Routing/ActionConstraintMatcherPolicyTest.cs b/src/Mvc/Mvc/test/Routing/ActionConstraintMatcherPolicyTest.cs index 8159a805ee84..939ccef63cb0 100644 --- a/src/Mvc/Mvc/test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/src/Mvc/Mvc/test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.Extensions.Logging.Abstractions; using Moq; namespace Microsoft.AspNetCore.Mvc.Routing; @@ -449,7 +450,8 @@ private ActionConstraintMatcherPolicy CreateSelector(ActionDescriptor[] actions) var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new IActionDescriptorProvider[] { actionDescriptorProvider.Object, }, - Enumerable.Empty()); + Enumerable.Empty(), + NullLogger.Instance); var cache = new ActionConstraintCache(actionDescriptorCollectionProvider, new[] {