diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..9f911ba396 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +\.vs/ diff --git a/Build.ps1 b/Build.ps1 index 8a1bd3ca60..4934457d3d 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -4,5 +4,14 @@ $revision = "{0:D4}" -f [convert]::ToInt32($revision, 10) dotnet restore .\src\JsonApiDotNetCore\JsonApiDotNetCore.csproj dotnet build .\src\JsonApiDotNetCore -c Release -If($env:APPVEYOR_REPO_TAG) { dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts } -Else { dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision } +echo "APPVEYOR_REPO_TAG: $env:APPVEYOR_REPO_TAG" +echo "VERSION-SUFFIX: alpha1-$revision" + +If($env:APPVEYOR_REPO_TAG -eq $true) { + echo "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts " + dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts +} +Else { + echo "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision" + dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision +} diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln new file mode 100644 index 0000000000..a9640c1142 --- /dev/null +++ b/JsonApiDotnetCore.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{C0EC9E70-EB2E-436F-9D94-FA16FA774123}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreExample", "src\JsonApiDotNetCoreExample\JsonApiDotNetCoreExample.csproj", "{97EE048B-16C0-43F6-BDA9-4E762B2F579F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreExampleTests", "test\JsonApiDotNetCoreExampleTests\JsonApiDotNetCoreExampleTests.csproj", "{0B959765-40D2-43B5-87EE-FE2FEF9DBED5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C5B4D998-CECB-454D-9F32-085A897577BE}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|Any CPU.Build.0 = Release|Any CPU + {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|Any CPU.Build.0 = Release|Any CPU + {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C0EC9E70-EB2E-436F-9D94-FA16FA774123} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} + {97EE048B-16C0-43F6-BDA9-4E762B2F579F} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} + {0B959765-40D2-43B5-87EE-FE2FEF9DBED5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + EndGlobalSection +EndGlobal diff --git a/build.sh b/build.sh index 7fe8f58e62..6efa9b472f 100755 --- a/build.sh +++ b/build.sh @@ -3,19 +3,8 @@ #exit if any command fails set -e -artifactsFolder="./artifacts" - -if [ -d $artifactsFolder ]; then - rm -R $artifactsFolder -fi - dotnet restore ./src/JsonApiDotNetCore/JsonApiDotNetCore.csproj dotnet restore ./src/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj dotnet restore ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj -dotnet test ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj - -revision=${TRAVIS_JOB_ID:=1} -revision=$(printf "%04d" $revision) - -dotnet pack ./src/JsonApiDotNetCore -c Release -o ./artifacts --version-suffix=$revision \ No newline at end of file +dotnet test ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 284b2184c5..2df9eaca4f 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -94,7 +94,7 @@ private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity) var data = new DocumentData { Type = contextEntity.EntityName, - Id = entity.Id.ToString() + Id = entity.StringId }; if (_jsonApiContext.IsRelationshipData) @@ -124,8 +124,8 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I { Links = new Links { - Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.InternalRelationshipName), - Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.InternalRelationshipName) + Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, r.InternalRelationshipName), + Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, r.InternalRelationshipName) } }; @@ -175,7 +175,7 @@ private DocumentData _getIncludedEntity(IIdentifiable entity) var data = new DocumentData { Type = contextEntity.EntityName, - Id = entity.Id.ToString() + Id = entity.StringId }; data.Attributes = new Dictionary(); @@ -205,7 +205,7 @@ private List> _getRelationships(IEnumerable e { relationships.Add(new Dictionary { {"type", typeName.EntityName.Dasherize() }, - {"id", ((IIdentifiable)entity).Id.ToString() } + {"id", ((IIdentifiable)entity).StringId } }); } return relationships; @@ -218,7 +218,7 @@ private Dictionary _getRelationship(object entity, string relati return new Dictionary { {"type", typeName.EntityName.Dasherize() }, - {"id", ((IIdentifiable)entity).Id.ToString() } + {"id", ((IIdentifiable)entity).StringId } }; } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 31a30bb454..ee08975a2d 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -13,7 +13,7 @@ namespace JsonApiDotNetCore.Controllers { - public class JsonApiController + public class JsonApiController : JsonApiController where T : class, IIdentifiable { public JsonApiController( @@ -24,7 +24,7 @@ public JsonApiController( { } } - public class JsonApiController + public class JsonApiController : JsonApiControllerMixin where T : class, IIdentifiable { private readonly IEntityRepository _entities; @@ -77,7 +77,7 @@ public virtual async Task GetAsync() public virtual async Task GetAsync(TId id) { T entity; - if(_jsonApiContext.QuerySet?.IncludedRelationships != null) + if (_jsonApiContext.QuerySet?.IncludedRelationships != null) entity = await _getWithRelationshipsAsync(id); else entity = await _entities.GetAsync(id); @@ -138,8 +138,7 @@ public virtual async Task PostAsync([FromBody] T entity) return UnprocessableEntity(); } - var stringId = entity.Id.ToString(); - if(stringId.Length > 0 && stringId != "0") + if (!string.IsNullOrEmpty(entity.StringId)) return Forbidden(); await _entities.CreateAsync(entity); @@ -158,7 +157,7 @@ public virtual async Task PatchAsync(TId id, [FromBody] T entity) var updatedEntity = await _entities.UpdateAsync(id, entity); - if(updatedEntity == null) return NotFound(); + if (updatedEntity == null) return NotFound(); return Ok(updatedEntity); } @@ -185,12 +184,12 @@ public virtual async Task PatchRelationshipsAsync(TId id, string .Relationships .FirstOrDefault(r => r.InternalRelationshipName == relationshipName); - var relationshipIds = relationships.Select(r=>r.Id); - + var relationshipIds = relationships.Select(r => r.Id); + await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds); return Ok(); - + } [HttpDelete("{id}")] @@ -208,14 +207,14 @@ private IQueryable ApplySortAndFilterQuery(IQueryable entities) { var query = _jsonApiContext.QuerySet; - if(_jsonApiContext.QuerySet == null) + if (_jsonApiContext.QuerySet == null) return entities; - if(query.Filters.Count > 0) - foreach(var filter in query.Filters) + if (query.Filters.Count > 0) + foreach (var filter in query.Filters) entities = _entities.Filter(entities, filter); - if(query.SortParameters != null && query.SortParameters.Count > 0) + if (query.SortParameters != null && query.SortParameters.Count > 0) entities = _entities.Sort(entities, query.SortParameters); return entities; @@ -224,7 +223,7 @@ private IQueryable ApplySortAndFilterQuery(IQueryable entities) private async Task> ApplyPageQueryAsync(IQueryable entities) { var pageManager = _jsonApiContext.PageManager; - if(!pageManager.IsPaginated) + if (!pageManager.IsPaginated) return entities; var query = _jsonApiContext.QuerySet?.PageQuery ?? new PageQuery(); @@ -238,7 +237,7 @@ private IQueryable IncludeRelationships(IQueryable entities, List { _jsonApiContext.IncludedRelationships = relationships; - foreach(var r in relationships) + foreach (var r in relationships) entities = _entities.Include(entities, r.ToProperCase()); return entities; diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs index 856ef4211d..c4f1692eaa 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Microsoft.EntityFrameworkCore; namespace JsonApiDotNetCore.Formatters { @@ -37,13 +38,15 @@ public Task ReadAsync(InputFormatterContext context) var loggerFactory = GetService(context); var logger = loggerFactory?.CreateLogger(); + var dbContext = GetService(context); + try { var body = GetRequestBody(context.HttpContext.Request.Body); var jsonApiContext = GetService(context); var model = jsonApiContext.IsRelationshipPath ? JsonApiDeSerializer.DeserializeRelationship(body, jsonApiContext) : - JsonApiDeSerializer.Deserialize(body, jsonApiContext); + JsonApiDeSerializer.Deserialize(body, jsonApiContext, dbContext); if(model == null) logger?.LogError("An error occurred while de-serializing the payload"); diff --git a/src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs b/src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs index 5fa1857b94..6958ada265 100644 --- a/src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs +++ b/src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs @@ -1,7 +1,5 @@ -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Models; @@ -18,21 +16,26 @@ public GenericProcessor(DbContext context) } public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds) + { + SetRelationships(parent, relationship, relationshipIds); + + await _context.SaveChangesAsync(); + } + + public void SetRelationships(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds) { var relationshipType = relationship.Type; - if(relationship.IsHasMany) + if (relationship.IsHasMany) { - var entities = _context.GetDbSet().Where(x => relationshipIds.Contains(x.Id.ToString())).ToList(); + var entities = _context.GetDbSet().Where(x => relationshipIds.Contains(x.StringId)).ToList(); relationship.SetValue(parent, entities); } else { - var entity = _context.GetDbSet().SingleOrDefault(x => relationshipIds.First() == x.Id.ToString()); + var entity = _context.GetDbSet().SingleOrDefault(x => relationshipIds.First() == x.StringId); relationship.SetValue(parent, entity); - } - - await _context.SaveChangesAsync(); + } } } } diff --git a/src/JsonApiDotNetCore/Internal/Generics/IGenericProcessor.cs b/src/JsonApiDotNetCore/Internal/Generics/IGenericProcessor.cs index 313db15dc1..27678acf82 100644 --- a/src/JsonApiDotNetCore/Internal/Generics/IGenericProcessor.cs +++ b/src/JsonApiDotNetCore/Internal/Generics/IGenericProcessor.cs @@ -7,5 +7,6 @@ namespace JsonApiDotNetCore.Internal public interface IGenericProcessor { Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds); + void SetRelationships(object parent, RelationshipAttribute relationship, IEnumerable relationshipIds); } } diff --git a/src/JsonApiDotNetCore/Internal/TypeHelper.cs b/src/JsonApiDotNetCore/Internal/TypeHelper.cs new file mode 100644 index 0000000000..3876ae5367 --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/TypeHelper.cs @@ -0,0 +1,24 @@ +using System; +using System.Reflection; + +namespace JsonApiDotNetCore.Internal +{ + public static class TypeHelper + { + public static object ConvertType(object value, Type type) + { + if(value == null) + return null; + + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) + type = Nullable.GetUnderlyingType(type); + + var stringValue = value.ToString(); + + if(type == typeof(Guid)) + return Guid.Parse(stringValue); + + return Convert.ChangeType(stringValue, type); + } + } +} diff --git a/src/JsonApiDotNetCore/Models/IIdentifiable.cs b/src/JsonApiDotNetCore/Models/IIdentifiable.cs index daa25cd799..93d7f859da 100644 --- a/src/JsonApiDotNetCore/Models/IIdentifiable.cs +++ b/src/JsonApiDotNetCore/Models/IIdentifiable.cs @@ -2,11 +2,11 @@ namespace JsonApiDotNetCore.Models { public interface IIdentifiable { - object Id { get; set; } + string StringId { get; set; } } public interface IIdentifiable : IIdentifiable { - new T Id { get; set; } + T Id { get; set; } } } diff --git a/src/JsonApiDotNetCore/Models/Identifiable.cs b/src/JsonApiDotNetCore/Models/Identifiable.cs index ead65bef28..c470656e63 100644 --- a/src/JsonApiDotNetCore/Models/Identifiable.cs +++ b/src/JsonApiDotNetCore/Models/Identifiable.cs @@ -1,3 +1,7 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using JsonApiDotNetCore.Internal; + namespace JsonApiDotNetCore.Models { public class Identifiable : Identifiable @@ -7,10 +11,32 @@ public class Identifiable : IIdentifiable, IIdentifiable { public virtual T Id { get; set; } - object IIdentifiable.Id + [NotMapped] + public string StringId + { + get { return GetStringId(Id); } + set { Id = (T)GetConcreteId(value); } + } + + protected virtual string GetStringId(object value) + { + var type = typeof(T); + var stringValue = value.ToString(); + + if(type == typeof(Guid)) + { + var guid = Guid.Parse(stringValue); + return (guid == Guid.Empty ? string.Empty : stringValue); + } + + if(stringValue == "0") return string.Empty; + + return stringValue; + } + + protected virtual object GetConcreteId(string value) { - get { return Id; } - set { Id = (T)value; } + return TypeHelper.ConvertType(value, typeof(T)); } } } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index ad7e7cba18..59772d9a76 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -8,15 +8,19 @@ using JsonApiDotNetCore.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.Collections; +using JsonApiDotNetCore.Data; +using Microsoft.EntityFrameworkCore; namespace JsonApiDotNetCore.Serialization { public static class JsonApiDeSerializer { - public static object Deserialize(string requestBody, IJsonApiContext context) + public static object Deserialize(string requestBody, IJsonApiContext context, + DbContext dbContext) { var document = JsonConvert.DeserializeObject(requestBody); - var entity = DataToObject(document.Data, context); + var entity = DataToObject(document.Data, context, dbContext); return entity; } @@ -31,21 +35,24 @@ public static object DeserializeRelationship(string requestBody, IJsonApiContext } - public static List DeserializeList(string requestBody, IJsonApiContext context) + public static List DeserializeList(string requestBody, IJsonApiContext context, + DbContext dbContext) { var documents = JsonConvert.DeserializeObject(requestBody); var deserializedList = new List(); foreach (var data in documents.Data) { - var entity = DataToObject(data, context); + var entity = DataToObject(data, context, dbContext); deserializedList.Add((TEntity)entity); } return deserializedList; } - private static object DataToObject(DocumentData data, IJsonApiContext context) + private static object DataToObject(DocumentData data, + IJsonApiContext context, + DbContext dbContext) { var entityTypeName = data.Type.ToProperCase(); @@ -53,14 +60,14 @@ private static object DataToObject(DocumentData data, IJsonApiContext context) context.RequestEntity = contextEntity; var entity = Activator.CreateInstance(contextEntity.EntityType); - + entity = _setEntityAttributes(entity, contextEntity, data.Attributes); - entity = _setRelationships(entity, contextEntity, data.Relationships); + entity = _setRelationships(entity, contextEntity, data.Relationships, dbContext); var identifiableEntity = (IIdentifiable)entity; if (data.Id != null) - identifiableEntity.Id = ChangeType(data.Id, identifiableEntity.Id.GetType()); + identifiableEntity.StringId = data.Id; return identifiableEntity; } @@ -68,6 +75,9 @@ private static object DataToObject(DocumentData data, IJsonApiContext context) private static object _setEntityAttributes( object entity, ContextEntity contextEntity, Dictionary attributeValues) { + if (attributeValues == null || attributeValues.Count == 0) + return entity; + var entityProperties = entity.GetType().GetProperties(); foreach (var attr in contextEntity.Attributes) @@ -80,7 +90,7 @@ private static object _setEntityAttributes( object newValue; if (attributeValues.TryGetValue(attr.PublicAttributeName.Dasherize(), out newValue)) { - var convertedValue = ChangeType(newValue, entityProperty.PropertyType); + var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType); entityProperty.SetValue(entity, convertedValue); } } @@ -89,7 +99,10 @@ private static object _setEntityAttributes( } private static object _setRelationships( - object entity, ContextEntity contextEntity, Dictionary relationships) + object entity, + ContextEntity contextEntity, + Dictionary relationships, + DbContext context) { if (relationships == null || relationships.Count == 0) return entity; @@ -98,41 +111,68 @@ private static object _setRelationships( foreach (var attr in contextEntity.Relationships) { - var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.InternalRelationshipName}Id"); + if (attr.IsHasOne) + entity = _setHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships); + else + entity = _setHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships, context); + } - if (entityProperty == null) - throw new JsonApiException("400", $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); + return entity; + } - var relationshipName = attr.InternalRelationshipName.Dasherize(); - RelationshipData relationshipData; - if (relationships.TryGetValue(relationshipName, out relationshipData)) - { - var data = (Dictionary)relationshipData.ExposedData; + private static object _setHasOneRelationship(object entity, + PropertyInfo[] entityProperties, + RelationshipAttribute attr, + ContextEntity contextEntity, + Dictionary relationships) + { + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.InternalRelationshipName}Id"); - if (data == null) continue; + if (entityProperty == null) + throw new JsonApiException("400", $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); - var newValue = data["id"]; - var convertedValue = ChangeType(newValue, entityProperty.PropertyType); - entityProperty.SetValue(entity, convertedValue); - } + var relationshipName = attr.InternalRelationshipName.Dasherize(); + + if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) + { + var data = (Dictionary)relationshipData.ExposedData; + + if (data == null) return entity; + + var newValue = data["id"]; + var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType); + entityProperty.SetValue(entity, convertedValue); } return entity; } - private static object ChangeType(object value, Type conversion) + private static object _setHasManyRelationship(object entity, + PropertyInfo[] entityProperties, + RelationshipAttribute attr, + ContextEntity contextEntity, + Dictionary relationships, + DbContext context) { - var t = conversion; + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName); - if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) + if (entityProperty == null) + throw new JsonApiException("400", $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); + + var relationshipName = attr.InternalRelationshipName.Dasherize(); + + if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) { - if (value == null) - return null; + var data = (List>)relationshipData.ExposedData; - t = Nullable.GetUnderlyingType(t); + if (data == null) return entity; + + var genericProcessor = GenericProcessorFactory.GetProcessor(attr.Type, context); + var ids = relationshipData.ManyData.Select(r => r["id"]); + genericProcessor.SetRelationships(entity, attr, ids); } - return Convert.ChangeType(value, t); + return entity; } } } diff --git a/src/JsonApiDotNetCoreExample/Controllers/TodoItemCollectionsController.cs b/src/JsonApiDotNetCoreExample/Controllers/TodoItemCollectionsController.cs index c2dbb48a51..e596e125c3 100644 --- a/src/JsonApiDotNetCoreExample/Controllers/TodoItemCollectionsController.cs +++ b/src/JsonApiDotNetCoreExample/Controllers/TodoItemCollectionsController.cs @@ -1,3 +1,4 @@ +using System; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Services; @@ -6,11 +7,11 @@ namespace JsonApiDotNetCoreExample.Controllers { - public class TodoItemCollectionsController : JsonApiController + public class TodoItemCollectionsController : JsonApiController { public TodoItemCollectionsController( IJsonApiContext jsonApiContext, - IEntityRepository entityRepository, + IEntityRepository entityRepository, ILoggerFactory loggerFactory) : base(jsonApiContext, entityRepository, loggerFactory) { } diff --git a/src/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/JsonApiDotNetCoreExample/Data/AppDbContext.cs index 46b29924f8..7406a11065 100644 --- a/src/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -11,6 +11,6 @@ public AppDbContext(DbContextOptions options) public DbSet TodoItems { get; set; } public DbSet People { get; set; } - public DbSet TodoItemCollection { get; set; } + public DbSet TodoItemCollections { get; set; } } } diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170117003343_Initial.Designer.cs b/src/JsonApiDotNetCoreExample/Migrations/20170117003343_Initial.Designer.cs deleted file mode 100755 index 50597ef0db..0000000000 --- a/src/JsonApiDotNetCoreExample/Migrations/20170117003343_Initial.Designer.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using JsonApiDotNetCoreExample.Data; - -namespace JsonApiDotNetCoreExample.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20170117003343_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) - .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("FirstName"); - - b.Property("LastName"); - - b.HasKey("Id"); - - b.ToTable("People"); - }); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Description"); - - b.Property("OwnerId"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("TodoItems"); - }); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => - { - b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner") - .WithMany("TodoItems") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade); - }); - } - } -} diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170131150223_AddOrdinalToTodoItems.Designer.cs b/src/JsonApiDotNetCoreExample/Migrations/20170131150223_AddOrdinalToTodoItems.Designer.cs deleted file mode 100755 index 21b868430a..0000000000 --- a/src/JsonApiDotNetCoreExample/Migrations/20170131150223_AddOrdinalToTodoItems.Designer.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using JsonApiDotNetCoreExample.Data; - -namespace JsonApiDotNetCoreExample.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20170131150223_AddOrdinalToTodoItems")] - partial class AddOrdinalToTodoItems - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) - .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("FirstName"); - - b.Property("LastName"); - - b.HasKey("Id"); - - b.ToTable("People"); - }); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Description"); - - b.Property("Ordinal"); - - b.Property("OwnerId"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("TodoItems"); - }); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => - { - b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner") - .WithMany("TodoItems") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade); - }); - } - } -} diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170131150223_AddOrdinalToTodoItems.cs b/src/JsonApiDotNetCoreExample/Migrations/20170131150223_AddOrdinalToTodoItems.cs deleted file mode 100755 index 9c9fee88d8..0000000000 --- a/src/JsonApiDotNetCoreExample/Migrations/20170131150223_AddOrdinalToTodoItems.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace JsonApiDotNetCoreExample.Migrations -{ - public partial class AddOrdinalToTodoItems : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Ordinal", - table: "TodoItems", - nullable: false, - defaultValue: 0L); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Ordinal", - table: "TodoItems"); - } - } -} diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170228175630_MakeOwnerOptionalOnTodoItems.Designer.cs b/src/JsonApiDotNetCoreExample/Migrations/20170228175630_MakeOwnerOptionalOnTodoItems.Designer.cs deleted file mode 100755 index 504ec8f1d3..0000000000 --- a/src/JsonApiDotNetCoreExample/Migrations/20170228175630_MakeOwnerOptionalOnTodoItems.Designer.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using JsonApiDotNetCoreExample.Data; - -namespace JsonApiDotNetCoreExample.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20170228175630_MakeOwnerOptionalOnTodoItems")] - partial class MakeOwnerOptionalOnTodoItems - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) - .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("FirstName"); - - b.Property("LastName"); - - b.HasKey("Id"); - - b.ToTable("People"); - }); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Description"); - - b.Property("Ordinal"); - - b.Property("OwnerId"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("TodoItems"); - }); - - modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => - { - b.HasOne("JsonApiDotNetCoreExample.Models.Person", "Owner") - .WithMany("TodoItems") - .HasForeignKey("OwnerId"); - }); - } - } -} diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170228175630_MakeOwnerOptionalOnTodoItems.cs b/src/JsonApiDotNetCoreExample/Migrations/20170228175630_MakeOwnerOptionalOnTodoItems.cs deleted file mode 100755 index 96f199d56b..0000000000 --- a/src/JsonApiDotNetCoreExample/Migrations/20170228175630_MakeOwnerOptionalOnTodoItems.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace JsonApiDotNetCoreExample.Migrations -{ - public partial class MakeOwnerOptionalOnTodoItems : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_TodoItems_People_OwnerId", - table: "TodoItems"); - - migrationBuilder.AlterColumn( - name: "OwnerId", - table: "TodoItems", - nullable: true); - - migrationBuilder.AddForeignKey( - name: "FK_TodoItems_People_OwnerId", - table: "TodoItems", - column: "OwnerId", - principalTable: "People", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_TodoItems_People_OwnerId", - table: "TodoItems"); - - migrationBuilder.AlterColumn( - name: "OwnerId", - table: "TodoItems", - nullable: false); - - migrationBuilder.AddForeignKey( - name: "FK_TodoItems_People_OwnerId", - table: "TodoItems", - column: "OwnerId", - principalTable: "People", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170228193414_AddTodoItemCollection.cs b/src/JsonApiDotNetCoreExample/Migrations/20170228193414_AddTodoItemCollection.cs deleted file mode 100755 index 3262a40f28..0000000000 --- a/src/JsonApiDotNetCoreExample/Migrations/20170228193414_AddTodoItemCollection.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace JsonApiDotNetCoreExample.Migrations -{ - public partial class AddTodoItemCollection : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CollectionId", - table: "TodoItems", - nullable: true); - - migrationBuilder.CreateTable( - name: "TodoItemCollection", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn), - Name = table.Column(nullable: true), - OwnerId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoItemCollection", x => x.Id); - table.ForeignKey( - name: "FK_TodoItemCollection_People_OwnerId", - column: x => x.OwnerId, - principalTable: "People", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_TodoItems_CollectionId", - table: "TodoItems", - column: "CollectionId"); - - migrationBuilder.CreateIndex( - name: "IX_TodoItemCollection_OwnerId", - table: "TodoItemCollection", - column: "OwnerId"); - - migrationBuilder.AddForeignKey( - name: "FK_TodoItems_TodoItemCollection_CollectionId", - table: "TodoItems", - column: "CollectionId", - principalTable: "TodoItemCollection", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_TodoItems_TodoItemCollection_CollectionId", - table: "TodoItems"); - - migrationBuilder.DropTable( - name: "TodoItemCollection"); - - migrationBuilder.DropIndex( - name: "IX_TodoItems_CollectionId", - table: "TodoItems"); - - migrationBuilder.DropColumn( - name: "CollectionId", - table: "TodoItems"); - } - } -} diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170228193414_AddTodoItemCollection.Designer.cs b/src/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.Designer.cs old mode 100755 new mode 100644 similarity index 89% rename from src/JsonApiDotNetCoreExample/Migrations/20170228193414_AddTodoItemCollection.Designer.cs rename to src/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.Designer.cs index eb84318106..be31f0ccae --- a/src/JsonApiDotNetCoreExample/Migrations/20170228193414_AddTodoItemCollection.Designer.cs +++ b/src/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.Designer.cs @@ -8,14 +8,14 @@ namespace JsonApiDotNetCoreExample.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20170228193414_AddTodoItemCollection")] - partial class AddTodoItemCollection + [Migration("20170315140127_initial")] + partial class initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) - .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); + .HasAnnotation("ProductVersion", "1.1.1"); modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b => { @@ -36,7 +36,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Id") .ValueGeneratedOnAdd(); - b.Property("CollectionId"); + b.Property("CollectionId"); b.Property("Description"); @@ -55,7 +55,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); @@ -66,7 +66,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("OwnerId"); - b.ToTable("TodoItemCollection"); + b.ToTable("TodoItemCollections"); }); modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => diff --git a/src/JsonApiDotNetCoreExample/Migrations/20170117003343_Initial.cs b/src/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.cs old mode 100755 new mode 100644 similarity index 55% rename from src/JsonApiDotNetCoreExample/Migrations/20170117003343_Initial.cs rename to src/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.cs index e7ea77fb86..ce40d8016e --- a/src/JsonApiDotNetCoreExample/Migrations/20170117003343_Initial.cs +++ b/src/JsonApiDotNetCoreExample/Migrations/20170315140127_initial.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCoreExample.Migrations { - public partial class Initial : Migration + public partial class initial : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -23,30 +23,67 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_People", x => x.Id); }); + migrationBuilder.CreateTable( + name: "TodoItemCollections", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(nullable: true), + OwnerId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItemCollections", x => x.Id); + table.ForeignKey( + name: "FK_TodoItemCollections_People_OwnerId", + column: x => x.OwnerId, + principalTable: "People", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "TodoItems", columns: table => new { Id = table.Column(nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn), + CollectionId = table.Column(nullable: true), Description = table.Column(nullable: true), - OwnerId = table.Column(nullable: false) + Ordinal = table.Column(nullable: false), + OwnerId = table.Column(nullable: true) }, constraints: table => { table.PrimaryKey("PK_TodoItems", x => x.Id); + table.ForeignKey( + name: "FK_TodoItems_TodoItemCollections_CollectionId", + column: x => x.CollectionId, + principalTable: "TodoItemCollections", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); table.ForeignKey( name: "FK_TodoItems_People_OwnerId", column: x => x.OwnerId, principalTable: "People", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Restrict); }); + migrationBuilder.CreateIndex( + name: "IX_TodoItems_CollectionId", + table: "TodoItems", + column: "CollectionId"); + migrationBuilder.CreateIndex( name: "IX_TodoItems_OwnerId", table: "TodoItems", column: "OwnerId"); + + migrationBuilder.CreateIndex( + name: "IX_TodoItemCollections_OwnerId", + table: "TodoItemCollections", + column: "OwnerId"); } protected override void Down(MigrationBuilder migrationBuilder) @@ -54,6 +91,9 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropTable( name: "TodoItems"); + migrationBuilder.DropTable( + name: "TodoItemCollections"); + migrationBuilder.DropTable( name: "People"); } diff --git a/src/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs b/src/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs index 9fe2c5e9c2..58722dd4f7 100755 --- a/src/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs +++ b/src/JsonApiDotNetCoreExample/Migrations/AppDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) - .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); + .HasAnnotation("ProductVersion", "1.1.1"); modelBuilder.Entity("JsonApiDotNetCoreExample.Models.Person", b => { @@ -35,7 +35,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .ValueGeneratedOnAdd(); - b.Property("CollectionId"); + b.Property("CollectionId"); b.Property("Description"); @@ -54,7 +54,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItemCollection", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); @@ -65,7 +65,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OwnerId"); - b.ToTable("TodoItemCollection"); + b.ToTable("TodoItemCollections"); }); modelBuilder.Entity("JsonApiDotNetCoreExample.Models.TodoItem", b => diff --git a/src/JsonApiDotNetCoreExample/Models/TodoItem.cs b/src/JsonApiDotNetCoreExample/Models/TodoItem.cs index 5d706da5db..1c27c043d6 100644 --- a/src/JsonApiDotNetCoreExample/Models/TodoItem.cs +++ b/src/JsonApiDotNetCoreExample/Models/TodoItem.cs @@ -1,4 +1,4 @@ -using JsonApiDotNetCore.Internal; +using System; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCoreExample.Models @@ -12,7 +12,7 @@ public class TodoItem : Identifiable public long Ordinal { get; set; } public int? OwnerId { get; set; } - public int? CollectionId { get; set; } + public Guid? CollectionId { get; set; } [HasOne("owner")] public virtual Person Owner { get; set; } diff --git a/src/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs b/src/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs index c5b1dda453..95a523dff3 100644 --- a/src/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs +++ b/src/JsonApiDotNetCoreExample/Models/TodoItemCollection.cs @@ -1,9 +1,10 @@ +using System; using System.Collections.Generic; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCoreExample.Models { - public class TodoItemCollection : Identifiable + public class TodoItemCollection : Identifiable { [Attr("name")] public string Name { get; set; } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs index 3409e1824c..7c7b145830 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs @@ -10,14 +10,21 @@ using JsonApiDotNetCoreExampleTests.Services; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; +using DotNetCoreDocs; +using JsonApiDotNetCoreExample; +using DotNetCoreDocs.Writers; namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility { [Collection("WebHostCollection")] public class RepositoryOverrideTests { - public RepositoryOverrideTests() - { } + private DocsFixture _fixture; + + public RepositoryOverrideTests(DocsFixture fixture) + { + _fixture = fixture; + } [Fact] public async Task Total_Record_Count_Included() @@ -50,7 +57,7 @@ public async Task Total_Record_Count_Included() // act var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(responseBody, jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(responseBody, jsonApiContext, _fixture.GetService()); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index 027e0e29e2..4ab19e04d8 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -15,6 +14,9 @@ using Microsoft.AspNetCore.TestHost; using Newtonsoft.Json; using Xunit; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec { @@ -29,11 +31,57 @@ public CreatingDataTests(DocsFixture fixture) { _fixture = fixture; _jsonApiContext = fixture.GetService(); - _todoItemFaker = new Faker() + _todoItemFaker = new Faker() .RuleFor(t => t.Description, f => f.Lorem.Sentence()) .RuleFor(t => t.Ordinal, f => f.Random.Number()); } + [Fact] + public async Task Can_Create_Guid_Identifiable_Entities() + { + // arrange + var builder = new WebHostBuilder() + .UseStartup(); + var httpMethod = new HttpMethod("POST"); + var server = new TestServer(builder); + var client = server.CreateClient(); + + var context = _fixture.GetService(); + + var owner = new JsonApiDotNetCoreExample.Models.Person(); + context.People.Add(owner); + await context.SaveChangesAsync(); + + var route = "/api/v1/todo-item-collections"; + var request = new HttpRequestMessage(httpMethod, route); + var content = new + { + data = new + { + type = "todo-item-collections", + relationships = new + { + owner = new + { + data = new + { + type = "people", + id = owner.Id.ToString() + } + } + } + } + }; + request.Content = new StringContent(JsonConvert.SerializeObject(content)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + + // act + var response = await client.SendAsync(request); + + // assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + [Fact] public async Task Request_With_ClientGeneratedId_Returns_403() { @@ -69,6 +117,73 @@ public async Task Request_With_ClientGeneratedId_Returns_403() Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } + [Fact] + public async Task Can_Create_And_Set_HasMany_Relationships() + { + // arrange + var builder = new WebHostBuilder() + .UseStartup(); + var httpMethod = new HttpMethod("POST"); + var server = new TestServer(builder); + var client = server.CreateClient(); + + var context = _fixture.GetService(); + + var owner = new JsonApiDotNetCoreExample.Models.Person(); + var todoItem = new TodoItem(); + todoItem.Owner = owner; + context.People.Add(owner); + context.TodoItems.Add(todoItem); + await context.SaveChangesAsync(); + + var route = "/api/v1/todo-item-collections"; + var request = new HttpRequestMessage(httpMethod, route); + var content = new + { + data = new + { + type = "todo-item-collections", + relationships = new Dictionary + { + { "owner", new { + data = new + { + type = "people", + id = owner.Id.ToString() + } + } }, + { "todo-items", new { + data = new dynamic[] + { + new { + type = "todo-items", + id = todoItem.Id.ToString() + } + } + } } + } + } + }; + + request.Content = new StringContent(JsonConvert.SerializeObject(content)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json"); + + // act + var response = await client.SendAsync(request); + var body = await response.Content.ReadAsStringAsync(); + var deserializedBody = (TodoItemCollection)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, context); + var newId = deserializedBody.Id; + var contextCollection = context.TodoItemCollections + .Include(c=> c.Owner) + .Include(c => c.TodoItems) + .SingleOrDefault(c => c.Id == newId); + + // assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal(owner.Id, contextCollection.OwnerId); + Assert.NotEmpty(contextCollection.TodoItems); + } + [Fact] public async Task ShouldReceiveLocationHeader_InResponse() { @@ -99,7 +214,7 @@ public async Task ShouldReceiveLocationHeader_InResponse() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext); + var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs index 6a11cb459d..7c1339f86c 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs @@ -50,7 +50,7 @@ public async Task Request_ForEmptyCollection_Returns_EmptyDataCollection() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs index 1038b17146..0e9449ad86 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs @@ -56,7 +56,7 @@ public async Task Can_Get_TodoItems() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -85,7 +85,7 @@ public async Task Can_Paginate_TodoItems() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -114,7 +114,7 @@ public async Task Can_Filter_TodoItems() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -144,7 +144,7 @@ public async Task Can_Filter_TodoItems_Using_Like_Operator() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -182,7 +182,7 @@ public async Task Can_Sort_TodoItems_By_Ordinal_Ascending() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -224,7 +224,7 @@ public async Task Can_Sort_TodoItems_By_Ordinal_Descending() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext); + var deserializedBody = JsonApiDeSerializer.DeserializeList(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -258,7 +258,7 @@ public async Task Can_Get_TodoItem_ById() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext); + var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -288,7 +288,7 @@ public async Task Can_Get_TodoItem_WithOwner() // Act var response = await _fixture.MakeRequest(description, httpMethod, route); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext); + var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -343,7 +343,7 @@ public async Task Can_Post_TodoItem() // Act var response = await _fixture.MakeRequest(description, request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext); + var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -390,7 +390,7 @@ public async Task Can_Patch_TodoItem() // Act var response = await _fixture.MakeRequest(description, request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext); + var deserializedBody = (TodoItem)JsonApiDeSerializer.Deserialize(body, _jsonApiContext, _fixture.GetService()); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode);