diff --git a/src/JsonApiDotNetCore/Configuration/InjectablesAssemblyScanner.cs b/src/JsonApiDotNetCore/Configuration/InjectablesAssemblyScanner.cs
new file mode 100644
index 0000000000..9985533776
--- /dev/null
+++ b/src/JsonApiDotNetCore/Configuration/InjectablesAssemblyScanner.cs
@@ -0,0 +1,129 @@
+using System.Reflection;
+using JsonApiDotNetCore.Repositories;
+using JsonApiDotNetCore.Resources;
+using JsonApiDotNetCore.Services;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace JsonApiDotNetCore.Configuration;
+
+///
+/// Scans assemblies for injectables (types that implement ,
+/// or ) and registers them in the IoC container.
+///
+internal sealed class InjectablesAssemblyScanner
+{
+ internal static readonly HashSet ServiceUnboundInterfaces =
+ [
+ typeof(IResourceService<,>),
+ typeof(IResourceCommandService<,>),
+ typeof(IResourceQueryService<,>),
+ typeof(IGetAllService<,>),
+ typeof(IGetByIdService<,>),
+ typeof(IGetSecondaryService<,>),
+ typeof(IGetRelationshipService<,>),
+ typeof(ICreateService<,>),
+ typeof(IAddToRelationshipService<,>),
+ typeof(IUpdateService<,>),
+ typeof(ISetRelationshipService<,>),
+ typeof(IDeleteService<,>),
+ typeof(IRemoveFromRelationshipService<,>)
+ ];
+
+ internal static readonly HashSet RepositoryUnboundInterfaces =
+ [
+ typeof(IResourceRepository<,>),
+ typeof(IResourceWriteRepository<,>),
+ typeof(IResourceReadRepository<,>)
+ ];
+
+ internal static readonly HashSet ResourceDefinitionUnboundInterfaces = [typeof(IResourceDefinition<,>)];
+
+ private readonly ResourceDescriptorAssemblyCache _assemblyCache;
+ private readonly IServiceCollection _services;
+ private readonly TypeLocator _typeLocator = new();
+
+ public InjectablesAssemblyScanner(ResourceDescriptorAssemblyCache assemblyCache, IServiceCollection services)
+ {
+ ArgumentGuard.NotNull(assemblyCache);
+ ArgumentGuard.NotNull(services);
+
+ _assemblyCache = assemblyCache;
+ _services = services;
+ }
+
+ public void DiscoverInjectables()
+ {
+ IReadOnlyCollection descriptors = _assemblyCache.GetResourceDescriptors();
+ IReadOnlyCollection assemblies = _assemblyCache.GetAssemblies();
+
+ foreach (Assembly assembly in assemblies)
+ {
+ AddDbContextResolvers(assembly);
+ AddInjectables(descriptors, assembly);
+ }
+ }
+
+ private void AddDbContextResolvers(Assembly assembly)
+ {
+ IEnumerable dbContextTypes = _typeLocator.GetDerivedTypes(assembly, typeof(DbContext));
+
+ foreach (Type dbContextType in dbContextTypes)
+ {
+ Type dbContextResolverClosedType = typeof(DbContextResolver<>).MakeGenericType(dbContextType);
+ _services.TryAddScoped(typeof(IDbContextResolver), dbContextResolverClosedType);
+ }
+ }
+
+ private void AddInjectables(IEnumerable resourceDescriptors, Assembly assembly)
+ {
+ foreach (ResourceDescriptor resourceDescriptor in resourceDescriptors)
+ {
+ AddServices(assembly, resourceDescriptor);
+ AddRepositories(assembly, resourceDescriptor);
+ AddResourceDefinitions(assembly, resourceDescriptor);
+ }
+ }
+
+ private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor)
+ {
+ foreach (Type serviceUnboundInterface in ServiceUnboundInterfaces)
+ {
+ RegisterImplementations(assembly, serviceUnboundInterface, resourceDescriptor);
+ }
+ }
+
+ private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor)
+ {
+ foreach (Type repositoryUnboundInterface in RepositoryUnboundInterfaces)
+ {
+ RegisterImplementations(assembly, repositoryUnboundInterface, resourceDescriptor);
+ }
+ }
+
+ private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resourceDescriptor)
+ {
+ foreach (Type resourceDefinitionUnboundInterface in ResourceDefinitionUnboundInterfaces)
+ {
+ RegisterImplementations(assembly, resourceDefinitionUnboundInterface, resourceDescriptor);
+ }
+ }
+
+ private void RegisterImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
+ {
+ Type[] typeArguments =
+ [
+ resourceDescriptor.ResourceClrType,
+ resourceDescriptor.IdClrType
+ ];
+
+ (Type implementationType, Type serviceInterface)? result = _typeLocator.GetContainerRegistrationFromAssembly(assembly, interfaceType, typeArguments);
+
+ if (result != null)
+ {
+ (Type implementationType, Type serviceInterface) = result.Value;
+ _services.TryAddScoped(serviceInterface, implementationType);
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
index 17ca6677c3..2004178ccd 100644
--- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
+++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs
@@ -23,17 +23,15 @@
namespace JsonApiDotNetCore.Configuration;
///
-/// A utility class that builds a JsonApi application. It registers all required services and allows the user to override parts of the startup
+/// A utility class that builds a JSON:API application. It registers all required services and allows the user to override parts of the startup
/// configuration.
///
-internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, IDisposable
+internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder
{
- private readonly JsonApiOptions _options = new();
private readonly IServiceCollection _services;
private readonly IMvcCoreBuilder _mvcBuilder;
- private readonly ResourceGraphBuilder _resourceGraphBuilder;
- private readonly ServiceDiscoveryFacade _serviceDiscoveryFacade;
- private readonly ServiceProvider _intermediateProvider;
+ private readonly JsonApiOptions _options = new();
+ private readonly ResourceDescriptorAssemblyCache _assemblyCache = new();
public Action? ConfigureMvcOptions { get; set; }
@@ -44,12 +42,6 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv
_services = services;
_mvcBuilder = mvcBuilder;
- _intermediateProvider = services.BuildServiceProvider();
-
- var loggerFactory = _intermediateProvider.GetRequiredService();
-
- _resourceGraphBuilder = new ResourceGraphBuilder(_options, loggerFactory);
- _serviceDiscoveryFacade = new ServiceDiscoveryFacade(_services, _resourceGraphBuilder, loggerFactory);
}
///
@@ -61,35 +53,51 @@ public void ConfigureJsonApiOptions(Action? configureOptions)
}
///
- /// Executes the action provided by the user to configure .
+ /// Executes the action provided by the user to configure auto-discovery.
///
public void ConfigureAutoDiscovery(Action? configureAutoDiscovery)
{
- configureAutoDiscovery?.Invoke(_serviceDiscoveryFacade);
+ if (configureAutoDiscovery != null)
+ {
+ var facade = new ServiceDiscoveryFacade(_assemblyCache);
+ configureAutoDiscovery.Invoke(facade);
+ }
}
///
- /// Configures and builds the resource graph with resources from the provided sources and adds it to the DI container.
+ /// Configures and builds the resource graph with resources from the provided sources and adds them to the IoC container.
///
public void ConfigureResourceGraph(ICollection dbContextTypes, Action? configureResourceGraph)
{
ArgumentGuard.NotNull(dbContextTypes);
- _serviceDiscoveryFacade.DiscoverResources();
-
- foreach (Type dbContextType in dbContextTypes)
+ _services.TryAddSingleton(serviceProvider =>
{
- var dbContext = (DbContext)_intermediateProvider.GetRequiredService(dbContextType);
- _resourceGraphBuilder.Add(dbContext);
- }
+ var loggerFactory = serviceProvider.GetRequiredService();
+ var resourceGraphBuilder = new ResourceGraphBuilder(_options, loggerFactory);
- configureResourceGraph?.Invoke(_resourceGraphBuilder);
+ var scanner = new ResourcesAssemblyScanner(_assemblyCache, resourceGraphBuilder);
+ scanner.DiscoverResources();
- IResourceGraph resourceGraph = _resourceGraphBuilder.Build();
+ if (dbContextTypes.Count > 0)
+ {
+ using IServiceScope scope = serviceProvider.CreateScope();
- _options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
+ foreach (Type dbContextType in dbContextTypes)
+ {
+ var dbContext = (DbContext)scope.ServiceProvider.GetRequiredService(dbContextType);
+ resourceGraphBuilder.Add(dbContext);
+ }
+ }
+
+ configureResourceGraph?.Invoke(resourceGraphBuilder);
+
+ IResourceGraph resourceGraph = resourceGraphBuilder.Build();
+
+ _options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
- _services.TryAddSingleton(resourceGraph);
+ return resourceGraph;
+ });
}
///
@@ -114,15 +122,16 @@ public void ConfigureMvc()
}
///
- /// Discovers DI registrable services in the assemblies marked for discovery.
+ /// Registers injectables in the IoC container found in assemblies marked for auto-discovery.
///
public void DiscoverInjectables()
{
- _serviceDiscoveryFacade.DiscoverInjectables();
+ var scanner = new InjectablesAssemblyScanner(_assemblyCache, _services);
+ scanner.DiscoverInjectables();
}
///
- /// Registers the remaining internals.
+ /// Registers the remaining internals in the IoC container.
///
public void ConfigureServiceContainer(ICollection dbContextTypes)
{
@@ -182,7 +191,7 @@ private void AddMiddlewareLayer()
private void AddResourceLayer()
{
- RegisterImplementationForInterfaces(ServiceDiscoveryFacade.ResourceDefinitionUnboundInterfaces, typeof(JsonApiResourceDefinition<,>));
+ RegisterImplementationForInterfaces(InjectablesAssemblyScanner.ResourceDefinitionUnboundInterfaces, typeof(JsonApiResourceDefinition<,>));
_services.TryAddScoped();
_services.TryAddScoped();
@@ -190,7 +199,7 @@ private void AddResourceLayer()
private void AddRepositoryLayer()
{
- RegisterImplementationForInterfaces(ServiceDiscoveryFacade.RepositoryUnboundInterfaces, typeof(EntityFrameworkCoreRepository<,>));
+ RegisterImplementationForInterfaces(InjectablesAssemblyScanner.RepositoryUnboundInterfaces, typeof(EntityFrameworkCoreRepository<,>));
_services.TryAddScoped();
@@ -204,7 +213,7 @@ private void AddRepositoryLayer()
private void AddServiceLayer()
{
- RegisterImplementationForInterfaces(ServiceDiscoveryFacade.ServiceUnboundInterfaces, typeof(JsonApiResourceService<,>));
+ RegisterImplementationForInterfaces(InjectablesAssemblyScanner.ServiceUnboundInterfaces, typeof(JsonApiResourceService<,>));
}
private void RegisterImplementationForInterfaces(HashSet unboundInterfaces, Type unboundImplementationType)
@@ -291,9 +300,4 @@ private void AddOperationsLayer()
_services.TryAddScoped();
_services.TryAddScoped();
}
-
- public void Dispose()
- {
- _intermediateProvider.Dispose();
- }
}
diff --git a/src/JsonApiDotNetCore/Configuration/ResourcesAssemblyScanner.cs b/src/JsonApiDotNetCore/Configuration/ResourcesAssemblyScanner.cs
new file mode 100644
index 0000000000..cdfdc4446c
--- /dev/null
+++ b/src/JsonApiDotNetCore/Configuration/ResourcesAssemblyScanner.cs
@@ -0,0 +1,29 @@
+using JsonApiDotNetCore.Resources;
+
+namespace JsonApiDotNetCore.Configuration;
+
+///
+/// Scans assemblies for types that implement and adds them to the resource graph.
+///
+internal sealed class ResourcesAssemblyScanner
+{
+ private readonly ResourceDescriptorAssemblyCache _assemblyCache;
+ private readonly ResourceGraphBuilder _resourceGraphBuilder;
+
+ public ResourcesAssemblyScanner(ResourceDescriptorAssemblyCache assemblyCache, ResourceGraphBuilder resourceGraphBuilder)
+ {
+ ArgumentGuard.NotNull(assemblyCache);
+ ArgumentGuard.NotNull(resourceGraphBuilder);
+
+ _assemblyCache = assemblyCache;
+ _resourceGraphBuilder = resourceGraphBuilder;
+ }
+
+ public void DiscoverResources()
+ {
+ foreach (ResourceDescriptor resourceDescriptor in _assemblyCache.GetResourceDescriptors())
+ {
+ _resourceGraphBuilder.Add(resourceDescriptor.ResourceClrType, resourceDescriptor.IdClrType);
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
index b25c208086..5ad4474d95 100644
--- a/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/Configuration/ServiceCollectionExtensions.cs
@@ -43,7 +43,7 @@ private static void SetupApplicationBuilder(IServiceCollection services, Action<
Action? configureAutoDiscovery, Action? configureResources, IMvcCoreBuilder? mvcBuilder,
ICollection dbContextTypes)
{
- using var applicationBuilder = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
+ var applicationBuilder = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
applicationBuilder.ConfigureJsonApiOptions(configureOptions);
applicationBuilder.ConfigureAutoDiscovery(configureAutoDiscovery);
@@ -61,7 +61,7 @@ public static IServiceCollection AddResourceService(this IServiceColle
{
ArgumentGuard.NotNull(services);
- RegisterTypeForUnboundInterfaces(services, typeof(TService), ServiceDiscoveryFacade.ServiceUnboundInterfaces);
+ RegisterTypeForUnboundInterfaces(services, typeof(TService), InjectablesAssemblyScanner.ServiceUnboundInterfaces);
return services;
}
@@ -74,7 +74,7 @@ public static IServiceCollection AddResourceRepository(this IServic
{
ArgumentGuard.NotNull(services);
- RegisterTypeForUnboundInterfaces(services, typeof(TRepository), ServiceDiscoveryFacade.RepositoryUnboundInterfaces);
+ RegisterTypeForUnboundInterfaces(services, typeof(TRepository), InjectablesAssemblyScanner.RepositoryUnboundInterfaces);
return services;
}
@@ -87,7 +87,7 @@ public static IServiceCollection AddResourceDefinition(this
{
ArgumentGuard.NotNull(services);
- RegisterTypeForUnboundInterfaces(services, typeof(TResourceDefinition), ServiceDiscoveryFacade.ResourceDefinitionUnboundInterfaces);
+ RegisterTypeForUnboundInterfaces(services, typeof(TResourceDefinition), InjectablesAssemblyScanner.ResourceDefinitionUnboundInterfaces);
return services;
}
diff --git a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
index 01f9ecc7cb..3139930852 100644
--- a/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
+++ b/src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs
@@ -1,66 +1,23 @@
using System.Reflection;
-using JetBrains.Annotations;
-using JsonApiDotNetCore.Repositories;
-using JsonApiDotNetCore.Resources;
-using JsonApiDotNetCore.Services;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Logging;
namespace JsonApiDotNetCore.Configuration;
///
-/// Scans for types like resources, services, repositories and resource definitions in an assembly and registers them to the IoC container.
+/// Provides auto-discovery by scanning assemblies for resources and related injectables.
///
-[PublicAPI]
public sealed class ServiceDiscoveryFacade
{
- internal static readonly HashSet ServiceUnboundInterfaces =
- [
- typeof(IResourceService<,>),
- typeof(IResourceCommandService<,>),
- typeof(IResourceQueryService<,>),
- typeof(IGetAllService<,>),
- typeof(IGetByIdService<,>),
- typeof(IGetSecondaryService<,>),
- typeof(IGetRelationshipService<,>),
- typeof(ICreateService<,>),
- typeof(IAddToRelationshipService<,>),
- typeof(IUpdateService<,>),
- typeof(ISetRelationshipService<,>),
- typeof(IDeleteService<,>),
- typeof(IRemoveFromRelationshipService<,>)
- ];
+ private readonly ResourceDescriptorAssemblyCache _assemblyCache;
- internal static readonly HashSet RepositoryUnboundInterfaces =
- [
- typeof(IResourceRepository<,>),
- typeof(IResourceWriteRepository<,>),
- typeof(IResourceReadRepository<,>)
- ];
-
- internal static readonly HashSet ResourceDefinitionUnboundInterfaces = [typeof(IResourceDefinition<,>)];
-
- private readonly ILogger _logger;
- private readonly IServiceCollection _services;
- private readonly ResourceGraphBuilder _resourceGraphBuilder;
- private readonly ResourceDescriptorAssemblyCache _assemblyCache = new();
- private readonly TypeLocator _typeLocator = new();
-
- public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, ILoggerFactory loggerFactory)
+ internal ServiceDiscoveryFacade(ResourceDescriptorAssemblyCache assemblyCache)
{
- ArgumentGuard.NotNull(services);
- ArgumentGuard.NotNull(resourceGraphBuilder);
- ArgumentGuard.NotNull(loggerFactory);
+ ArgumentGuard.NotNull(assemblyCache);
- _logger = loggerFactory.CreateLogger();
- _services = services;
- _resourceGraphBuilder = resourceGraphBuilder;
+ _assemblyCache = assemblyCache;
}
///
- /// Mark the calling assembly for scanning of resources and injectables.
+ /// Includes the calling assembly for auto-discovery of resources and related injectables.
///
public ServiceDiscoveryFacade AddCurrentAssembly()
{
@@ -68,102 +25,13 @@ public ServiceDiscoveryFacade AddCurrentAssembly()
}
///
- /// Mark the specified assembly for scanning of resources and injectables.
+ /// Includes the specified assembly for auto-discovery of resources and related injectables.
///
public ServiceDiscoveryFacade AddAssembly(Assembly assembly)
{
ArgumentGuard.NotNull(assembly);
_assemblyCache.RegisterAssembly(assembly);
- _logger.LogDebug($"Registering assembly '{assembly.FullName}' for discovery of resources and injectables.");
-
return this;
}
-
- internal void DiscoverResources()
- {
- foreach (ResourceDescriptor resourceDescriptor in _assemblyCache.GetResourceDescriptors())
- {
- AddResource(resourceDescriptor);
- }
- }
-
- internal void DiscoverInjectables()
- {
- IReadOnlyCollection descriptors = _assemblyCache.GetResourceDescriptors();
- IReadOnlyCollection assemblies = _assemblyCache.GetAssemblies();
-
- foreach (Assembly assembly in assemblies)
- {
- AddDbContextResolvers(assembly);
- AddInjectables(descriptors, assembly);
- }
- }
-
- private void AddInjectables(IReadOnlyCollection resourceDescriptors, Assembly assembly)
- {
- foreach (ResourceDescriptor resourceDescriptor in resourceDescriptors)
- {
- AddServices(assembly, resourceDescriptor);
- AddRepositories(assembly, resourceDescriptor);
- AddResourceDefinitions(assembly, resourceDescriptor);
- }
- }
-
- private void AddDbContextResolvers(Assembly assembly)
- {
- IEnumerable dbContextTypes = _typeLocator.GetDerivedTypes(assembly, typeof(DbContext));
-
- foreach (Type dbContextType in dbContextTypes)
- {
- Type dbContextResolverClosedType = typeof(DbContextResolver<>).MakeGenericType(dbContextType);
- _services.TryAddScoped(typeof(IDbContextResolver), dbContextResolverClosedType);
- }
- }
-
- private void AddResource(ResourceDescriptor resourceDescriptor)
- {
- _resourceGraphBuilder.Add(resourceDescriptor.ResourceClrType, resourceDescriptor.IdClrType);
- }
-
- private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor)
- {
- foreach (Type serviceUnboundInterface in ServiceUnboundInterfaces)
- {
- RegisterImplementations(assembly, serviceUnboundInterface, resourceDescriptor);
- }
- }
-
- private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor)
- {
- foreach (Type repositoryUnboundInterface in RepositoryUnboundInterfaces)
- {
- RegisterImplementations(assembly, repositoryUnboundInterface, resourceDescriptor);
- }
- }
-
- private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resourceDescriptor)
- {
- foreach (Type resourceDefinitionUnboundInterface in ResourceDefinitionUnboundInterfaces)
- {
- RegisterImplementations(assembly, resourceDefinitionUnboundInterface, resourceDescriptor);
- }
- }
-
- private void RegisterImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
- {
- Type[] typeArguments =
- [
- resourceDescriptor.ResourceClrType,
- resourceDescriptor.IdClrType
- ];
-
- (Type implementationType, Type serviceInterface)? result = _typeLocator.GetContainerRegistrationFromAssembly(assembly, interfaceType, typeArguments);
-
- if (result != null)
- {
- (Type implementationType, Type serviceInterface) = result.Value;
- _services.TryAddScoped(serviceInterface, implementationType);
- }
- }
}