Skip to content

Missing spec tests #43

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 10 commits into from
Mar 1, 2017
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ public class Person : Identifiable<Guid>
{ }
```

You can use the non-generic `Identifiable` if your primary key is an integer:

```csharp
public class Person : Identifiable
{ }
```

If you need to hang annotations or attributes on the `Id` property, you can override the virtual member:

```csharp
public class Person : Identifiable
{
[Key]
[Column("person_id")]
public override int Id { get; set; }
}
```

#### Specifying Public Attributes

If you want an attribute on your model to be publicly available,
Expand Down
39 changes: 37 additions & 2 deletions src/JsonApiDotNetCore/Controllers/JsonApiController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -139,9 +138,13 @@ public virtual async Task<IActionResult> PostAsync([FromBody] T entity)
return UnprocessableEntity();
}

var stringId = entity.Id.ToString();
if(stringId.Length > 0 && stringId != "0")
return Forbidden();

await _entities.CreateAsync(entity);

return Created(HttpContext.Request.Path, entity);
return Created($"{HttpContext.Request.Path}/{entity.Id}", entity);
}

[HttpPatch("{id}")]
Expand All @@ -155,9 +158,41 @@ public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] T entity)

var updatedEntity = await _entities.UpdateAsync(id, entity);

if(updatedEntity == null) return NotFound();

return Ok(updatedEntity);
}

[HttpPatch("{id}/relationships/{relationshipName}")]
public virtual async Task<IActionResult> PatchRelationshipsAsync(TId id, string relationshipName, [FromBody] List<DocumentData> relationships)
{
relationshipName = _jsonApiContext.ContextGraph
.GetRelationshipName<T>(relationshipName.ToProperCase());

if (relationshipName == null)
{
_logger?.LogInformation($"Relationship name not specified returning 422");
return UnprocessableEntity();
}

var entity = await _entities.GetAndIncludeAsync(id, relationshipName);

if (entity == null)
return NotFound();

var relationship = _jsonApiContext.ContextGraph
.GetContextEntity(typeof(T))
.Relationships
.FirstOrDefault(r => r.RelationshipName == relationshipName);

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

await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds);

return Ok();

}

[HttpDelete("{id}")]
public virtual async Task<IActionResult> DeleteAsync(TId id)
{
Expand Down
5 changes: 5 additions & 0 deletions src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ protected IActionResult UnprocessableEntity()
{
return new StatusCodeResult(422);
}

protected IActionResult Forbidden()
{
return new StatusCodeResult(403);
}
}
}
8 changes: 7 additions & 1 deletion src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)

await _context.SaveChangesAsync();

return oldEntity;
return oldEntity;
}

public async Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds)
{
var genericProcessor = GenericProcessorFactory.GetProcessor(relationship.BaseType, _context);
await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds);
}

public virtual async Task<bool> DeleteAsync(TId id)
Expand Down
3 changes: 3 additions & 0 deletions src/JsonApiDotNetCore/Data/IEntityRepository.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Query;
using JsonApiDotNetCore.Models;

Expand Down Expand Up @@ -33,6 +34,8 @@ public interface IEntityRepository<TEntity, in TId>

Task<TEntity> UpdateAsync(TId id, TEntity entity);

Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds);

Task<bool> DeleteAsync(TId id);
}
}
4 changes: 3 additions & 1 deletion src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
var body = GetRequestBody(context.HttpContext.Request.Body);
var jsonApiContext = GetService<IJsonApiContext>(context);
var model = JsonApiDeSerializer.Deserialize(body, jsonApiContext);
var model = jsonApiContext.IsRelationshipPath ?
JsonApiDeSerializer.DeserializeRelationship(body, jsonApiContext) :
JsonApiDeSerializer.Deserialize(body, jsonApiContext);

if(model == null)
logger?.LogError("An error occurred while de-serializing the payload");
Expand Down
39 changes: 39 additions & 0 deletions src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Models;
using Microsoft.EntityFrameworkCore;

namespace JsonApiDotNetCore.Internal
{
public class GenericProcessor<T> : IGenericProcessor where T : class, IIdentifiable
{
private readonly DbContext _context;
public GenericProcessor(DbContext context)
{
_context = context;
}

public async Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds)
{
var relationshipType = relationship.BaseType;

// TODO: replace with relationship.IsMany
if(relationship.Type.GetInterfaces().Contains(typeof(IEnumerable)))
{
var entities = _context.GetDbSet<T>().Where(x => relationshipIds.Contains(x.Id.ToString())).ToList();
relationship.SetValue(parent, entities);
}
else
{
var entity = _context.GetDbSet<T>().SingleOrDefault(x => relationshipIds.First() == x.Id.ToString());
relationship.SetValue(parent, entity);
}

await _context.SaveChangesAsync();
}
}
}
19 changes: 19 additions & 0 deletions src/JsonApiDotNetCore/Internal/Generics/GenericProcessorFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using Microsoft.EntityFrameworkCore;

namespace JsonApiDotNetCore.Internal
{
/// <summary>
/// Used to generate a generic operations processor when the types
/// are not know until runtime. The typical use case would be for
/// accessing relationship data.
/// </summary>
public static class GenericProcessorFactory
{
public static IGenericProcessor GetProcessor(Type type, DbContext dbContext)
{
var repositoryType = typeof(GenericProcessor<>).MakeGenericType(type);
return (IGenericProcessor)Activator.CreateInstance(repositoryType, dbContext);
}
}
}
10 changes: 10 additions & 0 deletions src/JsonApiDotNetCore/Internal/Generics/IGenericProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace JsonApiDotNetCore.Internal
{
public interface IGenericProcessor
{
Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds);
}
}
22 changes: 22 additions & 0 deletions src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Linq;

namespace JsonApiDotNetCore.Internal
{
public static class JsonApiExceptionFactory
{
public static JsonApiException GetException(Exception exception)
{
var exceptionType = exception.GetType().ToString().Split('.').Last();
switch(exceptionType)
{
case "JsonApiException":
return (JsonApiException)exception;
case "InvalidCastException":
return new JsonApiException("409", exception.Message);
default:
return new JsonApiException("500", exception.Message);
}
}
}
}
20 changes: 20 additions & 0 deletions src/JsonApiDotNetCore/Internal/Relationship.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonApiDotNetCore.Extensions;

namespace JsonApiDotNetCore.Internal
{
public class Relationship
{
public Type Type { get; set; }
public Type BaseType { get {
return (Type.GetInterfaces().Contains(typeof(IEnumerable))) ?
Type.GenericTypeArguments[0] :
Type;
} }

public string RelationshipName { get; set; }

public void SetValue(object entity, object newValue)
{
var propertyInfo = entity
.GetType()
.GetProperty(RelationshipName);

propertyInfo.SetValue(entity, newValue);
}
}
}
5 changes: 1 addition & 4 deletions src/JsonApiDotNetCore/Middleware/JsonApiExceptionFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ public void OnException(ExceptionContext context)
{
_logger?.LogError(new EventId(), context.Exception, "An unhandled exception occurred during the request");

var jsonApiException = context.Exception as JsonApiException;
var jsonApiException = JsonApiExceptionFactory.GetException(context.Exception);

if(jsonApiException == null)
jsonApiException = new JsonApiException("500", context.Exception.Message);

var error = jsonApiException.GetError();
var result = new ObjectResult(error);
result.StatusCode = Convert.ToInt16(error.Status);
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Models/Identifiable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public class Identifiable : Identifiable<int>

public class Identifiable<T> : IIdentifiable<T>, IIdentifiable
{
public T Id { get; set; }
public virtual T Id { get; set; }

object IIdentifiable.Id
{
Expand Down
14 changes: 12 additions & 2 deletions src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonApiDotNetCore.Serialization
{
Expand All @@ -15,12 +16,21 @@ public static class JsonApiDeSerializer
public static object Deserialize(string requestBody, IJsonApiContext context)
{
var document = JsonConvert.DeserializeObject<Document>(requestBody);

var entity = DataToObject(document.Data, context);

return entity;
}

public static object DeserializeRelationship(string requestBody, IJsonApiContext context)
{
var data = JToken.Parse(requestBody)["data"];

if(data is JArray)
return data.ToObject<List<DocumentData>>();

return new List<DocumentData> { data.ToObject<DocumentData>() };
}


public static List<TEntity> DeserializeList<TEntity>(string requestBody, IJsonApiContext context)
{
var documents = JsonConvert.DeserializeObject<Documents>(requestBody);
Expand Down
2 changes: 2 additions & 0 deletions src/JsonApiDotNetCore/Services/IJsonApiContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface IJsonApiContext
QuerySet QuerySet { get; set; }
bool IsRelationshipData { get; set; }
List<string> IncludedRelationships { get; set; }
bool IsRelationshipPath { get; }
PageManager PageManager { get; set; }

}
}
4 changes: 3 additions & 1 deletion src/JsonApiDotNetCore/Services/JsonApiContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ public JsonApiContext(
public string BasePath { get; set; }
public QuerySet QuerySet { get; set; }
public bool IsRelationshipData { get; set; }
public bool IsRelationshipPath { get; private set; }
public List<string> IncludedRelationships { get; set; }
public PageManager PageManager { get; set; }

public IJsonApiContext ApplyContext<T>()
{
var context = _httpContextAccessor.HttpContext;
var path = context.Request.Path.Value.Split('/');

RequestEntity = ContextGraph.GetContextEntity(typeof(T));

Expand All @@ -45,7 +47,7 @@ public IJsonApiContext ApplyContext<T>()
var linkBuilder = new LinkBuilder(this);
BasePath = linkBuilder.GetBasePath(context, RequestEntity.EntityName);
PageManager = GetPageManager();

IsRelationshipPath = path[path.Length - 2] == "relationships";
return this;
}

Expand Down
Loading