Skip to content

De-Couple deserializer from GenericProcessorFactory #254

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 13 commits into from
Jun 12, 2018
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
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
Id = entity.StringId
};

if (_jsonApiContext.IsRelationshipData)
if (_jsonApiContext.IsRelationshipPath)
return data;

data.Attributes = new Dictionary<string, object>();
Expand Down
18 changes: 18 additions & 0 deletions src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,29 @@ public virtual async Task<TEntity> GetAndIncludeAsync(TId id, string relationshi

public virtual async Task<TEntity> CreateAsync(TEntity entity)
{
AttachHasManyPointers();
_dbSet.Add(entity);

await _context.SaveChangesAsync();
return entity;
}

/// <summary>
/// This is used to allow creation of HasMany relationships when the
/// dependent side of the relationship already exists.
/// </summary>
private void AttachHasManyPointers()
{
var relationships = _jsonApiContext.HasManyRelationshipPointers.Get();
foreach(var relationship in relationships)
{
foreach(var pointer in relationship.Value)
{
_context.Entry(pointer).State = EntityState.Unchanged;
}
}
}

public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
{
var oldEntity = await GetAsync(id);
Expand Down
4 changes: 0 additions & 4 deletions src/JsonApiDotNetCore/Data/IEntityRepository.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JsonApiDotNetCore.Internal.Query;
using JsonApiDotNetCore.Models;

namespace JsonApiDotNetCore.Data
Expand Down
16 changes: 4 additions & 12 deletions src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
using Microsoft.EntityFrameworkCore;
using System;
using Microsoft.EntityFrameworkCore;

namespace JsonApiDotNetCore.Extensions
{
public static class DbContextExtensions
{
public static DbSet<T> GetDbSet<T>(this DbContext context) where T : class
{
var contextProperties = context.GetType().GetProperties();
foreach(var property in contextProperties)
{
if (property.PropertyType == typeof(DbSet<T>))
return (DbSet<T>)property.GetValue(context);
}

throw new ArgumentException($"DbSet of type {typeof(T).FullName} not found on the DbContext", nameof(T));
}
[Obsolete("This is no longer required since the introduction of context.Set<T>", error: false)]
public static DbSet<T> GetDbSet<T>(this DbContext context) where T : class
=> context.Set<T>();
}
}
23 changes: 23 additions & 0 deletions src/JsonApiDotNetCore/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,28 @@ public static Type GetElementType(this IEnumerable enumerable)

return elementType;
}

/// <summary>
/// Creates a List{TInterface} where TInterface is the generic for type specified by t
/// </summary>
public static IEnumerable GetEmptyCollection(this Type t)
{
if (t == null) throw new ArgumentNullException(nameof(t));

var listType = typeof(List<>).MakeGenericType(t);
var list = (IEnumerable)Activator.CreateInstance(listType);
return list;
}

/// <summary>
/// Creates a new instance of type t, casting it to the specified TInterface
/// </summary>
public static TInterface New<TInterface>(this Type t)
{
if (t == null) throw new ArgumentNullException(nameof(t));

var instance = (TInterface)Activator.CreateInstance(t);
return instance;
}
}
}
8 changes: 8 additions & 0 deletions src/JsonApiDotNetCore/Internal/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ namespace JsonApiDotNetCore.Internal
{
public static class TypeHelper
{
public static IList ConvertCollection(IEnumerable<object> collection, Type targetType)
{
var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(targetType)) as IList;
foreach(var item in collection)
list.Add(ConvertType(item, targetType));
return list;
}

public static object ConvertType(object value, Type type)
{
if (value == null)
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>2.2.4</VersionPrefix>
<VersionPrefix>2.2.5</VersionPrefix>
<TargetFrameworks>$(NetStandardVersion)</TargetFrameworks>
<AssemblyName>JsonApiDotNetCore</AssemblyName>
<PackageId>JsonApiDotNetCore</PackageId>
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Models/HasManyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public override void SetValue(object entity, object newValue)
.GetType()
.GetProperty(InternalRelationshipName);

propertyInfo.SetValue(entity, newValue);
propertyInfo.SetValue(entity, newValue);
}
}
}
7 changes: 6 additions & 1 deletion src/JsonApiDotNetCore/Models/HasOneAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ public HasOneAttribute(string publicName, Link documentLinks = Link.All, bool ca
? $"{InternalRelationshipName}Id"
: _explicitIdentifiablePropertyName;

/// <summary>
/// Sets the value of the property identified by this attribute
/// </summary>
/// <param name="entity">The target object</param>
/// <param name="newValue">The new property value</param>
public override void SetValue(object entity, object newValue)
{
var propertyName = (newValue.GetType() == Type)
var propertyName = (newValue?.GetType() == Type)
? InternalRelationshipName
: IdentifiablePropertyName;

Expand Down
22 changes: 22 additions & 0 deletions src/JsonApiDotNetCore/Models/RelationshipAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI
public Link DocumentLinks { get; } = Link.All;
public bool CanInclude { get; }

public bool TryGetHasOne(out HasOneAttribute result)
{
if (IsHasOne)
{
result = (HasOneAttribute)this;
return true;
}
result = null;
return false;
}

public bool TryGetHasMany(out HasManyAttribute result)
{
if (IsHasMany)
{
result = (HasManyAttribute)this;
return true;
}
result = null;
return false;
}

public abstract void SetValue(object entity, object newValue);

public override string ToString()
Expand Down
49 changes: 49 additions & 0 deletions src/JsonApiDotNetCore/Request/HasManyRelationshipPointers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace JsonApiDotNetCore.Request
{
/// <summary>
/// Stores information to set relationships for the request resource.
/// These relationships must already exist and should not be re-created.
///
/// The expected use case is POST-ing or PATCH-ing
/// an entity with HasMany relaitonships:
/// <code>
/// {
/// "data": {
/// "type": "photos",
/// "attributes": {
/// "title": "Ember Hamster",
/// "src": "http://example.com/images/productivity.png"
/// },
/// "relationships": {
/// "tags": {
/// "data": [
/// { "type": "tags", "id": "2" },
/// { "type": "tags", "id": "3" }
/// ]
/// }
/// }
/// }
/// }
/// </code>
/// </summary>
public class HasManyRelationshipPointers
{
private Dictionary<Type, IList> _hasManyRelationships = new Dictionary<Type, IList>();

/// <summary>
/// Add the relationship to the list of relationships that should be
/// set in the repository layer.
/// </summary>
public void Add(Type dependentType, IList entities)
=> _hasManyRelationships[dependentType] = entities;

/// <summary>
/// Get all the models that should be associated
/// </summary>
public Dictionary<Type, IList> Get() => _hasManyRelationships;
}
}
50 changes: 32 additions & 18 deletions src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Generics;
using JsonApiDotNetCore.Models;
Expand All @@ -15,14 +16,20 @@ namespace JsonApiDotNetCore.Serialization
public class JsonApiDeSerializer : IJsonApiDeSerializer
{
private readonly IJsonApiContext _jsonApiContext;
private readonly IGenericProcessorFactory _genericProcessorFactory;

[Obsolete(
"The deserializer no longer depends on the IGenericProcessorFactory",
error: false)]
public JsonApiDeSerializer(
IJsonApiContext jsonApiContext,
IGenericProcessorFactory genericProcessorFactory)
{
_jsonApiContext = jsonApiContext;
_genericProcessorFactory = genericProcessorFactory;
}

public JsonApiDeSerializer(IJsonApiContext jsonApiContext)
{
_jsonApiContext = jsonApiContext;
}

public object Deserialize(string requestBody)
Expand Down Expand Up @@ -200,20 +207,25 @@ private object SetHasOneRelationship(object entity,

var rio = (ResourceIdentifierObject)relationshipData.ExposedData;

if (rio == null) return entity;

var newValue = rio.Id;

var foreignKey = attr.IdentifiablePropertyName;
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == foreignKey);
if (entityProperty == null)
if (entityProperty == null && rio != null)
throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain a foreign key property '{foreignKey}' for has one relationship '{attr.InternalRelationshipName}'");

var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType);
if (entityProperty != null)
{
// e.g. PATCH /articles
// {... { "relationships":{ "Owner": { "data" :null } } } }
if (rio == null && Nullable.GetUnderlyingType(entityProperty.PropertyType) == null)
throw new JsonApiException(400, $"Cannot set required relationship identifier '{attr.IdentifiablePropertyName}' to null.");

_jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue;
var newValue = rio?.Id ?? null;
var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType);

entityProperty.SetValue(entity, convertedValue);
_jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue;

entityProperty.SetValue(entity, convertedValue);
}
}

