Skip to content

Commit 382acce

Browse files
authored
Improve usability RequestSerializer (#613)
* feat: remove inability to use request serializer with unknown type at runtime * fix: typo in test setup * chore: spacing * fix: add IResourceQueryService and IResourceCmdService to DI container in application builder * chore: update comments IRequestSerializer
1 parent dc7ae14 commit 382acce

File tree

9 files changed

+45
-56
lines changed

9 files changed

+45
-56
lines changed

src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ public void ConfigureServices()
150150
_services.AddScoped(typeof(IResourceService<>), typeof(DefaultResourceService<>));
151151
_services.AddScoped(typeof(IResourceService<,>), typeof(DefaultResourceService<,>));
152152

153+
_services.AddScoped(typeof(IResourceQueryService<,>), typeof(DefaultResourceService<,>));
154+
_services.AddScoped(typeof(IResourceCmdService<,>), typeof(DefaultResourceService<,>));
155+
153156
_services.AddSingleton<ILinksConfiguration>(JsonApiOptions);
154157
_services.AddSingleton(resourceGraph);
155158
_services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftLoggingVersion)" />
2828
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
2929
<PackageReference Include="System.ValueTuple" Version="$(TuplesVersion)" />
30-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-62925-02" PrivateAssets="All" />
3130
</ItemGroup>
3231

3332
<!--

src/JsonApiDotNetCore/RequestServices/Contracts/IUpdatedFields.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,4 @@ public interface ITargetedFields
1717
/// </summary>
1818
List<RelationshipAttribute> Relationships { get; set; }
1919
}
20-
2120
}

src/JsonApiDotNetCore/Serialization/Client/IRequestSerializer.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Collections;
2+
using System.Collections.Generic;
23
using System.Linq.Expressions;
34
using JsonApiDotNetCore.Models;
5+
using JsonApiDotNetCore.Internal.Contracts;
46

57
namespace JsonApiDotNetCore.Serialization.Client
68
{
@@ -23,19 +25,16 @@ public interface IRequestSerializer
2325
/// <returns>The serialized content</returns>
2426
string Serialize(IEnumerable entities);
2527
/// <summary>
26-
/// Sets the <see cref="AttrAttribute"/>s to serialize for resources of type <typeparamref name="TResource"/>.
27-
/// If no <see cref="AttrAttribute"/>s are specified, by default all attributes are included in the serialized result.
28+
/// Sets the attributes that will be included in the serialized payload.
29+
/// You can use <see cref="IResourceGraph.GetAttributes{TResource}(Expression{System.Func{TResource, dynamic}})"/>
30+
/// to conveniently access the desired <see cref="AttrAttribute"/> instances
2831
/// </summary>
29-
/// <typeparam name="TResource">Type of the resource to serialize</typeparam>
30-
/// <param name="filter">Should be of the form: (TResource e) => new { e.Attr1, e.Attr2 }</param>
31-
void SetAttributesToSerialize<TResource>(Expression<System.Func<TResource, dynamic>> filter) where TResource : class, IIdentifiable;
32+
public IEnumerable<AttrAttribute> AttributesToSerialize { set; }
3233
/// <summary>
33-
/// Sets the <see cref="RelationshipAttribute"/>s to serialize for resources of type <typeparamref name="TResource"/>.
34-
/// If no <see cref="RelationshipAttribute"/>s are specified, by default no relationships are included in the serialization result.
35-
/// The <paramref name="filter"/>should be of the form: (TResource e) => new { e.Attr1, e.Attr2 }
34+
/// Sets the relationships that will be included in the serialized payload.
35+
/// You can use <see cref="IResourceGraph.GetRelationships{TResource}(Expression{System.Func{TResource, dynamic}})"/>
36+
/// to conveniently access the desired <see cref="RelationshipAttribute"/> instances
3637
/// </summary>
37-
/// <typeparam name="TResource">Type of the resource to serialize</typeparam>
38-
/// <param name="filter">Should be of the form: (TResource e) => new { e.Attr1, e.Attr2 }</param>
39-
void SetRelationshipsToSerialize<TResource>(Expression<System.Func<TResource, dynamic>> filter) where TResource : class, IIdentifiable;
38+
public IEnumerable<RelationshipAttribute> RelationshipsToSerialize { set; }
4039
}
4140
}

