Skip to content

Commit 7edb021

Browse files
author
Bart Koelman
committed
Removed dead code, cleaned up ResourceGraph and reduced collection copying
1 parent 51d16b2 commit 7edb021

File tree

12 files changed

+90
-177
lines changed

12 files changed

+90
-177
lines changed

src/JsonApiDotNetCore/Configuration/IResourceGraph.cs

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,62 +18,38 @@ public interface IResourceGraph : IResourceContextProvider
1818
/// all exposed fields are returned.
1919
/// </summary>
2020
/// <typeparam name="TResource">
21-
/// The resource for which to retrieve fields.
21+
/// The resource type for which to retrieve fields.
2222
/// </typeparam>
2323
/// <param name="selector">
24-
/// Should be of the form: (TResource e) => new { e.Field1, e.Field2 }
24+
/// Should be of the form: (TResource r) => new { r.Field1, r.Field2 }
2525
/// </param>
26-
IReadOnlyCollection<ResourceFieldAttribute> GetFields<TResource>(Expression<Func<TResource, dynamic>> selector = null)
26+
IReadOnlyCollection<ResourceFieldAttribute> GetFields<TResource>(Expression<Func<TResource, dynamic>> selector)
2727
where TResource : class, IIdentifiable;
2828

2929
/// <summary>
30-
/// Gets all attributes for <typeparamref name="TResource" /> that are targeted by the selector. If no selector is provided, all exposed fields are
30+
/// Gets all attributes for <typeparamref name="TResource" /> that are targeted by the selector. If no selector is provided, all exposed attributes are
3131
/// returned.
3232
/// </summary>
3333
/// <typeparam name="TResource">
34-
/// The resource for which to retrieve attributes.
34+
/// The resource type for which to retrieve attributes.
3535
/// </typeparam>
3636
/// <param name="selector">
37-
/// Should be of the form: (TResource e) => new { e.Attribute1, e.Attribute2 }
37+
/// Should be of the form: (TResource r) => new { r.Attribute1, r.Attribute2 }
3838
/// </param>
39-
IReadOnlyCollection<AttrAttribute> GetAttributes<TResource>(Expression<Func<TResource, dynamic>> selector = null)
39+
IReadOnlyCollection<AttrAttribute> GetAttributes<TResource>(Expression<Func<TResource, dynamic>> selector)
4040
where TResource : class, IIdentifiable;
4141

4242
/// <summary>
43-
/// Gets all relationships for <typeparamref name="TResource" /> that are targeted by the selector. If no selector is provided, all exposed fields are
44-
/// returned.
43+
/// Gets all relationships for <typeparamref name="TResource" /> that are targeted by the selector. If no selector is provided, all exposed relationships
44+
/// are returned.
4545
/// </summary>
4646
/// <typeparam name="TResource">
47-
/// The resource for which to retrieve relationships.
47+
/// The resource type for which to retrieve relationships.
4848
/// </typeparam>
4949
/// <param name="selector">
50-
/// Should be of the form: (TResource e) => new { e.Relationship1, e.Relationship2 }
50+
/// Should be of the form: (TResource r) => new { r.Relationship1, r.Relationship2 }
5151
/// </param>
52-
IReadOnlyCollection<RelationshipAttribute> GetRelationships<TResource>(Expression<Func<TResource, dynamic>> selector = null)
52+
IReadOnlyCollection<RelationshipAttribute> GetRelationships<TResource>(Expression<Func<TResource, dynamic>> selector)
5353
where TResource : class, IIdentifiable;
54-
55-
/// <summary>
56-
/// Gets all exposed fields (attributes and relationships) for the specified type.
57-
/// </summary>
58-
/// <param name="type">
59-
/// The resource type. Must implement <see cref="IIdentifiable" />.
60-
/// </param>
61-
IReadOnlyCollection<ResourceFieldAttribute> GetFields(Type type);
62-
63-
/// <summary>
64-
/// Gets all exposed attributes for the specified type.
65-
/// </summary>
66-
/// <param name="type">
67-
/// The resource type. Must implement <see cref="IIdentifiable" />.
68-
/// </param>
69-
IReadOnlyCollection<AttrAttribute> GetAttributes(Type type);
70-
71-
/// <summary>
72-
/// Gets all exposed relationships for the specified type.
73-
/// </summary>
74-
/// <param name="type">
75-
/// The resource type. Must implement <see cref="IIdentifiable" />.
76-
/// </param>
77-
IReadOnlyCollection<RelationshipAttribute> GetRelationships(Type type);
7854
}
7955
}

src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv
5151
var loggerFactory = _intermediateProvider.GetRequiredService<ILoggerFactory>();
5252

5353
_resourceGraphBuilder = new ResourceGraphBuilder(_options, loggerFactory);
54-
_serviceDiscoveryFacade = new ServiceDiscoveryFacade(_services, _resourceGraphBuilder, _options, loggerFactory);
54+
_serviceDiscoveryFacade = new ServiceDiscoveryFacade(_services, _resourceGraphBuilder, loggerFactory);
5555
}
5656

5757
/// <summary>

src/JsonApiDotNetCore/Configuration/ResourceGraph.cs

Lines changed: 54 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -47,133 +47,110 @@ public ResourceContext GetResourceContext(Type resourceType)
4747
: _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType);
4848
}
4949

50-
/// <inheritdoc />
51-
public ResourceContext GetResourceContext<TResource>()
52-
where TResource : class, IIdentifiable
50+
private bool IsLazyLoadingProxyForResourceType(Type resourceType)
5351
{
54-
return GetResourceContext(typeof(TResource));
52+
return ProxyTargetAccessorType?.IsAssignableFrom(resourceType) ?? false;
5553
}
5654

5755
/// <inheritdoc />
58-
public IReadOnlyCollection<ResourceFieldAttribute> GetFields<TResource>(Expression<Func<TResource, dynamic>> selector = null)
56+
public ResourceContext GetResourceContext<TResource>()
5957
where TResource : class, IIdentifiable
6058
{
61-
return Getter(selector);
59+
return GetResourceContext(typeof(TResource));
6260
}
6361

6462
/// <inheritdoc />
65-
public IReadOnlyCollection<AttrAttribute> GetAttributes<TResource>(Expression<Func<TResource, dynamic>> selector = null)
63+
public IReadOnlyCollection<ResourceFieldAttribute> GetFields<TResource>(Expression<Func<TResource, dynamic>> selector)
6664
where TResource : class, IIdentifiable
6765
{
68-
return Getter(selector, FieldFilterType.Attribute).Cast<AttrAttribute>().ToArray();
66+
ArgumentGuard.NotNull(selector, nameof(selector));
67+
68+
return FilterFields<TResource, ResourceFieldAttribute>(selector);
6969
}
7070

7171
/// <inheritdoc />
72-
public IReadOnlyCollection<RelationshipAttribute> GetRelationships<TResource>(Expression<Func<TResource, dynamic>> selector = null)
72+
public IReadOnlyCollection<AttrAttribute> GetAttributes<TResource>(Expression<Func<TResource, dynamic>> selector)
7373
where TResource : class, IIdentifiable
7474
{
75-
return Getter(selector, FieldFilterType.Relationship).Cast<RelationshipAttribute>().ToArray();
75+
ArgumentGuard.NotNull(selector, nameof(selector));
76+
77+
return FilterFields<TResource, AttrAttribute>(selector);
7678
}
7779

7880
/// <inheritdoc />
79-
public IReadOnlyCollection<ResourceFieldAttribute> GetFields(Type type)
81+
public IReadOnlyCollection<RelationshipAttribute> GetRelationships<TResource>(Expression<Func<TResource, dynamic>> selector)
82+
where TResource : class, IIdentifiable
8083
{
81-
ArgumentGuard.NotNull(type, nameof(type));
84+
ArgumentGuard.NotNull(selector, nameof(selector));
8285

83-
return GetResourceContext(type).Fields;
86+
return FilterFields<TResource, RelationshipAttribute>(selector);
8487
}
8588

86-
/// <inheritdoc />
87-
public IReadOnlyCollection<AttrAttribute> GetAttributes(Type type)
89+
private IReadOnlyCollection<TField> FilterFields<TResource, TField>(Expression<Func<TResource, dynamic>> selector)
90+
where TResource : class, IIdentifiable
91+
where TField : ResourceFieldAttribute
8892
{
89-
ArgumentGuard.NotNull(type, nameof(type));
93+
IReadOnlyCollection<TField> source = GetFieldsOfType<TResource, TField>();
94+
var matches = new List<TField>();
9095

91-
return GetResourceContext(type).Attributes;
92-
}
96+
foreach (string memberName in ToMemberNames(selector))
97+
{
98+
TField matchingField = source.FirstOrDefault(field => field.Property.Name == memberName);
9399

94-
/// <inheritdoc />
95-
public IReadOnlyCollection<RelationshipAttribute> GetRelationships(Type type)
96-
{
97-
ArgumentGuard.NotNull(type, nameof(type));
100+
if (matchingField == null)
101+
{
102+
throw new ArgumentException($"Member '{memberName}' is not exposed as a JSON:API field.");
103+
}
98104

99-
return GetResourceContext(type).Relationships;
105+
matches.Add(matchingField);
106+
}
107+
108+
return matches;
100109
}
101110

102-
private IReadOnlyCollection<ResourceFieldAttribute> Getter<TResource>(Expression<Func<TResource, dynamic>> selector = null,
103-
FieldFilterType type = FieldFilterType.None)
104-
where TResource : class, IIdentifiable
111+
private IReadOnlyCollection<TKind> GetFieldsOfType<TResource, TKind>()
112+
where TKind : ResourceFieldAttribute
105113
{
106-
IReadOnlyCollection<ResourceFieldAttribute> available;
114+
ResourceContext resourceContext = GetResourceContext(typeof(TResource));
107115

108-
if (type == FieldFilterType.Attribute)
109-
{
110-
available = GetResourceContext(typeof(TResource)).Attributes;
111-
}
112-
else if (type == FieldFilterType.Relationship)
113-
{
114-
available = GetResourceContext(typeof(TResource)).Relationships;
115-
}
116-
else
116+
if (typeof(TKind) == typeof(AttrAttribute))
117117
{
118-
available = GetResourceContext(typeof(TResource)).Fields;
118+
return (IReadOnlyCollection<TKind>)resourceContext.Attributes;
119119
}
120120

121-
if (selector == null)
121+
if (typeof(TKind) == typeof(RelationshipAttribute))
122122
{
123-
return available;
123+
return (IReadOnlyCollection<TKind>)resourceContext.Relationships;
124124
}
125125

126-
var targeted = new List<ResourceFieldAttribute>();
126+
return (IReadOnlyCollection<TKind>)resourceContext.Fields;
127+
}
127128

129+
private IEnumerable<string> ToMemberNames<TResource>(Expression<Func<TResource, dynamic>> selector)
130+
{
128131
Expression selectorBody = RemoveConvert(selector.Body);
129132

130133
if (selectorBody is MemberExpression memberExpression)
131134
{
132135
// model => model.Field1
133-
try
134-
{
135-
targeted.Add(available.Single(field => field.Property.Name == memberExpression.Member.Name));
136-
return targeted;
137-
}
138-
catch (InvalidOperationException)
139-
{
140-
ThrowNotExposedError(memberExpression.Member.Name, type);
141-
}
142-
}
143136

144-
if (selectorBody is NewExpression newExpression)
137+
yield return memberExpression.Member.Name;
138+
}
139+
else if (selectorBody is NewExpression newExpression)
145140
{
146141
// model => new { model.Field1, model.Field2 }
147-
string memberName = null;
148142

149-
try
150-
{
151-
if (newExpression.Members == null)
152-
{
153-
return targeted;
154-
}
155-
156-
foreach (MemberInfo member in newExpression.Members)
157-
{
158-
memberName = member.Name;
159-
targeted.Add(available.Single(field => field.Property.Name == memberName));
160-
}
161-
162-
return targeted;
163-
}
164-
catch (InvalidOperationException)
143+
foreach (MemberInfo member in newExpression.Members ?? Enumerable.Empty<MemberInfo>())
165144
{
166-
ThrowNotExposedError(memberName, type);
145+
yield return member.Name;
167146
}
168147
}
169-
170-
throw new ArgumentException($"The expression '{selector}' should select a single property or select multiple properties into an anonymous type. " +
171-
"For example: 'article => article.Title' or 'article => new { article.Title, article.PageCount }'.");
172-
}
173-
174-
private bool IsLazyLoadingProxyForResourceType(Type resourceType)
175-
{
176-
return ProxyTargetAccessorType?.IsAssignableFrom(resourceType) ?? false;
148+
else
149+
{
150+
throw new ArgumentException(
151+
$"The expression '{selector}' should select a single property or select multiple properties into an anonymous type. " +
152+
"For example: 'article => article.Title' or 'article => new { article.Title, article.PageCount }'.");
153+
}
177154
}
178155

179156
private static Expression RemoveConvert(Expression expression)
@@ -192,17 +169,5 @@ private static Expression RemoveConvert(Expression expression)
192169
}
193170
}
194171
}
195-
196-
private void ThrowNotExposedError(string memberName, FieldFilterType type)
197-
{
198-
throw new ArgumentException($"{memberName} is not a JSON:API exposed {type:g}.");
199-
}
200-
201-
private enum FieldFilterType
202-
{
203-
None,
204-
Attribute,
205-
Relationship
206-
}
207172
}
208173
}

src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -202,26 +202,6 @@ private IReadOnlyCollection<RelationshipAttribute> GetRelationships(Type resourc
202202
return attributes;
203203
}
204204

205-
private Type TryGetThroughType(PropertyInfo throughProperty)
206-
{
207-
if (throughProperty.PropertyType.IsGenericType)
208-
{
209-
Type[] typeArguments = throughProperty.PropertyType.GetGenericArguments();
210-
211-
if (typeArguments.Length == 1)
212-
{
213-
Type constructedThroughType = typeof(ICollection<>).MakeGenericType(typeArguments[0]);
214-
215-
if (throughProperty.PropertyType.IsOrImplementsInterface(constructedThroughType))
216-
{
217-
return typeArguments[0];
218-
}
219-
}
220-
}
221-
222-
return null;
223-
}
224-
225205
private Type GetRelationshipType(RelationshipAttribute relationship, PropertyInfo property)
226206
{
227207
ArgumentGuard.NotNull(relationship, nameof(relationship));

src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,18 @@ public class ServiceDiscoveryFacade
6666
private readonly ILogger<ServiceDiscoveryFacade> _logger;
6767
private readonly IServiceCollection _services;
6868
private readonly ResourceGraphBuilder _resourceGraphBuilder;
69-
private readonly IJsonApiOptions _options;
7069
private readonly ResourceDescriptorAssemblyCache _assemblyCache = new();
7170
private readonly TypeLocator _typeLocator = new();
7271

73-
public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, IJsonApiOptions options,
74-
ILoggerFactory loggerFactory)
72+
public ServiceDiscoveryFacade(IServiceCollection services, ResourceGraphBuilder resourceGraphBuilder, ILoggerFactory loggerFactory)
7573
{
7674
ArgumentGuard.NotNull(services, nameof(services));
7775
ArgumentGuard.NotNull(resourceGraphBuilder, nameof(resourceGraphBuilder));
7876
ArgumentGuard.NotNull(loggerFactory, nameof(loggerFactory));
79-
ArgumentGuard.NotNull(options, nameof(options));
8077

8178
_logger = loggerFactory.CreateLogger<ServiceDiscoveryFacade>();
8279
_services = services;
8380
_resourceGraphBuilder = resourceGraphBuilder;
84-
_options = options;
8581
}
8682