return entity;
Expand All @@ -225,11 +237,6 @@ private object SetHasManyRelationship(object entity,
ContextEntity contextEntity,
Dictionary<string, RelationshipData> relationships)
{
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName);

if (entityProperty == null)
throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}");

var relationshipName = attr.PublicRelationshipName;

if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData))
Expand All @@ -238,11 +245,18 @@ private object SetHasManyRelationship(object entity,

if (data == null) return entity;

var genericProcessor = _genericProcessorFactory.GetProcessor<IGenericProcessor>(typeof(GenericProcessor<>), attr.Type);
var relationshipShells = relationshipData.ManyData.Select(r =>
{
var instance = attr.Type.New<IIdentifiable>();
instance.StringId = r.Id;
return instance;
});

var convertedCollection = TypeHelper.ConvertCollection(relationshipShells, attr.Type);

var ids = relationshipData.ManyData.Select(r => r.Id);
attr.SetValue(entity, convertedCollection);

genericProcessor.SetRelationships(entity, attr, ids);
_jsonApiContext.HasManyRelationshipPointers.Add(attr.Type, convertedCollection);
}

return entity;
Expand Down
7 changes: 2 additions & 5 deletions src/JsonApiDotNetCore/Services/EntityResourceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,7 @@ private async Task<T> GetWithRelationshipsAsync(TId id)
}

public virtual async Task<object> GetRelationshipsAsync(TId id, string relationshipName)
{
_jsonApiContext.IsRelationshipData = true;
return await GetRelationshipAsync(id, relationshipName);
}
=> await GetRelationshipAsync(id, relationshipName);

public virtual async Task<object> GetRelationshipAsync(TId id, string relationshipName)
{
Expand Down Expand Up @@ -136,7 +133,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa
.Relationships
.FirstOrDefault(r => r.InternalRelationshipName == relationshipName);

var relationshipIds = relationships.Select(r => r.Id?.ToString());
var relationshipIds = relationships.Select(r => r?.Id?.ToString());

await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds);
}
Expand Down
Loading