src/JsonApiDotNetCore/Serialization/Client/RequestSerializer.cs

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4-
using System.Linq.Expressions;
4+
using System.Linq;
55
using JsonApiDotNetCore.Internal.Contracts;
66
using JsonApiDotNetCore.Models;
7-
using JsonApiDotNetCore.Services;
87
using Newtonsoft.Json;
98

109
namespace JsonApiDotNetCore.Serialization.Client
@@ -14,17 +13,13 @@ namespace JsonApiDotNetCore.Serialization.Client
1413
/// </summary>
1514
public class RequestSerializer : BaseDocumentBuilder, IRequestSerializer
1615
{
17-
private readonly Dictionary<Type, List<AttrAttribute>> _attributesToSerializeCache;
18-
private readonly Dictionary<Type, List<RelationshipAttribute>> _relationshipsToSerializeCache;
1916
private Type _currentTargetedResource;
2017
private readonly IResourceGraph _resourceGraph;
2118
public RequestSerializer(IResourceGraph resourceGraph,
2219
IResourceObjectBuilder resourceObjectBuilder)
2320
: base(resourceObjectBuilder, resourceGraph)
2421
{
2522
_resourceGraph = resourceGraph;
26-
_attributesToSerializeCache = new Dictionary<Type, List<AttrAttribute>>();
27-
_relationshipsToSerializeCache = new Dictionary<Type, List<RelationshipAttribute>>();
2823
}
2924

3025
/// <inheritdoc/>
@@ -60,55 +55,45 @@ public string Serialize(IEnumerable entities)
6055
}
6156

6257
/// <inheritdoc/>
63-
public void SetAttributesToSerialize<TResource>(Expression<Func<TResource, dynamic>> filter)
64-
where TResource : class, IIdentifiable
65-
{
66-
var allowedAttributes = _resourceGraph.GetAttributes(filter);
67-
_attributesToSerializeCache[typeof(TResource)] = allowedAttributes;
68-
}
58+
public IEnumerable<AttrAttribute> AttributesToSerialize { private get; set; }
6959

7060
/// <inheritdoc/>
71-
public void SetRelationshipsToSerialize<TResource>(Expression<Func<TResource, dynamic>> filter)
72-
where TResource : class, IIdentifiable
73-
{
74-
var allowedRelationships = _resourceGraph.GetRelationships(filter);
75-
_relationshipsToSerializeCache[typeof(TResource)] = allowedRelationships;
76-
}
61+
public IEnumerable<RelationshipAttribute> RelationshipsToSerialize { private get; set; }
7762

7863
/// <summary>
7964
/// By default, the client serializer includes all attributes in the result,
80-
/// unless a list of allowed attributes was supplied using the <see cref="SetAttributesToSerialize"/>
65+
/// unless a list of allowed attributes was supplied using the <see cref="AttributesToSerialize"/>
8166
/// method. For any related resources, attributes are never exposed.
8267
/// </summary>
8368
private List<AttrAttribute> GetAttributesToSerialize(IIdentifiable entity)
8469
{
85-
var resourceType = entity.GetType();
86-
if (_currentTargetedResource != resourceType)
70+
var currentResourceType = entity.GetType();
71+
if (_currentTargetedResource != currentResourceType)
8772
// We're dealing with a relationship that is being serialized, for which
8873
// we never want to include any attributes in the payload.
8974
return new List<AttrAttribute>();
9075

91-
if (!_attributesToSerializeCache.TryGetValue(resourceType, out var attributes))
92-
return _resourceGraph.GetAttributes(resourceType);
76+
if (AttributesToSerialize == null)
77+
return _resourceGraph.GetAttributes(currentResourceType);
9378

94-
return attributes;
79+
return AttributesToSerialize.ToList();
9580
}
9681

