Skip to content

Improve usability RequestSerializer #613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ public void ConfigureServices()
_services.AddScoped(typeof(IResourceService<>), typeof(DefaultResourceService<>));
_services.AddScoped(typeof(IResourceService<,>), typeof(DefaultResourceService<,>));

_services.AddScoped(typeof(IResourceQueryService<,>), typeof(DefaultResourceService<,>));
_services.AddScoped(typeof(IResourceCmdService<,>), typeof(DefaultResourceService<,>));

_services.AddSingleton<ILinksConfiguration>(JsonApiOptions);
_services.AddSingleton(resourceGraph);
_services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Expand Down
1 change: 0 additions & 1 deletion src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftLoggingVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.ValueTuple" Version="$(TuplesVersion)" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta-62925-02" PrivateAssets="All" />
</ItemGroup>

<!--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ public interface ITargetedFields
/// </summary>
List<RelationshipAttribute> Relationships { get; set; }
}

}
21 changes: 10 additions & 11 deletions src/JsonApiDotNetCore/Serialization/Client/IRequestSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Internal.Contracts;

namespace JsonApiDotNetCore.Serialization.Client
{
Expand All @@ -23,19 +25,16 @@ public interface IRequestSerializer
/// <returns>The serialized content</returns>
string Serialize(IEnumerable entities);
/// <summary>
/// Sets the <see cref="AttrAttribute"/>s to serialize for resources of type <typeparamref name="TResource"/>.
/// If no <see cref="AttrAttribute"/>s are specified, by default all attributes are included in the serialized result.
/// Sets the attributes that will be included in the serialized payload.
/// You can use <see cref="IResourceGraph.GetAttributes{TResource}(Expression{System.Func{TResource, dynamic}})"/>
/// to conveniently access the desired <see cref="AttrAttribute"/> instances
/// </summary>
/// <typeparam name="TResource">Type of the resource to serialize</typeparam>
/// <param name="filter">Should be of the form: (TResource e) => new { e.Attr1, e.Attr2 }</param>
void SetAttributesToSerialize<TResource>(Expression<System.Func<TResource, dynamic>> filter) where TResource : class, IIdentifiable;
public IEnumerable<AttrAttribute> AttributesToSerialize { set; }
/// <summary>
/// Sets the <see cref="RelationshipAttribute"/>s to serialize for resources of type <typeparamref name="TResource"/>.
/// If no <see cref="RelationshipAttribute"/>s are specified, by default no relationships are included in the serialization result.
/// The <paramref name="filter"/>should be of the form: (TResource e) => new { e.Attr1, e.Attr2 }
/// Sets the relationships that will be included in the serialized payload.
/// You can use <see cref="IResourceGraph.GetRelationships{TResource}(Expression{System.Func{TResource, dynamic}})"/>
/// to conveniently access the desired <see cref="RelationshipAttribute"/> instances
/// </summary>
/// <typeparam name="TResource">Type of the resource to serialize</typeparam>
/// <param name="filter">Should be of the form: (TResource e) => new { e.Attr1, e.Attr2 }</param>
void SetRelationshipsToSerialize<TResource>(Expression<System.Func<TResource, dynamic>> filter) where TResource : class, IIdentifiable;
public IEnumerable<RelationshipAttribute> RelationshipsToSerialize { set; }
}
}
41 changes: 13 additions & 28 deletions src/JsonApiDotNetCore/Serialization/Client/RequestSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
using Newtonsoft.Json;

namespace JsonApiDotNetCore.Serialization.Client
Expand All @@ -14,17 +13,13 @@ namespace JsonApiDotNetCore.Serialization.Client
/// </summary>
public class RequestSerializer : BaseDocumentBuilder, IRequestSerializer
{
private readonly Dictionary<Type, List<AttrAttribute>> _attributesToSerializeCache;
private readonly Dictionary<Type, List<RelationshipAttribute>> _relationshipsToSerializeCache;
private Type _currentTargetedResource;
private readonly IResourceGraph _resourceGraph;
public RequestSerializer(IResourceGraph resourceGraph,
IResourceObjectBuilder resourceObjectBuilder)
: base(resourceObjectBuilder, resourceGraph)
{
_resourceGraph = resourceGraph;
_attributesToSerializeCache = new Dictionary<Type, List<AttrAttribute>>();
_relationshipsToSerializeCache = new Dictionary<Type, List<RelationshipAttribute>>();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -60,55 +55,45 @@ public string Serialize(IEnumerable entities)
}

/// <inheritdoc/>
public void SetAttributesToSerialize<TResource>(Expression<Func<TResource, dynamic>> filter)
where TResource : class, IIdentifiable
{
var allowedAttributes = _resourceGraph.GetAttributes(filter);
_attributesToSerializeCache[typeof(TResource)] = allowedAttributes;
}
public IEnumerable<AttrAttribute> AttributesToSerialize { private get; set; }

/// <inheritdoc/>
public void SetRelationshipsToSerialize<TResource>(Expression<Func<TResource, dynamic>> filter)
where TResource : class, IIdentifiable
{
var allowedRelationships = _resourceGraph.GetRelationships(filter);
_relationshipsToSerializeCache[typeof(TResource)] = allowedRelationships;
}
public IEnumerable<RelationshipAttribute> RelationshipsToSerialize { private get; set; }

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

if (!_attributesToSerializeCache.TryGetValue(resourceType, out var attributes))
return _resourceGraph.GetAttributes(resourceType);
if (AttributesToSerialize == null)
return _resourceGraph.GetAttributes(currentResourceType);

return attributes;
return AttributesToSerialize.ToList();
}

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

