Skip to content

Commit 0482a60

Browse files
committed
finish relational routing
1 parent e1c9508 commit 0482a60

File tree

6 files changed

+87
-34
lines changed

6 files changed

+87
-34
lines changed
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
using System;
2+
using System.Collections;
23
using System.ComponentModel;
34
using System.Linq;
45
using System.Linq.Expressions;
56
using System.Reflection;
67
using JsonApiDotNetCore.Attributes;
8+
using JsonApiDotNetCore.Extensions;
79

810
namespace JsonApiDotNetCore.Abstractions
911
{
1012
public static class ModelAccessor
1113
{
12-
public static Type GetTypeFromModelRelationshipName(object model, string relationshipName)
14+
public static Type GetTypeFromModelRelationshipName(Type modelType, string relationshipName)
1315
{
14-
return model.GetType().GetProperties().Where(propertyInfo => propertyInfo.GetMethod.IsVirtual).ToList().FirstOrDefault(
15-
virtualProperty => virtualProperty.Name == relationshipName)?.PropertyType;
16+
var properties = modelType.GetProperties().Where(propertyInfo => propertyInfo.GetMethod.IsVirtual).ToList();
17+
var relationshipType = properties.FirstOrDefault(
18+
virtualProperty => virtualProperty.Name.ToCamelCase() == relationshipName.ToCamelCase())?.PropertyType;
19+
if(relationshipType.GetTypeInfo().IsGenericType)
20+
{
21+
return relationshipType.GetGenericArguments().Single();
22+
}
23+
return relationshipType;
1624
}
17-
1825
}
1926
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using JsonApiDotNetCore.Abstractions;
6+
using System.Reflection;
7+
using JsonApiDotNetCore.Extensions;
8+
using JsonApiDotNetCore.Routing;
9+
using Microsoft.EntityFrameworkCore;
10+
11+
public class GenericDataAccess
12+
{
13+
public DbSet<T> GetDbSet<T>(DbContext context) where T : class
14+
{
15+
return context.Set<T>();
16+
}
17+
18+
public IQueryable<T> IncludeEntity<T>(IQueryable<T> queryable, string includedEntityName) where T : class
19+
{
20+
return queryable.Include(includedEntityName);
21+
}
22+
}

JsonApiDotNetCore/Data/ResourceRepository.cs

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,18 @@ public List<object> Get()
2626

2727
public object Get(string id)
2828
{
29-
var relationalRoute = _context.Route as RelationalRoute;
30-
if (relationalRoute == null)
29+
if (_context.Route is RelationalRoute)
3130
{
32-
return GetEntityById(_context.Route.BaseModelType, id, null);
31+
return GetRelated(id, _context.Route as RelationalRoute);
3332
}
34-
return GetRelated(id, relationalRoute);
33+
return GetEntityById(_context.Route.BaseModelType, id, null);
3534
}
3635

3736
private object GetRelated(string id, RelationalRoute relationalRoute)
3837
{
3938
// HACK: this would rely on lazy loading to work...will probably fail
40-
var entity = GetEntityById(relationalRoute.RelationalType, id, relationalRoute.RelationshipName);
41-
return relationalRoute.RelationalType.GetProperties().FirstOrDefault(pi => pi.Name.ToCamelCase() == relationalRoute.RelationshipName.ToCamelCase()).GetValue(entity);
39+
var entity = GetEntityById(relationalRoute.BaseModelType, id, relationalRoute.RelationshipName);
40+
return relationalRoute.BaseModelType.GetProperties().FirstOrDefault(pi => pi.Name.ToCamelCase() == relationalRoute.RelationshipName.ToCamelCase()).GetValue(entity);
4241
}
4342

4443

@@ -52,30 +51,21 @@ private object GetEntityById(Type modelType, string id, string includedRelations
5251
{
5352
// HACK: I _believe_ by casting to IEnumerable, we are loading all records into memory, if so... find a better way...
5453
// Also, we are making a BIG assumption that the resource has an attribute Id and not ResourceId which is allowed by EF
55-
var methodToCall = typeof(ResourceRepository).GetMethods().Single(method => method.Name.Equals("GetDbSet"));
56-
var genericMethod = methodToCall.MakeGenericMethod(modelType);
57-
genericMethod.Invoke(genericMethod, null);
58-
var dbSet = genericMethod.Invoke(this, null);
54+
var dataAccessorInstance = Activator.CreateInstance(typeof(GenericDataAccess));
55+
var dataAccessorMethod = dataAccessorInstance.GetType().GetMethod("GetDbSet");
56+
var genericMethod = dataAccessorMethod.MakeGenericMethod(modelType);
57+
var dbSet = genericMethod.Invoke(dataAccessorInstance, new [] {((DbContext) _context.DbContext) });
5958

6059
if (!string.IsNullOrEmpty(includedRelationship))
6160
{
62-
var includeMethod = typeof(ResourceRepository).GetMethods().Single(method => method.Name.Equals("IncludeEntity"));
61+
var includeMethod = dataAccessorInstance.GetType().GetMethod("IncludeEntity");
6362
var genericIncludeMethod = includeMethod.MakeGenericMethod(modelType);
64-
genericIncludeMethod.Invoke(genericMethod, null);
65-
dbSet = genericIncludeMethod.Invoke(this, new []{ dbSet, includedRelationship });
63+
dbSet = genericIncludeMethod.Invoke(dataAccessorInstance, new []{ dbSet, includedRelationship.ToProperCase() });
6664
}
6765

6866
return (dbSet as IEnumerable<dynamic>).SingleOrDefault(x => x.Id.ToString() == id);
6967
}
7068

71-
private DbSet<T> GetDbSet<T>() where T : class
72-
{
73-
return ((DbContext) _context.DbContext).Set<T>();
74-
}
7569

76-
private IQueryable<T> IncludeEntity<T>(IQueryable<T> queryable, string includedEntityName) where T : class
77-
{
78-
return queryable.Include(includedEntityName);
79-
}
8070
}
8171
}

