Skip to content

Commit 36d7635

Browse files
authored
Hooks discovery detection of implemented hooks issue (#617)
* fix: discovery issue related to assemblies * fix: LoaDatabaseValue typo * fix: set EnableResourceHooks to true by default * chore: add comment in code in hooksdiscovery * fix: use IServiceProvider instead of IScopedServiceProvider * fix: discovery unit test with ServiceProvider instead of ScopedServiceProvider * refactor: TResource instead of TEntity * chore: process PR review * chore: move comment to separate line (stylecop)
1 parent 3707110 commit 36d7635

File tree

6 files changed

+61
-82
lines changed

6 files changed

+61
-82
lines changed

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ public class JsonApiOptions : IJsonApiOptions
4242
/// <summary>
4343
/// Whether or not ResourceHooks are enabled.
4444
///
45-
/// Default is set to <see langword="false"/> for backward compatibility.
45+
/// Default is set to <see langword="true"/>
4646
/// </summary>
47-
public bool EnableResourceHooks { get; set; } = false;
47+
public bool EnableResourceHooks { get; set; } = true;
4848

4949
/// <summary>
5050
/// Whether or not database values should be included by default
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using JsonApiDotNetCore.Graph;
54
using JsonApiDotNetCore.Internal;
65
using JsonApiDotNetCore.Models;
6+
using Microsoft.Extensions.DependencyInjection;
77

88
namespace JsonApiDotNetCore.Hooks
99
{
@@ -12,78 +12,74 @@ namespace JsonApiDotNetCore.Hooks
1212
/// </summary>
1313
public class HooksDiscovery<TResource> : IHooksDiscovery<TResource> where TResource : class, IIdentifiable
1414
{
15+
private readonly Type _boundResourceDefinitionType = typeof(ResourceDefinition<TResource>);
1516
private readonly ResourceHook[] _allHooks;
1617
private readonly ResourceHook[] _databaseValuesAttributeAllowed =
1718
{
1819
ResourceHook.BeforeUpdate,
1920
ResourceHook.BeforeUpdateRelationship,
2021
ResourceHook.BeforeDelete
2122
};
23+
2224
/// <inheritdoc/>
2325
public ResourceHook[] ImplementedHooks { get; private set; }
2426
public ResourceHook[] DatabaseValuesEnabledHooks { get; private set; }
2527
public ResourceHook[] DatabaseValuesDisabledHooks { get; private set; }
2628

27-
28-
public HooksDiscovery()
29+
public HooksDiscovery(IServiceProvider provider)
2930
{
3031
_allHooks = Enum.GetValues(typeof(ResourceHook))
3132
.Cast<ResourceHook>()
3233
.Where(h => h != ResourceHook.None)
3334
.ToArray();
34-
DiscoverImplementedHooksForModel();
35+
36+
Type containerType;
37+
using (var scope = provider.CreateScope())
38+
{
39+
containerType = scope.ServiceProvider.GetService(_boundResourceDefinitionType)?.GetType();
40+
}
41+
42+
DiscoverImplementedHooks(containerType);
3543
}
3644

3745
/// <summary>
3846
/// Discovers the implemented hooks for a model.
3947
/// </summary>
4048
/// <returns>The implemented hooks for model.</returns>
41-
void DiscoverImplementedHooksForModel()
49+
void DiscoverImplementedHooks(Type containerType)
4250
{
43-
Type parameterizedResourceDefinition = typeof(ResourceDefinition<TResource>);
44-
var derivedTypes = TypeLocator.GetDerivedTypes(typeof(TResource).Assembly, parameterizedResourceDefinition).ToList();
45-
46-
47-
var implementedHooks = new List<ResourceHook>();
48-
var enabledHooks = new List<ResourceHook>() { ResourceHook.BeforeImplicitUpdateRelationship } ;
49-
var disabledHooks = new List<ResourceHook>();
50-
Type targetType = null;
51-
try
51+
if (containerType == null || containerType == _boundResourceDefinitionType)
5252
{
53-
targetType = derivedTypes.SingleOrDefault(); // multiple containers is not supported
53+
return;
5454
}
55-
catch
56-
{
57-
throw new JsonApiSetupException($"It is currently not supported to" +
58-
"implement hooks across multiple implementations of ResourceDefinition<T>");
59-
}
60-
if (targetType != null)
55+
56+
var implementedHooks = new List<ResourceHook>();
57+
// this hook can only be used with enabled database values
58+
var databaseValuesEnabledHooks = new List<ResourceHook> { ResourceHook.BeforeImplicitUpdateRelationship };
59+
var databaseValuesDisabledHooks = new List<ResourceHook>();
60+
foreach (var hook in _allHooks)
6161
{
62-
foreach (var hook in _allHooks)
62+
var method = containerType.GetMethod(hook.ToString("G"));
63+
if (method.DeclaringType == _boundResourceDefinitionType)
64+
continue;
65+
66+
implementedHooks.Add(hook);
67+
var attr = method.GetCustomAttributes(true).OfType<LoadDatabaseValues>().SingleOrDefault();
68+
if (attr != null)
6369
{
64-
var method = targetType.GetMethod(hook.ToString("G"));
65-
if (method.DeclaringType != parameterizedResourceDefinition)
70+
if (!_databaseValuesAttributeAllowed.Contains(hook))
6671
{
67-
implementedHooks.Add(hook);
68-
var attr = method.GetCustomAttributes(true).OfType<LoaDatabaseValues>().SingleOrDefault();
69-
if (attr != null)
70-
{
71-
if (!_databaseValuesAttributeAllowed.Contains(hook))
72-
{
73-
throw new JsonApiSetupException($"DatabaseValuesAttribute cannot be used on hook" +
74-
$"{hook.ToString("G")} in resource definition {parameterizedResourceDefinition.Name}");
75-
}
76-
var targetList = attr.value ? enabledHooks : disabledHooks;
77-
targetList.Add(hook);
78-
}
79-
}
72+
throw new JsonApiSetupException($"DatabaseValuesAttribute cannot be used on hook" +
73+
$"{hook.ToString("G")} in resource definition {containerType.Name}");
74+
}
75+
var targetList = attr.value ? databaseValuesEnabledHooks : databaseValuesDisabledHooks;
76+
targetList.Add(hook);
8077
}
81-
8278
}
83-
ImplementedHooks = implementedHooks.ToArray();
84-
DatabaseValuesDisabledHooks = disabledHooks.ToArray();
85-
DatabaseValuesEnabledHooks = enabledHooks.ToArray();
8679

80+
ImplementedHooks = implementedHooks.ToArray();
81+
DatabaseValuesDisabledHooks = databaseValuesDisabledHooks.ToArray();
82+
DatabaseValuesEnabledHooks = databaseValuesEnabledHooks.ToArray();
8783
}
8884
}
89-
}
85+
}