return relationships;
return RelationshipsToSerialize.ToList();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public async Task Can_Create_User_With_Password()
{
// Arrange
var user = _userFaker.Generate();

var serializer = _fixture.GetSerializer<User>(p => new { p.Password, p.Username });


Expand Down
12 changes: 6 additions & 6 deletions test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using JsonApiDotNetCore.Builders;
using JsonApiDotNetCoreExampleTests.Helpers.Models;
using JsonApiDotNetCoreExample.Models;
using JsonApiDotNetCore.Internal.Contracts;

namespace JsonApiDotNetCoreExampleTests.Acceptance
{
Expand All @@ -31,17 +32,16 @@ public TestFixture()

public HttpClient Client { get; set; }
public AppDbContext Context { get; private set; }


public IRequestSerializer GetSerializer<TResource>(Expression<Func<TResource, dynamic>> attributes = null, Expression<Func<TResource, dynamic>> relationships = null) where TResource : class, IIdentifiable
{
var serializer = GetService<IRequestSerializer>();
var graph = GetService<IResourceGraph>();
if (attributes != null)
{
serializer.SetAttributesToSerialize(attributes);
}
serializer.AttributesToSerialize = graph.GetAttributes(attributes);
if (relationships != null)
{
serializer.SetRelationshipsToSerialize(relationships);
}
serializer.RelationshipsToSerialize = graph.GetRelationships(relationships);
return serializer;
}
public IResponseDeserializer GetDeserializer()
Expand Down
7 changes: 5 additions & 2 deletions test/NoEntityFrameworkTests/TestFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using JsonApiDotNetCore.Builders;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Serialization.Client;
using Microsoft.AspNetCore.Hosting;
Expand Down Expand Up @@ -28,12 +29,14 @@ public TestFixture()
public IRequestSerializer GetSerializer<TResource>(Expression<Func<TResource, dynamic>> attributes = null, Expression<Func<TResource, dynamic>> relationships = null) where TResource : class, IIdentifiable
{
var serializer = GetService<IRequestSerializer>();
var graph = GetService<IResourceGraph>();
if (attributes != null)
serializer.SetAttributesToSerialize(attributes);
serializer.AttributesToSerialize = graph.GetAttributes(attributes);
if (relationships != null)
serializer.SetRelationshipsToSerialize(relationships);
serializer.RelationshipsToSerialize = graph.GetRelationships(relationships);
return serializer;
}

public IResponseDeserializer GetDeserializer()
{
var resourceGraph = new ResourceGraphBuilder().AddResource<TodoItem>("todo-items").Build();
Expand Down
14 changes: 7 additions & 7 deletions test/UnitTests/Serialization/Client/RequestSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void SerializeSingle_ResourceWithTargetedSetAttributes_CanBuild()
{
// Arrange
var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 };
_serializer.SetAttributesToSerialize<TestResource>(tr => tr.StringField);
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => tr.StringField);

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

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

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

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

// Act
string serialized = _serializer.Serialize(entities);
Expand Down Expand Up @@ -215,7 +215,7 @@ public void SerializeMany_ResourcesWithTargetedAttributes_CanBuild()
public void SerializeSingle_Null_CanBuild()
{
// Arrange
_serializer.SetAttributesToSerialize<TestResource>(tr => tr.StringField);
_serializer.AttributesToSerialize = _resourceGraph.GetAttributes<TestResource>(tr => tr.StringField);

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

// Act
string serialized = _serializer.Serialize(entities);
Expand Down