JsonApiDotNetCore/Extensions/StringExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,15 @@ public static string ToCamelCase(this string str)
1414
}
1515
return str;
1616
}
17+
public static string ToProperCase(this string str)
18+
{
19+
var splittedPhraseChars = str.ToCharArray();
20+
if (splittedPhraseChars.Length > 0)
21+
{
22+
splittedPhraseChars[0] = new string(splittedPhraseChars[0], 1).ToUpper().ToCharArray()[0];
23+
return new String(splittedPhraseChars);
24+
}
25+
return str;
26+
}
1727
}
1828
}

JsonApiDotNetCore/Routing/RouteBuilder.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ public Route BuildFromRequest()
2323
{
2424
var remainingPathString = SetBaseRouteDefinition();
2525

26-
if (!remainingPathString.HasValue)
26+
if (PathStringIsEmpty(remainingPathString))
2727
{ // {baseResource}
2828
return new Route(_baseRouteDefinition.ModelType, _request.Method, null, _baseRouteDefinition);
2929
}
3030

3131
remainingPathString = SetBaseResourceId(remainingPathString);
32-
if (!remainingPathString.HasValue)
32+
if (PathStringIsEmpty(remainingPathString))
3333
{ // {baseResource}/{baseResourceId}
3434
return new Route(_baseRouteDefinition.ModelType, _request.Method, _baseResourceId, _baseRouteDefinition);
3535
}
@@ -46,6 +46,11 @@ public Route BuildFromRequest()
4646
return new RelationalRoute(_baseRouteDefinition.ModelType, _request.Method, _baseResourceId, _baseRouteDefinition, relationshipType, relatedResource);
4747
}
4848

