diff --git a/Build.ps1 b/Build.ps1 index 7cdc82e3d9..33cc65f4d9 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -66,4 +66,4 @@ Else { Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision" dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision --include-symbols CheckLastExitCode -} +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Graph/IdentifiableTypeCache.cs b/src/JsonApiDotNetCore/Graph/IdentifiableTypeCache.cs new file mode 100644 index 0000000000..cc45386e9e --- /dev/null +++ b/src/JsonApiDotNetCore/Graph/IdentifiableTypeCache.cs @@ -0,0 +1,35 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Graph +{ + /// + /// Used to cache and locate types, to facilitate auto-resource discovery + /// + internal sealed class IdentifiableTypeCache + { + private readonly ConcurrentDictionary> _typeCache = new ConcurrentDictionary>(); + + /// + /// Get all implementations of in the assembly + /// + public IEnumerable GetIdentifiableTypes(Assembly assembly) + { + return _typeCache.GetOrAdd(assembly, asm => FindIdentifiableTypes(asm).ToList()); + } + + private static IEnumerable FindIdentifiableTypes(Assembly assembly) + { + foreach (var type in assembly.GetTypes()) + { + if (TypeLocator.TryGetResourceDescriptor(type, out var descriptor)) + { + yield return descriptor; + } + } + } + } +} diff --git a/src/JsonApiDotNetCore/Graph/ResourceDescriptor.cs b/src/JsonApiDotNetCore/Graph/ResourceDescriptor.cs index 7fd97d3059..aac9824c56 100644 --- a/src/JsonApiDotNetCore/Graph/ResourceDescriptor.cs +++ b/src/JsonApiDotNetCore/Graph/ResourceDescriptor.cs @@ -13,6 +13,6 @@ public ResourceDescriptor(Type resourceType, Type idType) public Type ResourceType { get; } public Type IdType { get; } - internal static ResourceDescriptor Empty => new ResourceDescriptor(null, null); + internal static ResourceDescriptor Empty { get; } = new ResourceDescriptor(null, null); } } diff --git a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs index a9a4e022d6..0a2e34ab6c 100644 --- a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs @@ -47,6 +47,7 @@ public class ServiceDiscoveryFacade : IServiceDiscoveryFacade }; private readonly IServiceCollection _services; private readonly IResourceGraphBuilder _resourceGraphBuilder; + private readonly IdentifiableTypeCache _typeCache = new IdentifiableTypeCache(); public ServiceDiscoveryFacade(IServiceCollection services, IResourceGraphBuilder resourceGraphBuilder) { @@ -67,7 +68,7 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly) { AddDbContextResolvers(assembly); - var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly); + var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly); foreach (var resourceDescriptor in resourceDescriptors) { AddResource(assembly, resourceDescriptor); @@ -77,7 +78,6 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly) return this; } - public IEnumerable FindDerivedTypes(Type baseType) { return baseType.Assembly.GetTypes().Where(t => @@ -108,9 +108,9 @@ private void AddDbContextResolvers(Assembly assembly) /// The assembly to search for resources in. public ServiceDiscoveryFacade AddResources(Assembly assembly) { - var identifiables = TypeLocator.GetIdentifiableTypes(assembly); - foreach (var identifiable in identifiables) - AddResource(assembly, identifiable); + var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly); + foreach (var resourceDescriptor in resourceDescriptors) + AddResource(assembly, resourceDescriptor); return this; } @@ -148,7 +148,7 @@ private void AddResourceToGraph(ResourceDescriptor identifiable) /// The assembly to search for resources in. public ServiceDiscoveryFacade AddServices(Assembly assembly) { - var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly); + var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly); foreach (var resourceDescriptor in resourceDescriptors) { AddServices(assembly, resourceDescriptor); @@ -170,7 +170,7 @@ private void AddServices(Assembly assembly, ResourceDescriptor resourceDescripto /// The assembly to search for resources in. public ServiceDiscoveryFacade AddRepositories(Assembly assembly) { - var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly); + var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly); foreach (var resourceDescriptor in resourceDescriptors) { AddRepositories(assembly, resourceDescriptor); @@ -186,7 +186,7 @@ private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescr RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); } } - + private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) { if (resourceDescriptor.IdType == typeof(Guid) && interfaceType.GetTypeInfo().GenericTypeParameters.Length == 1) diff --git a/src/JsonApiDotNetCore/Graph/TypeLocator.cs b/src/JsonApiDotNetCore/Graph/TypeLocator.cs index 9fd4f5a458..a7444db227 100644 --- a/src/JsonApiDotNetCore/Graph/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Graph/TypeLocator.cs @@ -1,4 +1,4 @@ -using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Models; using System; using System.Collections.Generic; @@ -12,8 +12,6 @@ namespace JsonApiDotNetCore.Graph /// internal static class TypeLocator { - private static readonly Dictionary> _identifiableTypeCache = new Dictionary>(); - /// /// Determine whether or not this is a json:api resource by checking if it implements . /// Returns the status and the resultant id type, either `(true, Type)` OR `(false, null)` @@ -24,29 +22,6 @@ public static Type GetIdType(Type resourceType) return identifiableInterface?.GetGenericArguments()[0]; } - /// - /// Get all implementations of in the assembly - /// - public static IEnumerable GetIdentifiableTypes(Assembly assembly) - => (_identifiableTypeCache.TryGetValue(assembly, out _) == false) - ? FindIdentifiableTypes(assembly) - : _identifiableTypeCache[assembly]; - - private static IEnumerable FindIdentifiableTypes(Assembly assembly) - { - var descriptors = new List(); - _identifiableTypeCache[assembly] = descriptors; - - foreach (var type in assembly.GetTypes()) - { - if (TryGetResourceDescriptor(type, out var descriptor)) - { - descriptors.Add(descriptor); - yield return descriptor; - } - } - } - /// /// Attempts to get a descriptor of the resource type. /// diff --git a/test/UnitTests/Graph/IdentifiableTypeCacheTests.cs b/test/UnitTests/Graph/IdentifiableTypeCacheTests.cs new file mode 100644 index 0000000000..a0b4220f75 --- /dev/null +++ b/test/UnitTests/Graph/IdentifiableTypeCacheTests.cs @@ -0,0 +1,39 @@ +using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Models; +using UnitTests.Internal; +using Xunit; + +namespace UnitTests.Graph +{ + public class IdentifiableTypeCacheTests + { + [Fact] + public void GetIdentifiableTypes_Locates_Identifiable_Resource() + { + // Arrange + var resourceType = typeof(Model); + var typeCache = new IdentifiableTypeCache(); + + // Act + var results = typeCache.GetIdentifiableTypes(resourceType.Assembly); + + // Assert + Assert.Contains(results, r => r.ResourceType == resourceType); + } + + [Fact] + public void GetIdentifiableTypes_Only_Contains_IIdentifiable_Types() + { + // Arrange + var resourceType = typeof(Model); + var typeCache = new IdentifiableTypeCache(); + + // Act + var resourceDescriptors = typeCache.GetIdentifiableTypes(resourceType.Assembly); + + // Assert + foreach(var resourceDescriptor in resourceDescriptors) + Assert.True(typeof(IIdentifiable).IsAssignableFrom(resourceDescriptor.ResourceType)); + } + } +} diff --git a/test/UnitTests/Graph/TypeLocator_Tests.cs b/test/UnitTests/Graph/TypeLocator_Tests.cs index dfd4e723c4..e15b037c2b 100644 --- a/test/UnitTests/Graph/TypeLocator_Tests.cs +++ b/test/UnitTests/Graph/TypeLocator_Tests.cs @@ -81,33 +81,6 @@ public void GetIdType_Correctly_Identifies_NonJsonApiResource() Assert.Equal(expectedIdType, idType); } - [Fact] - public void GetIdentifiableTypes_Locates_Identifiable_Resource() - { - // Arrange - var resourceType = typeof(Model); - - // Act - var results = TypeLocator.GetIdentifiableTypes(resourceType.Assembly); - - // Assert - Assert.Contains(results, r => r.ResourceType == resourceType); - } - - [Fact] - public void GetIdentifiableTypes_Only_Contains_IIdentifiable_Types() - { - // Arrange - var resourceType = typeof(Model); - - // Act - var resourceDescriptors = TypeLocator.GetIdentifiableTypes(resourceType.Assembly); - - // Assert - foreach(var resourceDescriptor in resourceDescriptors) - Assert.True(typeof(IIdentifiable).IsAssignableFrom(resourceDescriptor.ResourceType)); - } - [Fact] public void TryGetResourceDescriptor_Returns_True_If_Type_Is_IIdentifiable() {