src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System;
22
namespace JsonApiDotNetCore.Hooks
33
{
4-
public class LoaDatabaseValues : Attribute
4+
public class LoadDatabaseValues : Attribute
55
{
66
public readonly bool value;
7-
public LoaDatabaseValues(bool mode = true)
7+
public LoadDatabaseValues(bool mode = true)
88
{
99
value = mode;
1010
}

src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public IEnumerable<EntityDiffPair<TResource>> GetDiffs()
9090

9191
private HashSet<TResource> ThrowNoDbValuesError()
9292
{
93-
throw new MemberAccessException("Cannot iterate over the diffs if the LoaDatabaseValues option is set to false");
93+
throw new MemberAccessException($"Cannot iterate over the diffs if the ${nameof(LoadDatabaseValues)} option is set to false");
9494
}
9595
}
9696

test/UnitTests/Graph/TypeLocator_Tests.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Reflection;
32
using JsonApiDotNetCore.Graph;
43
using JsonApiDotNetCore.Models;
54
using Xunit;

test/UnitTests/ResourceHooks/DiscoveryTests.cs

+18-34
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using JsonApiDotNetCore.Builders;
66
using JsonApiDotNetCore.Internal;
77
using JsonApiDotNetCore.Internal.Contracts;
8+
using System;
9+
using Microsoft.Extensions.DependencyInjection;
810

911
namespace UnitTests.ResourceHooks
1012
{
@@ -19,85 +21,67 @@ public DummyResourceDefinition() : base(new ResourceGraphBuilder().AddResource<D
1921
public override void AfterDelete(HashSet<Dummy> entities, ResourcePipeline pipeline, bool succeeded) { }
2022
}
2123

24+
private IServiceProvider MockProvider<TResource>(object service) where TResource : class, IIdentifiable
25+
{
26+
var services = new ServiceCollection();
27+
services.AddScoped((_) => (ResourceDefinition<TResource>)service);
28+
return services.BuildServiceProvider();
29+
}
30+
2231
[Fact]
2332
public void Hook_Discovery()
2433
{
2534
// Arrange & act
26-
var hookConfig = new HooksDiscovery<Dummy>();
35+
var hookConfig = new HooksDiscovery<Dummy>(MockProvider<Dummy>(new DummyResourceDefinition()));
2736
// Assert
2837
Assert.Contains(ResourceHook.BeforeDelete, hookConfig.ImplementedHooks);
2938
Assert.Contains(ResourceHook.AfterDelete, hookConfig.ImplementedHooks);
30-
3139
}
3240

3341
public class AnotherDummy : Identifiable { }
3442
public abstract class ResourceDefintionBase<T> : ResourceDefinition<T> where T : class, IIdentifiable
3543
{
36-
protected ResourceDefintionBase(IResourceGraph resourceGraph) : base(resourceGraph) { }
37-
44+
public ResourceDefintionBase(IResourceGraph resourceGraph) : base(resourceGraph) { }
3845
public override IEnumerable<T> BeforeDelete(IEntityHashSet<T> affected, ResourcePipeline pipeline) { return affected; }
3946
public override void AfterDelete(HashSet<T> entities, ResourcePipeline pipeline, bool succeeded) { }
4047
}
4148

4249
public class AnotherDummyResourceDefinition : ResourceDefintionBase<AnotherDummy>
4350
{
44-
public AnotherDummyResourceDefinition() : base(new ResourceGraphBuilder().AddResource<Dummy>().Build()) { }
51+
public AnotherDummyResourceDefinition() : base(new ResourceGraphBuilder().AddResource<AnotherDummy>().Build()) { }
4552
}
53+
4654
[Fact]
4755
public void Hook_Discovery_With_Inheritance()
4856
{
4957
// Arrange & act
50-
var hookConfig = new HooksDiscovery<AnotherDummy>();
58+
var hookConfig = new HooksDiscovery<AnotherDummy>(MockProvider<AnotherDummy>(new AnotherDummyResourceDefinition()));
5159
// Assert
5260
Assert.Contains(ResourceHook.BeforeDelete, hookConfig.ImplementedHooks);
5361
Assert.Contains(ResourceHook.AfterDelete, hookConfig.ImplementedHooks);
5462
}
5563

56-
5764
public class YetAnotherDummy : Identifiable { }
5865
public class YetAnotherDummyResourceDefinition : ResourceDefinition<YetAnotherDummy>
5966
{
6067
public YetAnotherDummyResourceDefinition() : base(new ResourceGraphBuilder().AddResource<YetAnotherDummy>().Build()) { }
6168

6269
public override IEnumerable<YetAnotherDummy> BeforeDelete(IEntityHashSet<YetAnotherDummy> affected, ResourcePipeline pipeline) { return affected; }
6370

64-
[LoaDatabaseValues(false)]
71+
[LoadDatabaseValues(false)]
6572
public override void AfterDelete(HashSet<YetAnotherDummy> entities, ResourcePipeline pipeline, bool succeeded) { }
6673
}
67-
[Fact]
68-
public void LoaDatabaseValues_Attribute_Not_Allowed()
69-
{
70-
// assert
71-
Assert.Throws<JsonApiSetupException>(() =>
72-
{
73-
// Arrange & act
74-
var hookConfig = new HooksDiscovery<YetAnotherDummy>();
75-
});
76-
77-
}
78-
79-
public class DoubleDummy : Identifiable { }
80-
public class DoubleDummyResourceDefinition1 : ResourceDefinition<DoubleDummy>
81-
{
82-
public DoubleDummyResourceDefinition1() : base(new ResourceGraphBuilder().AddResource<DoubleDummy>().Build()) { }
8374

84-
public override IEnumerable<DoubleDummy> BeforeDelete(IEntityHashSet<DoubleDummy> affected, ResourcePipeline pipeline) { return affected; }
85-
}
86-
public class DoubleDummyResourceDefinition2 : ResourceDefinition<DoubleDummy>
87-
{
88-
public DoubleDummyResourceDefinition2() : base(new ResourceGraphBuilder().AddResource<DoubleDummy>().Build()) { }
89-
90-
public override void AfterDelete(HashSet<DoubleDummy> entities, ResourcePipeline pipeline, bool succeeded) { }
91-
}
9275
[Fact]
93-
public void Multiple_Implementations_Of_ResourceDefinitions()
76+
public void LoadDatabaseValues_Attribute_Not_Allowed()
9477
{
9578
// assert
9679
Assert.Throws<JsonApiSetupException>(() =>
9780
{
9881
// Arrange & act
99-
var hookConfig = new HooksDiscovery<DoubleDummy>();
82+
var hookConfig = new HooksDiscovery<YetAnotherDummy>(MockProvider<YetAnotherDummy>(new YetAnotherDummyResourceDefinition()));
10083
});
84+
10185
}
10286
}
10387
}

0 commit comments

Comments
 (0)