9782
/// <summary>
9883
/// By default, the client serializer does not include any relationships
9984
/// for entities in the primary data unless explicitly included using
100-
/// <see cref="SetRelationshipsToSerialize{T}(Expression{Func{T, dynamic}})"/>.
85+
/// <see cref="RelationshipsToSerialize"/>.
10186
/// </summary>
10287
private List<RelationshipAttribute> GetRelationshipsToSerialize(IIdentifiable entity)
10388
{
10489
var currentResourceType = entity.GetType();
10590
/// only allow relationship attributes to be serialized if they were set using
10691
/// <see cref="RelationshipsToInclude{T}(Expression{Func{T, dynamic}})"/>
10792
/// and the current <paramref name="entity"/> is a main entry in the primary data.
108-
if (!_relationshipsToSerializeCache.TryGetValue(currentResourceType, out var relationships))
109-
return new List<RelationshipAttribute>();
93+
if (RelationshipsToSerialize == null)
94+
return _resourceGraph.GetRelationships(currentResourceType);
11095

111-
return relationships;
96+
return RelationshipsToSerialize.ToList();
11297
}
11398
}
11499
}

test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public async Task Can_Create_User_With_Password()
7272
{
7373
// Arrange
7474
var user = _userFaker.Generate();
75+
7576
var serializer = _fixture.GetSerializer<User>(p => new { p.Password, p.Username });
7677

7778

test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using JsonApiDotNetCore.Builders;
1212
using JsonApiDotNetCoreExampleTests.Helpers.Models;
1313
using JsonApiDotNetCoreExample.Models;
14+
using JsonApiDotNetCore.Internal.Contracts;
1415

1516
namespace JsonApiDotNetCoreExampleTests.Acceptance
1617
{
@@ -31,17 +32,16 @@ public TestFixture()
3132

3233
public HttpClient Client { get; set; }
3334
public AppDbContext Context { get; private set; }
35+
36+
3437
public IRequestSerializer GetSerializer<TResource>(Expression<Func<TResource, dynamic>> attributes = null, Expression<Func<TResource, dynamic>> relationships = null) where TResource : class, IIdentifiable
3538
{
3639
var serializer = GetService<IRequestSerializer>();
40+
var graph = GetService<IResourceGraph>();
3741
if (attributes != null)
38-
{
39-
serializer.SetAttributesToSerialize(attributes);
40-
}
42+
serializer.AttributesToSerialize = graph.GetAttributes(attributes);
4143
if (relationships != null)
42-
{
43-
serializer.SetRelationshipsToSerialize(relationships);
44-
}
44+
serializer.RelationshipsToSerialize = graph.GetRelationships(relationships);
4545
return serializer;
4646
}
4747
public IResponseDeserializer GetDeserializer()

test/NoEntityFrameworkTests/TestFixture.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using JsonApiDotNetCore.Builders;
2+
using JsonApiDotNetCore.Internal.Contracts;
23
using JsonApiDotNetCore.Models;
34
using JsonApiDotNetCore.Serialization.Client;
45
using Microsoft.AspNetCore.Hosting;
@@ -28,12 +29,14 @@ public TestFixture()
2829
public IRequestSerializer GetSerializer<TResource>(Expression<Func<TResource, dynamic>> attributes = null, Expression<Func<TResource, dynamic>> relationships = null) where TResource : class, IIdentifiable
2930
{
3031
var serializer = GetService<IRequestSerializer>();
32+
var graph = GetService<IResourceGraph>();
3133
if (attributes != null)
32-
serializer.SetAttributesToSerialize(attributes);
34+
serializer.AttributesToSerialize = graph.GetAttributes(attributes);
3335
if (relationships != null)
34-
serializer.SetRelationshipsToSerialize(relationships);
36+
serializer.RelationshipsToSerialize = graph.GetRelationships(relationships);
3537
return serializer;
3638
}
39+
3740
public IResponseDeserializer GetDeserializer()
3841
{
3942
var resourceGraph = new ResourceGraphBuilder().AddResource<TodoItem>("todo-items").Build();

test/UnitTests/Serialization/Client/RequestSerializerTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void SerializeSingle_ResourceWithTargetedSetAttributes_CanBuild()
5656
{
5757
// Arrange
5858
var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 };
59-
_serializer.SetAttributesToSerialize<TestResource>(tr => tr.StringField);
59+
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => tr.StringField);
6060

6161
// Act
6262
string serialized = _serializer.Serialize(entity);
@@ -81,7 +81,7 @@ public void SerializeSingle_NoIdWithTargetedSetAttributes_CanBuild()
8181
{
8282
// Arrange
8383
var entityNoId = new TestResource() { Id = 0, StringField = "value", NullableIntField = 123 };
84-
_serializer.SetAttributesToSerialize<TestResource>(tr => tr.StringField);
84+
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => tr.StringField);
8585

8686
// Act
8787
string serialized = _serializer.Serialize(entityNoId);
@@ -106,7 +106,7 @@ public void SerializeSingle_ResourceWithoutTargetedAttributes_CanBuild()
106106
{
107107
// Arrange
108108
var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 };
109-
_serializer.SetAttributesToSerialize<TestResource>(tr => new { });
109+
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => new { });
110110

111111
// Act
112112
string serialized = _serializer.Serialize(entity);
@@ -133,7 +133,7 @@ public void SerializeSingle_ResourceWithTargetedRelationships_CanBuild()
133133
PopulatedToOne = new OneToOneDependent { Id = 10 },
134134
PopulatedToManies = new List<OneToManyDependent> { new OneToManyDependent { Id = 20 } }
135135
};
136-
_serializer.SetRelationshipsToSerialize<MultipleRelationshipsPrincipalPart>(tr => new { tr.EmptyToOne, tr.EmptyToManies, tr.PopulatedToOne, tr.PopulatedToManies });
136+
_serializer.RelationshipsToSerialize = _resourceGraph.GetRelationships<MultipleRelationshipsPrincipalPart>(tr => new { tr.EmptyToOne, tr.EmptyToManies, tr.PopulatedToOne, tr.PopulatedToManies });
137137

