Skip to content

Commit

Permalink
Log registered IApiDescriptionProvider implementations on load (#57318)
Browse files Browse the repository at this point in the history
* Log registered IApiDescriptionProvider implementations on load

* Use internal API for new constructor
  • Loading branch information
captainsafia authored Aug 13, 2024
1 parent 15d22a9 commit 08b60af
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

using System.Linq;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;

namespace Microsoft.AspNetCore.Mvc.ApiExplorer;

/// <inheritdoc />
public class ApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider
public partial class ApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider
{
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
private readonly IApiDescriptionProvider[] _apiDescriptionProviders;

private ApiDescriptionGroupCollection? _apiDescriptionGroups;
private readonly ILogger? _logger;

/// <summary>
/// Creates a new instance of <see cref="ApiDescriptionGroupCollectionProvider"/>.
Expand All @@ -28,7 +30,15 @@ public ApiDescriptionGroupCollectionProvider(
IEnumerable<IApiDescriptionProvider> apiDescriptionProviders)
{
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
_apiDescriptionProviders = apiDescriptionProviders.OrderBy(item => item.Order).ToArray();
_apiDescriptionProviders = [.. apiDescriptionProviders.OrderBy(item => item.Order)];
}

internal ApiDescriptionGroupCollectionProvider(
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
IEnumerable<IApiDescriptionProvider> apiDescriptionProviders,
ILoggerFactory loggerFactory) : this(actionDescriptorCollectionProvider, apiDescriptionProviders)
{
_logger = loggerFactory.CreateLogger<ApiDescriptionGroupCollectionProvider>();
}

/// <inheritdoc />
Expand All @@ -52,6 +62,10 @@ private ApiDescriptionGroupCollection GetCollection(ActionDescriptorCollection a

foreach (var provider in _apiDescriptionProviders)
{
if (_logger is not null)
{
Log.ApiDescriptionProviderExecuting(_logger, provider.GetType().Name, provider.GetType().Assembly.GetName().Name, provider.GetType().Assembly.GetName().Version?.ToString());
}
provider.OnProvidersExecuting(context);
}

Expand All @@ -67,4 +81,10 @@ private ApiDescriptionGroupCollection GetCollection(ActionDescriptorCollection a

return new ApiDescriptionGroupCollection(groups, actionDescriptors.Version);
}

private static partial class Log
{
[LoggerMessage(2, LogLevel.Debug, "Executing API description provider '{ProviderName}' from assembly {ProviderAssembly} v{AssemblyVersion}.", EventName = "ApiDescriptionProviderExecuting")]
public static partial void ApiDescriptionProviderExecuting(ILogger logger, string providerName, string? providerAssembly, string? assemblyVersion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;

namespace Microsoft.Extensions.DependencyInjection;

Expand All @@ -21,7 +22,10 @@ public static IServiceCollection AddEndpointsApiExplorer(this IServiceCollection
{
// Try to add default services in case MVC services aren't added.
services.TryAddSingleton<IActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider>();
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider>(sp => new ApiDescriptionGroupCollectionProvider(
sp.GetRequiredService<IActionDescriptorCollectionProvider>(),
sp.GetServices<IApiDescriptionProvider>(),
sp.GetRequiredService<ILoggerFactory>()));

services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, EndpointMetadataApiDescriptionProvider>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;

namespace Microsoft.Extensions.DependencyInjection;

Expand All @@ -30,7 +32,10 @@ public static IMvcCoreBuilder AddApiExplorer(this IMvcCoreBuilder builder)
[RequiresUnreferencedCode("MVC does not currently support trimming or native AOT.", Url = "https://aka.ms/aspnet/trimming")]
internal static void AddApiExplorerServices(IServiceCollection services)
{
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider>(sp => new ApiDescriptionGroupCollectionProvider(
sp.GetRequiredService<IActionDescriptorCollectionProvider>(),
sp.GetServices<IApiDescriptionProvider>(),
sp.GetRequiredService<ILoggerFactory>()));
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>());
}
Expand Down
13 changes: 13 additions & 0 deletions src/Mvc/test/Mvc.FunctionalTests/ApiExplorerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,19 @@ public async Task ApiAction_ForActionWithVoidResponseType(string path, Type type
});
}

[Fact]
public async Task ApiExplorer_LogsInvokedDescriptionProvidersOnStartup()
{
// Arrange & Act
var response = await Client.GetAsync("http://localhost/ApiExplorerHttpMethod/All");

// Assert
Assert.Contains(TestSink.Writes, w => w.EventId.Name?.Equals("ApiDescriptionProviderExecuting", StringComparison.Ordinal) == true);
Assert.Contains(TestSink.Writes, w => w.LoggerName.Equals("Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollectionProvider", StringComparison.Ordinal));
Assert.Contains(TestSink.Writes, w => w.Message.Equals("Executing API description provider 'DefaultApiDescriptionProvider' from assembly Microsoft.AspNetCore.Mvc.ApiExplorer v9.0.0.0.", StringComparison.Ordinal));
Assert.Contains(TestSink.Writes, w => w.Message.Equals("Executing API description provider 'JsonPatchOperationsArrayProvider' from assembly Microsoft.AspNetCore.Mvc.NewtonsoftJson v42.42.42.42.", StringComparison.Ordinal));
}

private IEnumerable<string> GetSortedMediaTypes(ApiExplorerResponseType apiResponseType)
{
return apiResponseType.ResponseFormats
Expand Down

0 comments on commit 08b60af

Please sign in to comment.