Skip to content

Commit e63ddf1

Browse files
author
Bart Koelman
authored
Small refactorings (#906)
* Made JsonApiController abstract * Optimization: only check for existing resource if an ID was specified in the request body * Rename WorkTags to singular, like the other properties * Added missing members defined in json:api to serialization models * Change IResourceGraph dependency in JsonApiRoutingConvention to IResourceContextProvider * Remove dead code path for _serializer == null (constructor asserts that it can never be null) * Removed unused type TestServerExtensions * Renamed private method * Rename IQueryLayerComposer.ComposeForHasManyThrough to ComposeForHasMany * Search/replace remaining usages of Context.RemoveRange with ClearTableAsync in tests * Rename IControllerResourceMapping.GetAssociatedResource to GetResourceTypeForController * Remove IRequestQueryStringAccessor.QueryString (no usages)
1 parent ff99353 commit e63ddf1

33 files changed

+110
-127
lines changed

benchmarks/Query/QueryParserBenchmarks.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,10 @@ private void Run(int iterations, Action action) {
107107

108108
private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor
109109
{
110-
public QueryString QueryString { get; private set; }
111110
public IQueryCollection Query { get; private set; }
112111

113112
public void SetQueryString(string queryString)
114113
{
115-
QueryString = new QueryString(queryString);
116114
Query = new QueryCollection(QueryHelpers.ParseQuery(queryString));
117115
}
118116
}

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ namespace JsonApiDotNetCore.Controllers
1616
/// </summary>
1717
/// <typeparam name="TResource">The resource type.</typeparam>
1818
/// <typeparam name="TId">The resource identifier type.</typeparam>
19-
public class JsonApiController<TResource, TId> : BaseJsonApiController<TResource, TId> where TResource : class, IIdentifiable<TId>
19+
public abstract class JsonApiController<TResource, TId> : BaseJsonApiController<TResource, TId> where TResource : class, IIdentifiable<TId>
2020
{
2121
/// <inheritdoc />
22-
public JsonApiController(
22+
protected JsonApiController(
2323
IJsonApiOptions options,
2424
ILoggerFactory loggerFactory,
2525
IResourceService<TResource, TId> resourceService)
2626
: base(options, loggerFactory, resourceService)
2727
{ }
2828

2929
/// <inheritdoc />
30-
public JsonApiController(
30+
protected JsonApiController(
3131
IJsonApiOptions options,
3232
ILoggerFactory loggerFactory,
3333
IGetAllService<TResource, TId> getAll = null,
@@ -118,18 +118,18 @@ public override async Task<IActionResult> DeleteRelationshipAsync(TId id, string
118118
}
119119

120120
/// <inheritdoc />
121-
public class JsonApiController<TResource> : JsonApiController<TResource, int> where TResource : class, IIdentifiable<int>
121+
public abstract class JsonApiController<TResource> : JsonApiController<TResource, int> where TResource : class, IIdentifiable<int>
122122
{
123123
/// <inheritdoc />
124-
public JsonApiController(
124+
protected JsonApiController(
125125
IJsonApiOptions options,
126126
ILoggerFactory loggerFactory,
127127
IResourceService<TResource, int> resourceService)
128128
: base(options, loggerFactory, resourceService)
129129
{ }
130130

131131
/// <inheritdoc />
132-
public JsonApiController(
132+
protected JsonApiController(
133133
IJsonApiOptions options,
134134
ILoggerFactory loggerFactory,
135135
IGetAllService<TResource, int> getAll = null,

src/JsonApiDotNetCore/Middleware/IControllerResourceMapping.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ public interface IControllerResourceMapping
1010
/// <summary>
1111
/// Get the associated resource type for the provided controller name.
1212
/// </summary>
13-
Type GetAssociatedResource(string controllerName);
13+
Type GetResourceTypeForController(string controllerName);
1414
}
1515
}

src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private static ResourceContext CreatePrimaryResourceContext(RouteValueDictionary
6969
var controllerName = (string) routeValues["controller"];
7070
if (controllerName != null)
7171
{
72-
var resourceType = controllerResourceMapping.GetAssociatedResource(controllerName);
72+
var resourceType = controllerResourceMapping.GetResourceTypeForController(controllerName);
7373
if (resourceType != null)
7474
{
7575
return resourceContextProvider.GetResourceContext(resourceType);

src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,20 @@ namespace JsonApiDotNetCore.Middleware
3131
public class JsonApiRoutingConvention : IJsonApiRoutingConvention
3232
{
3333
private readonly IJsonApiOptions _options;
34-
private readonly IResourceGraph _resourceGraph;
34+
private readonly IResourceContextProvider _resourceContextProvider;
3535
private readonly HashSet<string> _registeredTemplates = new HashSet<string>();
3636

3737
private readonly Dictionary<string, ResourceContext> _registeredResources =
3838
new Dictionary<string, ResourceContext>();
3939

40-
public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resourceGraph)
40+
public JsonApiRoutingConvention(IJsonApiOptions options, IResourceContextProvider resourceContextProvider)
4141
{
4242
_options = options ?? throw new ArgumentNullException(nameof(options));
43-
_resourceGraph = resourceGraph ?? throw new ArgumentNullException(nameof(resourceGraph));
43+
_resourceContextProvider = resourceContextProvider ?? throw new ArgumentNullException(nameof(resourceContextProvider));
4444
}
4545

4646
/// <inheritdoc />
47-
public Type GetAssociatedResource(string controllerName)
47+
public Type GetResourceTypeForController(string controllerName)
4848
{
4949
if (controllerName == null) throw new ArgumentNullException(nameof(controllerName));
5050

@@ -67,7 +67,7 @@ public void Apply(ApplicationModel application)
6767

6868
if (resourceType != null)
6969
{
70-
var resourceContext = _resourceGraph.GetResourceContext(resourceType);
70+
var resourceContext = _resourceContextProvider.GetResourceContext(resourceType);
7171

7272
if (resourceContext != null)
7373
{

src/JsonApiDotNetCore/Queries/IQueryLayerComposer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ QueryLayer WrapLayerForSecondaryEndpoint<TId>(QueryLayer secondaryLayer, Resourc
5353
QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relationship, ICollection<IIdentifiable> rightResourceIds);
5454

5555
/// <summary>
56-
/// Builds a query for a many-to-many relationship with a filter to match on its left and right resource IDs.
56+
/// Builds a query for a to-many relationship with a filter to match on its left and right resource IDs.
5757
/// </summary>
58-
QueryLayer ComposeForHasManyThrough<TId>(HasManyThroughAttribute hasManyThroughRelationship, TId leftId, ICollection<IIdentifiable> rightResourceIds);
58+
QueryLayer ComposeForHasMany<TId>(HasManyAttribute hasManyRelationship, TId leftId, ICollection<IIdentifiable> rightResourceIds);
5959
}
6060
}

src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,12 @@ public QueryLayer ComposeForGetRelationshipRightIds(RelationshipAttribute relati
337337
}
338338

339339
/// <inheritdoc />
340-
public QueryLayer ComposeForHasManyThrough<TId>(HasManyThroughAttribute hasManyThroughRelationship, TId leftId, ICollection<IIdentifiable> rightResourceIds)
340+
public QueryLayer ComposeForHasMany<TId>(HasManyAttribute hasManyRelationship, TId leftId, ICollection<IIdentifiable> rightResourceIds)
341341
{
342-
var leftResourceContext = _resourceContextProvider.GetResourceContext(hasManyThroughRelationship.LeftType);
342+
var leftResourceContext = _resourceContextProvider.GetResourceContext(hasManyRelationship.LeftType);
343343
var leftIdAttribute = GetIdAttribute(leftResourceContext);
344344

345-
var rightResourceContext = _resourceContextProvider.GetResourceContext(hasManyThroughRelationship.RightType);
345+
var rightResourceContext = _resourceContextProvider.GetResourceContext(hasManyRelationship.RightType);
346346
var rightIdAttribute = GetIdAttribute(rightResourceContext);
347347
var rightTypedIds = rightResourceIds.Select(resource => resource.GetTypedId()).ToArray();
348348

@@ -351,11 +351,11 @@ public QueryLayer ComposeForHasManyThrough<TId>(HasManyThroughAttribute hasManyT
351351

352352
return new QueryLayer(leftResourceContext)
353353
{
354-
Include = new IncludeExpression(new[] {new IncludeElementExpression(hasManyThroughRelationship)}),
354+
Include = new IncludeExpression(new[] {new IncludeElementExpression(hasManyRelationship)}),
355355
Filter = leftFilter,
356356
Projection = new Dictionary<ResourceFieldAttribute, QueryLayer>
357357
{
358-
[hasManyThroughRelationship] = new QueryLayer(rightResourceContext)
358+
[hasManyRelationship] = new QueryLayer(rightResourceContext)
359359
{
360360
Filter = rightFilter,
361361
Projection = new Dictionary<ResourceFieldAttribute, QueryLayer>

src/JsonApiDotNetCore/QueryStrings/IRequestQueryStringAccessor.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ namespace JsonApiDotNetCore.QueryStrings
77
/// </summary>
88
public interface IRequestQueryStringAccessor
99
{
10-
QueryString QueryString { get; }
1110
IQueryCollection Query { get; }
1211
}
1312
}

src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute rel
194194

195195
var relationshipIsBeingCleared = relationship is HasOneAttribute
196196
? rightValue == null
197-
: IsRequiredToManyRelationshipBeingCleared(relationship, leftResource, rightValue);
197+
: IsToManyRelationshipBeingCleared(relationship, leftResource, rightValue);
198198

199199
if (relationshipIsRequired && relationshipIsBeingCleared)
200200
{
@@ -203,7 +203,7 @@ protected void AssertIsNotClearingRequiredRelationship(RelationshipAttribute rel
203203
}
204204
}
205205

206-
private static bool IsRequiredToManyRelationshipBeingCleared(RelationshipAttribute relationship, TResource leftResource, object valueToAssign)
206+
private static bool IsToManyRelationshipBeingCleared(RelationshipAttribute relationship, TResource leftResource, object valueToAssign)
207207
{
208208
ICollection<IIdentifiable> newRightResourceIds = TypeHelper.ExtractResources(valueToAssign);
209209

src/JsonApiDotNetCore/Serialization/JsonApiWriter.cs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,23 @@ public JsonApiWriter(IJsonApiSerializer serializer, IExceptionHandler exceptionH
3636

3737
public async Task WriteAsync(OutputFormatterWriteContext context)
3838
{
39-
if (context == null)
40-
throw new ArgumentNullException(nameof(context));
39+
if (context == null) throw new ArgumentNullException(nameof(context));
4140

4241
var response = context.HttpContext.Response;
42+
response.ContentType = HeaderConstants.MediaType;
43+
4344
await using var writer = context.WriterFactory(response.Body, Encoding.UTF8);
4445
string responseContent;
45-
46-
if (_serializer == null)
46+
try
4747
{
48-
responseContent = JsonConvert.SerializeObject(context.Object);
48+
responseContent = SerializeResponse(context.Object, (HttpStatusCode) response.StatusCode);
4949
}
50-
else
50+
catch (Exception exception)
5151
{
52-
response.ContentType = HeaderConstants.MediaType;
53-
try
54-
{
55-
responseContent = SerializeResponse(context.Object, (HttpStatusCode)response.StatusCode);
56-
}
57-
catch (Exception exception)
58-
{
59-
var errorDocument = _exceptionHandler.HandleException(exception);
60-
responseContent = _serializer.Serialize(errorDocument);
52+
var errorDocument = _exceptionHandler.HandleException(exception);
53+
responseContent = _serializer.Serialize(errorDocument);
6154

62-
response.StatusCode = (int)errorDocument.GetErrorStatusCode();
63-
}
55+
response.StatusCode = (int) errorDocument.GetErrorStatusCode();
6456
}
6557

6658
var url = context.HttpContext.Request.GetEncodedUrl();

src/JsonApiDotNetCore/Serialization/Objects/Document.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ public sealed class Document : ExposableData<ResourceObject>
1414
[JsonProperty("meta", NullValueHandling = NullValueHandling.Ignore)]
1515
public IDictionary<string, object> Meta { get; set; }
1616

17+
/// <summary>
18+
/// see "jsonapi" in https://jsonapi.org/format/#document-top-level
19+
/// </summary>
20+
[JsonProperty("jsonapi", NullValueHandling = NullValueHandling.Ignore)]
21+
public IDictionary<string, object> JsonApi { get; set; }
22+
1723
/// <summary>
1824
/// see "links" in https://jsonapi.org/format/#document-top-level
1925
/// </summary>

src/JsonApiDotNetCore/Serialization/Objects/RelationshipEntry.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using Newtonsoft.Json;
23

34
namespace JsonApiDotNetCore.Serialization.Objects
@@ -6,5 +7,8 @@ public sealed class RelationshipEntry : ExposableData<ResourceIdentifierObject>
67
{
78
[JsonProperty("links", NullValueHandling = NullValueHandling.Ignore)]
89
public RelationshipLinks Links { get; set; }
10+
11+
[JsonProperty("meta", NullValueHandling = NullValueHandling.Ignore)]
12+
public IDictionary<string, object> Meta { get; set; }
913
}
1014
}

src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ namespace JsonApiDotNetCore.Serialization.Objects
44
{
55
public class ResourceIdentifierObject
66
{
7-
public ResourceIdentifierObject() { }
8-
9-
public ResourceIdentifierObject(string type, string id)
10-
{
11-
Type = type;
12-
Id = id;
13-
}
14-
157
[JsonProperty("type", Order = -3)]
168
public string Type { get; set; }
179

src/JsonApiDotNetCore/Serialization/Objects/TopLevelLinks.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public sealed class TopLevelLinks
1313
[JsonProperty("related")]
1414
public string Related { get; set; }
1515

16+
[JsonProperty("describedby")]
17+
public string DescribedBy { get; set; }
18+
1619
[JsonProperty("first")]
1720
public string First { get; set; }
1821

@@ -28,6 +31,7 @@ public sealed class TopLevelLinks
2831
// http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
2932
public bool ShouldSerializeSelf() => !string.IsNullOrEmpty(Self);
3033
public bool ShouldSerializeRelated() => !string.IsNullOrEmpty(Related);
34+
public bool ShouldSerializeDescribedBy() => !string.IsNullOrEmpty(DescribedBy);
3135
public bool ShouldSerializeFirst() => !string.IsNullOrEmpty(First);
3236
public bool ShouldSerializeLast() => !string.IsNullOrEmpty(Last);
3337
public bool ShouldSerializePrev() => !string.IsNullOrEmpty(Prev);

src/JsonApiDotNetCore/Services/JsonApiResourceService.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,13 @@ public virtual async Task<TResource> CreateAsync(TResource resource, Cancellatio
186186
}
187187
catch (DataStoreUpdateException)
188188
{
189-
var existingResource = await TryGetPrimaryResourceByIdAsync(resourceFromRequest.Id, TopFieldSelection.OnlyIdAttribute, cancellationToken);
190-
if (existingResource != null)
189+
if (!Equals(resourceFromRequest.Id, default(TId)))
191190
{
192-
throw new ResourceAlreadyExistsException(resourceFromRequest.StringId, _request.PrimaryResource.PublicName);
191+
var existingResource = await TryGetPrimaryResourceByIdAsync(resourceFromRequest.Id, TopFieldSelection.OnlyIdAttribute, cancellationToken);
192+
if (existingResource != null)
193+
{
194+
throw new ResourceAlreadyExistsException(resourceFromRequest.StringId, _request.PrimaryResource.PublicName);
195+
}
193196
}
194197

195198
await AssertResourcesToAssignInRelationshipsExistAsync(resourceFromRequest, cancellationToken);
@@ -287,7 +290,7 @@ public async Task AddToToManyRelationshipAsync(TId primaryId, string relationshi
287290
private async Task RemoveExistingIdsFromSecondarySet(TId primaryId, ISet<IIdentifiable> secondaryResourceIds,
288291
HasManyThroughAttribute hasManyThrough, CancellationToken cancellationToken)
289292
{
290-
var queryLayer = _queryLayerComposer.ComposeForHasManyThrough(hasManyThrough, primaryId, secondaryResourceIds);
293+
var queryLayer = _queryLayerComposer.ComposeForHasMany(hasManyThrough, primaryId, secondaryResourceIds);
291294
var primaryResources = await _repositoryAccessor.GetAsync<TResource>(queryLayer, cancellationToken);
292295

293296
var primaryResource = primaryResources.FirstOrDefault();

test/JsonApiDotNetCoreExampleTests/Helpers/Extensions/TestServerExtensions.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

test/JsonApiDotNetCoreExampleTests/IntegrationTests/Filtering/FilterDataTypeTests.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public async Task Can_filter_equality_on_type(string propertyName, object value)
4444

4545
await _testContext.RunOnDatabaseAsync(async dbContext =>
4646
{
47-
dbContext.RemoveRange(dbContext.FilterableResources);
47+
await dbContext.ClearTableAsync<FilterableResource>();
4848
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
4949

5050
await dbContext.SaveChangesAsync();
@@ -71,7 +71,7 @@ public async Task Can_filter_equality_on_type_Decimal()
7171

7272
await _testContext.RunOnDatabaseAsync(async dbContext =>
7373
{
74-
dbContext.RemoveRange(dbContext.FilterableResources);
74+
await dbContext.ClearTableAsync<FilterableResource>();
7575
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
7676

7777
await dbContext.SaveChangesAsync();
@@ -97,7 +97,7 @@ public async Task Can_filter_equality_on_type_Guid()
9797

9898
await _testContext.RunOnDatabaseAsync(async dbContext =>
9999
{
100-
dbContext.RemoveRange(dbContext.FilterableResources);
100+
await dbContext.ClearTableAsync<FilterableResource>();
101101
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
102102

103103
await dbContext.SaveChangesAsync();
@@ -123,7 +123,7 @@ public async Task Can_filter_equality_on_type_DateTime()
123123

124124
await _testContext.RunOnDatabaseAsync(async dbContext =>
125125
{
126-
dbContext.RemoveRange(dbContext.FilterableResources);
126+
await dbContext.ClearTableAsync<FilterableResource>();
127127
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
128128

129129
await dbContext.SaveChangesAsync();
@@ -152,7 +152,7 @@ public async Task Can_filter_equality_on_type_DateTimeOffset()
152152

153153
await _testContext.RunOnDatabaseAsync(async dbContext =>
154154
{
155-
dbContext.RemoveRange(dbContext.FilterableResources);
155+
await dbContext.ClearTableAsync<FilterableResource>();
156156
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
157157

158158
await dbContext.SaveChangesAsync();
@@ -178,7 +178,7 @@ public async Task Can_filter_equality_on_type_TimeSpan()
178178

179179
await _testContext.RunOnDatabaseAsync(async dbContext =>
180180
{
181-
dbContext.RemoveRange(dbContext.FilterableResources);
181+
await dbContext.ClearTableAsync<FilterableResource>();
182182
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
183183

184184
await dbContext.SaveChangesAsync();
@@ -204,7 +204,7 @@ public async Task Cannot_filter_equality_on_incompatible_value()
204204

205205
await _testContext.RunOnDatabaseAsync(async dbContext =>
206206
{
207-
dbContext.RemoveRange(dbContext.FilterableResources);
207+
await dbContext.ClearTableAsync<FilterableResource>();
208208
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
209209

210210
await dbContext.SaveChangesAsync();
@@ -261,7 +261,7 @@ public async Task Can_filter_is_null_on_type(string propertyName)
261261

262262
await _testContext.RunOnDatabaseAsync(async dbContext =>
263263
{
264-
dbContext.RemoveRange(dbContext.FilterableResources);
264+
await dbContext.ClearTableAsync<FilterableResource>();
265265
dbContext.FilterableResources.AddRange(resource, otherResource);
266266

267267
await dbContext.SaveChangesAsync();
@@ -312,7 +312,7 @@ public async Task Can_filter_is_not_null_on_type(string propertyName)
312312

313313
await _testContext.RunOnDatabaseAsync(async dbContext =>
314314
{
315-
dbContext.RemoveRange(dbContext.FilterableResources);
315+
await dbContext.ClearTableAsync<FilterableResource>();
316316
dbContext.FilterableResources.AddRange(resource, new FilterableResource());
317317

318318
await dbContext.SaveChangesAsync();

0 commit comments

Comments
 (0)