138138
// Act
139139
string serialized = _serializer.Serialize(entityWithRelationships);
@@ -182,7 +182,7 @@ public void SerializeMany_ResourcesWithTargetedAttributes_CanBuild()
182182
new TestResource() { Id = 1, StringField = "value1", NullableIntField = 123 },
183183
new TestResource() { Id = 2, StringField = "value2", NullableIntField = 123 }
184184
};
185-
_serializer.SetAttributesToSerialize<TestResource>(tr => tr.StringField);
185+
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => tr.StringField);
186186

187187
// Act
188188
string serialized = _serializer.Serialize(entities);
@@ -215,7 +215,7 @@ public void SerializeMany_ResourcesWithTargetedAttributes_CanBuild()
215215
public void SerializeSingle_Null_CanBuild()
216216
{
217217
// Arrange
218-
_serializer.SetAttributesToSerialize<TestResource>(tr => tr.StringField);
218+
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => tr.StringField);
219219

220220
// Act
221221
IIdentifiable obj = null;
@@ -235,7 +235,7 @@ public void SerializeMany_EmptyList_CanBuild()
235235
{
236236
// Arrange
237237
var entities = new List<TestResource> { };
238-
_serializer.SetAttributesToSerialize<TestResource>(tr => tr.StringField);
238+
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => tr.StringField);
239239

240240
// Act
241241
string serialized = _serializer.Serialize(entities);

0 commit comments

Comments
 (0)