49+
private bool PathStringIsEmpty(PathString pathString)
50+
{
51+
return pathString.HasValue ? string.IsNullOrEmpty(pathString.ToString().TrimStart('/')) : false;
52+
}
53+
4954
private PathString SetBaseRouteDefinition()
5055
{
5156
foreach (var rte in _configuration.Routes)

JsonApiDotNetCore/Services/JsonApiSerializer.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections;
1+
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.ComponentModel;
45
using System.Linq;
@@ -10,18 +11,33 @@
1011
using Newtonsoft.Json;
1112
using Newtonsoft.Json.Serialization;
1213
using System.Reflection;
14+
using JsonApiDotNetCore.Routing;
1315

1416
namespace JsonApiDotNetCore.Services
1517
{
1618
public class JsonApiSerializer
1719
{
1820
private readonly JsonApiContext _context;
1921
private readonly JsonApiModelConfiguration _jsonApiModelConfiguration;
22+
private string _entityName;
23+
private Type _entityType;
2024

2125
public JsonApiSerializer(JsonApiContext jsonApiContext, JsonApiModelConfiguration configuration)
2226
{
2327
_context = jsonApiContext;
2428
_jsonApiModelConfiguration = configuration;
29+
_entityName = GetEntityName();
30+
_entityType = GetEntityType();
31+
}
32+
33+
private string GetEntityName() {
34+
return (!(_context.Route is RelationalRoute) ? _context.Route.BaseRouteDefinition.ContextPropertyName
35+
: _jsonApiModelConfiguration.Routes.Single(r=> r.ModelType == ((RelationalRoute)_context.Route).RelationalType).ContextPropertyName).ToCamelCase();
36+
}
37+
38+
private Type GetEntityType() {
39+
return !(_context.Route is RelationalRoute) ? _context.Route.BaseRouteDefinition.ModelType
40+
: ((RelationalRoute)_context.Route).RelationalType;
2541
}
2642

2743
public string ToJsonApiDocument(object resultValue)
@@ -66,7 +82,7 @@ private JsonApiDatum ResourceToJsonApiDatum(JsonApiContext context, IJsonApiReso
6682
{
6783
return new JsonApiDatum
6884
{
69-
Type = context.Route.BaseRouteDefinition.ContextPropertyName.ToCamelCase(),
85+
Type = _entityName,
7086
Id = resource.Id,
7187
Attributes = GetAttributesFromResource(resource),
7288
Links = GetJsonApiDatumLinks(context, resource),
@@ -88,21 +104,24 @@ private Dictionary<string, string> GetJsonApiDocumentLinks(JsonApiContext jsonAp
88104
var request = jsonApiContext.HttpContext.Request;
89105
var route = jsonApiContext.Route;
90106

91-
return DocumentBuilder.BuildSelfLink(request.Scheme, request.Host.ToString(), _jsonApiModelConfiguration.Namespace,
92-
route.BaseRouteDefinition.ContextPropertyName.ToCamelCase(), route.ResourceId);
107+
return new Dictionary<string, string> {
108+
{
109+
"self", $"{request.Scheme}://{request.Host}{request.Path}"
110+
}
111+
};
93112
}
94113

95114
private Dictionary<string, string> GetJsonApiDatumLinks(JsonApiContext jsonApiContext, IJsonApiResource resource)
96115
{
97116
return DocumentBuilder.BuildSelfLink(jsonApiContext.HttpContext.Request.Scheme,
98117
jsonApiContext.HttpContext.Request.Host.ToString(), _jsonApiModelConfiguration.Namespace,
99-
jsonApiContext.Route.BaseRouteDefinition.ContextPropertyName.ToCamelCase(), resource.Id);
118+
_entityName, resource.Id);
100119
}
101120

102121
private Dictionary<string, object> BuildRelationshipsObject(JsonApiContext jsonApiContext, IJsonApiResource resource)
103122
{
104123
var relationships = new Dictionary<string, object>();
105-
jsonApiContext.Route.BaseModelType.GetProperties().Where(propertyInfo => propertyInfo.GetMethod.IsVirtual).ToList().ForEach(
124+
_entityType.GetProperties().Where(propertyInfo => propertyInfo.GetMethod.IsVirtual).ToList().ForEach(
106125
virtualProperty =>
107126
{
108127
relationships.Add(virtualProperty.Name, GetRelationshipLinks(jsonApiContext, resource, virtualProperty.Name.ToCamelCase()));
@@ -114,7 +133,7 @@ private Dictionary<string, string> GetRelationshipLinks(JsonApiContext jsonApiCo
114133
{
115134
return DocumentBuilder.BuildRelationshipLinks(jsonApiContext.HttpContext.Request.Scheme,
116135
jsonApiContext.HttpContext.Request.Host.ToString(), _jsonApiModelConfiguration.Namespace,
117-
jsonApiContext.Route.BaseRouteDefinition.ContextPropertyName.ToCamelCase(), resource.Id, relationshipName);
136+
_entityName, resource.Id, relationshipName);
118137
}
119138
}
120139
}

0 commit comments

Comments
 (0)