8783
/// <summary>

src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToke
314314
using var collector = new PlaceholderResourceCollector(_resourceFactory, _dbContext);
315315
TResource resource = collector.CreateForId<TResource, TId>(id);
316316

317-
foreach (RelationshipAttribute relationship in _resourceGraph.GetRelationships<TResource>())
317+
foreach (RelationshipAttribute relationship in _resourceGraph.GetResourceContext<TResource>().Relationships)
318318
{
319319
// Loads the data of the relationship, if in EF Core it is configured in such a way that loading the related
320320
// entities into memory is required for successfully executing the selected deletion behavior.

src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public abstract class RelationshipAttribute : ResourceFieldAttribute
7070
/// <summary>
7171
/// Gets the value of the resource property this attribute was declared on.
7272
/// </summary>
73-
public virtual object GetValue(object resource)
73+
public object GetValue(object resource)
7474
{
7575
ArgumentGuard.NotNull(resource, nameof(resource));
7676

@@ -80,7 +80,7 @@ public virtual object GetValue(object resource)
8080
/// <summary>
8181
/// Sets the value of the resource property this attribute was declared on.
8282
/// </summary>
83-
public virtual void SetValue(object resource, object newValue)
83+
public void SetValue(object resource, object newValue)
8484
{
8585
ArgumentGuard.NotNull(resource, nameof(resource));
8686

src/JsonApiDotNetCore/Serialization/Client/Internal/IRequestSerializer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ namespace JsonApiDotNetCore.Serialization.Client.Internal
1313
public interface IRequestSerializer
1414
{
1515
/// <summary>
16-
/// Sets the attributes that will be included in the serialized request body. You can use <see cref="IResourceGraph.GetAttributes{TResource}" /> to
17-
/// conveniently access the desired <see cref="AttrAttribute" /> instances.
16+
/// Sets the attributes that will be included in the serialized request body. You can use <see cref="IResourceContextProvider.GetResourceContext{T}()" />
17+
/// to conveniently access the desired <see cref="AttrAttribute" /> instances.
1818
/// </summary>
1919
public IReadOnlyCollection<AttrAttribute> AttributesToSerialize { get; set; }
2020

2121
/// <summary>
22-
/// Sets the relationships that will be included in the serialized request body. You can use <see cref="IResourceGraph.GetRelationships" /> to
23-
/// conveniently access the desired <see cref="RelationshipAttribute" /> instances.
22+
/// Sets the relationships that will be included in the serialized request body. You can use
23+
/// <see cref="IResourceContextProvider.GetResourceContext{T}()" /> to conveniently access the desired <see cref="RelationshipAttribute" /> instances.
2424
/// </summary>
2525
public IReadOnlyCollection<RelationshipAttribute> RelationshipsToSerialize { get; set; }
2626

src/JsonApiDotNetCore/Serialization/Client/Internal/RequestSerializer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ private IReadOnlyCollection<AttrAttribute> GetAttributesToSerialize(IIdentifiabl
9393

9494
if (AttributesToSerialize == null)
9595
{
96-
return _resourceGraph.GetAttributes(currentResourceType);
96+
ResourceContext resourceContext = _resourceGraph.GetResourceContext(currentResourceType);
97+
return resourceContext.Attributes;
9798
}
9899

99100
return AttributesToSerialize;

0 commit comments

Comments
 (0)