Skip to content

Commit 1bb0bbd

Browse files
authored
Merge pull request #336 from json-api-dotnet/develop
develop → master: operations
2 parents 181d809 + c52170d commit 1bb0bbd

17 files changed

+155
-46
lines changed

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ public static void AddJsonApiInternals(
122122

123123
services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>));
124124
services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>));
125+
125126
services.AddSingleton<JsonApiOptions>(jsonApiOptions);
126127
services.AddSingleton<IContextGraph>(jsonApiOptions.ContextGraph);
127128
services.AddScoped<IJsonApiContext, JsonApiContext>();

src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<VersionPrefix>2.3.4</VersionPrefix>
3+
<VersionPrefix>2.4.0</VersionPrefix>
44
<TargetFrameworks>$(NetStandardVersion)</TargetFrameworks>
55
<AssemblyName>JsonApiDotNetCore</AssemblyName>
66
<PackageId>JsonApiDotNetCore</PackageId>

src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Threading.Tasks;
33
using JsonApiDotNetCore.Internal;
4+
using JsonApiDotNetCore.Services;
45
using Microsoft.AspNetCore.Http;
56
using Microsoft.Extensions.Primitives;
67

@@ -9,16 +10,23 @@ namespace JsonApiDotNetCore.Middleware
910
public class RequestMiddleware
1011
{
1112
private readonly RequestDelegate _next;
12-
13+
1314
public RequestMiddleware(RequestDelegate next)
1415
{
1516
_next = next;
1617
}
1718

18-
public async Task Invoke(HttpContext context)
19+
public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext)
1920
{
2021
if (IsValid(context))
22+
{
23+
// HACK: this currently results in allocation of
24+
// objects that may or may not be used and even double allocation
25+
// since the JsonApiContext is using field initializers
26+
// Need to work on finding a better solution.
27+
jsonApiContext.BeginOperation();
2128
await _next(context);
29+
}
2230
}
2331

2432
private static bool IsValid(HttpContext context)
@@ -58,11 +66,11 @@ internal static bool ContainsMediaTypeParameters(string mediaType)
5866
var incomingMediaTypeSpan = mediaType.AsSpan();
5967

6068
// if the content type is not application/vnd.api+json then continue on
61-
if(incomingMediaTypeSpan.Length < Constants.ContentType.Length)
69+
if (incomingMediaTypeSpan.Length < Constants.ContentType.Length)
6270
return false;
6371

6472
var incomingContentType = incomingMediaTypeSpan.Slice(0, Constants.ContentType.Length);
65-
if(incomingContentType.SequenceEqual(Constants.ContentType.AsSpan()) == false)
73+
if (incomingContentType.SequenceEqual(Constants.ContentType.AsSpan()) == false)
6674
return false;
6775

6876
// anything appended to "application/vnd.api+json;" will be considered a media type param

src/JsonApiDotNetCore/Request/HasManyRelationshipPointers.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using System;
21
using System.Collections;
32
using System.Collections.Generic;
3+
using JsonApiDotNetCore.Models;
44

55
namespace JsonApiDotNetCore.Request
66
{
@@ -32,18 +32,18 @@ namespace JsonApiDotNetCore.Request
3232
/// </summary>
3333
public class HasManyRelationshipPointers
3434
{
35-
private Dictionary<Type, IList> _hasManyRelationships = new Dictionary<Type, IList>();
35+
private Dictionary<RelationshipAttribute, IList> _hasManyRelationships = new Dictionary<RelationshipAttribute, IList>();
3636

3737
/// <summary>
3838
/// Add the relationship to the list of relationships that should be
3939
/// set in the repository layer.
4040
/// </summary>
41-
public void Add(Type dependentType, IList entities)
42-
=> _hasManyRelationships[dependentType] = entities;
41+
public void Add(RelationshipAttribute relationship, IList entities)
42+
=> _hasManyRelationships[relationship] = entities;
4343

4444
/// <summary>
4545
/// Get all the models that should be associated
4646
/// </summary>
47-
public Dictionary<Type, IList> Get() => _hasManyRelationships;
47+
public Dictionary<RelationshipAttribute, IList> Get() => _hasManyRelationships;
4848
}
4949
}

src/JsonApiDotNetCore/Request/HasOneRelationshipPointers.cs

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using JsonApiDotNetCore.Models;
2-
using System;
32
using System.Collections.Generic;
43

54
namespace JsonApiDotNetCore.Request
@@ -29,18 +28,18 @@ namespace JsonApiDotNetCore.Request
2928
/// </summary>
3029
public class HasOneRelationshipPointers
3130
{
32-
private Dictionary<Type, IIdentifiable> _hasOneRelationships = new Dictionary<Type, IIdentifiable>();
31+
private Dictionary<RelationshipAttribute, IIdentifiable> _hasOneRelationships = new Dictionary<RelationshipAttribute, IIdentifiable>();
3332

3433
/// <summary>
3534
/// Add the relationship to the list of relationships that should be
3635
/// set in the repository layer.
3736
/// </summary>
38-
public void Add(Type dependentType, IIdentifiable entity)
39-
=> _hasOneRelationships[dependentType] = entity;
37+
public void Add(RelationshipAttribute relationship, IIdentifiable entity)
38+
=> _hasOneRelationships[relationship] = entity;
4039

4140
/// <summary>
4241
/// Get all the models that should be associated
4342
/// </summary>
44-
public Dictionary<Type, IIdentifiable> Get() => _hasOneRelationships;
43+
public Dictionary<RelationshipAttribute, IIdentifiable> Get() => _hasOneRelationships;
4544
}
4645
}

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public object DocumentToObject(DocumentData data, List<DocumentData> included =
124124
+ "If you have manually registered the resource, check that the call to AddResource correctly sets the public name."); ;
125125

126126
var entity = Activator.CreateInstance(contextEntity.EntityType);
127-
127+
128128
entity = SetEntityAttributes(entity, contextEntity, data.Attributes);
129129
entity = SetRelationships(entity, contextEntity, data.Relationships, included);
130130

@@ -141,7 +141,7 @@ private object SetEntityAttributes(
141141
{
142142
if (attributeValues == null || attributeValues.Count == 0)
143143
return entity;
144-
144+
145145
foreach (var attr in contextEntity.Attributes)
146146
{
147147
if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue))
@@ -174,7 +174,7 @@ private object DeserializeComplexType(JContainer obj, Type targetType)
174174
private object SetRelationships(
175175
object entity,
176176
ContextEntity contextEntity,
177-
Dictionary<string, RelationshipData> relationships,
177+
Dictionary<string, RelationshipData> relationships,
178178
List<DocumentData> included = null)
179179
{
180180
if (relationships == null || relationships.Count == 0)
@@ -203,7 +203,7 @@ private object SetHasOneRelationship(object entity,
203203

204204
if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData) == false)
205205
return entity;
206-
206+
207207
var relationshipAttr = _jsonApiContext.RequestEntity.Relationships
208208
.SingleOrDefault(r => r.PublicRelationshipName == relationshipName);
209209

@@ -234,7 +234,7 @@ private object SetHasOneRelationship(object entity,
234234
foreignKeyProperty.SetValue(entity, convertedValue);
235235

236236

237-
if(rio != null
237+
if (rio != null
238238
// if the resource identifier is null, there should be no reason to instantiate an instance
239239
&& rio.Id != null)
240240
{
@@ -247,7 +247,7 @@ private object SetHasOneRelationship(object entity,
247247
// we need to store the fact that this relationship was included in the payload
248248
// for EF, the repository will use these pointers to make ensure we don't try to
249249
// create resources if they already exist, we just need to create the relationship
250-
_jsonApiContext.HasOneRelationshipPointers.Add(attr.Type, includedRelationshipObject);
250+
_jsonApiContext.HasOneRelationshipPointers.Add(attr, includedRelationshipObject);
251251
}
252252

253253
return entity;
@@ -278,7 +278,7 @@ private object SetHasManyRelationship(object entity,
278278

279279
attr.SetValue(entity, convertedCollection);
280280

281-
_jsonApiContext.HasManyRelationshipPointers.Add(attr.Type, convertedCollection);
281+
_jsonApiContext.HasManyRelationshipPointers.Add(attr, convertedCollection);
282282
}
283283

284284
return entity;
@@ -301,7 +301,7 @@ private IIdentifiable GetIncludedRelationship(ResourceIdentifierObject relatedRe
301301
var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(relationshipAttr.Type);
302302
if (contextEntity == null)
303303
throw new JsonApiException(400, $"Included type '{relationshipAttr.Type}' is not a registered json:api resource.");
304-
304+
305305
SetEntityAttributes(relatedInstance, contextEntity, includedResource.Attributes);
306306

307307
return relatedInstance;

src/JsonApiDotNetCore/Services/IJsonApiContext.cs

+7
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,12 @@ public interface IJsonApiContext : IJsonApiRequest
152152

153153
[Obsolete("Use the proxied method IControllerContext.GetControllerAttribute instead.")]
154154
TAttribute GetControllerAttribute<TAttribute>() where TAttribute : Attribute;
155+
156+
/// <summary>
157+
/// **_Experimental_**: do not use. It is likely to change in the future.
158+
///
159+
/// Resets operational state information.
160+
/// </summary>
161+
void BeginOperation();
155162
}
156163
}

src/JsonApiDotNetCore/Services/JsonApiContext.cs

+14-4
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,14 @@ public JsonApiContext(
4747
public PageManager PageManager { get; set; }
4848
public IMetaBuilder MetaBuilder { get; set; }
4949
public IGenericProcessorFactory GenericProcessorFactory { get; set; }
50-
public Dictionary<AttrAttribute, object> AttributesToUpdate { get; set; } = new Dictionary<AttrAttribute, object>();
51-
public Dictionary<RelationshipAttribute, object> RelationshipsToUpdate { get; set; } = new Dictionary<RelationshipAttribute, object>();
5250
public Type ControllerType { get; set; }
5351
public Dictionary<string, object> DocumentMeta { get; set; }
5452
public bool IsBulkOperationRequest { get; set; }
55-
public HasManyRelationshipPointers HasManyRelationshipPointers { get; } = new HasManyRelationshipPointers();
56-
public HasOneRelationshipPointers HasOneRelationshipPointers { get; } = new HasOneRelationshipPointers();
53+
54+
public Dictionary<AttrAttribute, object> AttributesToUpdate { get; set; } = new Dictionary<AttrAttribute, object>();
55+
public Dictionary<RelationshipAttribute, object> RelationshipsToUpdate { get; set; } = new Dictionary<RelationshipAttribute, object>();
56+
public HasManyRelationshipPointers HasManyRelationshipPointers { get; private set; } = new HasManyRelationshipPointers();
57+
public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers();
5758

5859
public IJsonApiContext ApplyContext<T>(object controller)
5960
{
@@ -132,5 +133,14 @@ private PageManager GetPageManager()
132133
[Obsolete("Use the proxied method IControllerContext.GetControllerAttribute instead.")]
133134
public TAttribute GetControllerAttribute<TAttribute>() where TAttribute : Attribute
134135
=> _controllerContext.GetControllerAttribute<TAttribute>();
136+
137+
public void BeginOperation()
138+
{
139+
IncludedRelationships = new List<string>();
140+
AttributesToUpdate = new Dictionary<AttrAttribute, object>();
141+
RelationshipsToUpdate = new Dictionary<RelationshipAttribute, object>();
142+
HasManyRelationshipPointers = new HasManyRelationshipPointers();
143+
HasOneRelationshipPointers = new HasOneRelationshipPointers();
144+
}
135145
}
136146
}

src/JsonApiDotNetCore/Services/Operations/OperationProcessorResolver.cs

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using JsonApiDotNetCore.Internal;
12
using JsonApiDotNetCore.Internal.Generics;
23
using JsonApiDotNetCore.Models.Operations;
34
using JsonApiDotNetCore.Services.Operations.Processors;
@@ -90,6 +91,9 @@ public IOpProcessor LocateUpdateService(Operation operation)
9091
var resource = operation.GetResourceTypeName();
9192

9293
var contextEntity = _context.ContextGraph.GetContextEntity(resource);
94+
if (contextEntity == null)
95+
throw new JsonApiException(400, $"This API does not expose a resource of type '{resource}'.");
96+
9397
var processor = _processorFactory.GetProcessor<IOpProcessor>(
9498
typeof(IUpdateOpProcessor<,>), contextEntity.EntityType, contextEntity.IdentityType
9599
);

src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ public class OperationsProcessor : IOperationsProcessor
1919
{
2020
private readonly IOperationProcessorResolver _processorResolver;
2121
private readonly DbContext _dbContext;
22+
private readonly IJsonApiContext _jsonApiContext;
2223

2324
public OperationsProcessor(
2425
IOperationProcessorResolver processorResolver,
25-
IDbContextResolver dbContextResolver)
26+
IDbContextResolver dbContextResolver,
27+
IJsonApiContext jsonApiContext)
2628
{
2729
_processorResolver = processorResolver;
2830
_dbContext = dbContextResolver.GetContext();
31+
_jsonApiContext = jsonApiContext;
2932
}
3033

3134
public async Task<List<Operation>> ProcessAsync(List<Operation> inputOps)
@@ -40,6 +43,7 @@ public async Task<List<Operation>> ProcessAsync(List<Operation> inputOps)
4043
{
4144
foreach (var op in inputOps)
4245
{
46+
_jsonApiContext.BeginOperation();
4347
lastAttemptedOperation = op.Op;
4448
await ProcessOperation(op, outputOps);
4549
opIndex++;
@@ -75,7 +79,8 @@ private async Task ProcessOperation(Operation op, List<Operation> outputOps)
7579

7680
private void ReplaceLocalIdsInResourceObject(ResourceObject resourceObject, List<Operation> outputOps)
7781
{
78-
if (resourceObject == null) return;
82+
if (resourceObject == null)
83+
return;
7984

8085
// it is strange to me that a top level resource object might use a lid.
8186
// by not replacing it, we avoid a case where the first operation is an 'add' with an 'lid'

src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77

88
namespace JsonApiDotNetCore.Services.Operations.Processors
99
{
10-
public interface ICreateOpProcessor<T> : IOpProcessor
10+
public interface ICreateOpProcessor<T> : ICreateOpProcessor<T, int>
1111
where T : class, IIdentifiable<int>
1212
{ }
1313

1414
public interface ICreateOpProcessor<T, TId> : IOpProcessor
1515
where T : class, IIdentifiable<TId>
1616
{ }
1717

18-
public class CreateOpProcessor<T> : CreateOpProcessor<T, int>
18+
public class CreateOpProcessor<T>
19+
: CreateOpProcessor<T, int>, ICreateOpProcessor<T>
1920
where T : class, IIdentifiable<int>
2021
{
2122
public CreateOpProcessor(

src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace JsonApiDotNetCore.Services.Operations.Processors
1313
/// Handles all "<see cref="OperationCode.get"/>" operations
1414
/// </summary>
1515
/// <typeparam name="T">The resource type</typeparam>
16-
public interface IGetOpProcessor<T> : IOpProcessor
16+
public interface IGetOpProcessor<T> : IGetOpProcessor<T, int>
1717
where T : class, IIdentifiable<int>
1818
{ }
1919

@@ -27,7 +27,7 @@ public interface IGetOpProcessor<T, TId> : IOpProcessor
2727
{ }
2828

2929
/// <inheritdoc />
30-
public class GetOpProcessor<T> : GetOpProcessor<T, int>
30+
public class GetOpProcessor<T> : GetOpProcessor<T, int>, IGetOpProcessor<T>
3131
where T : class, IIdentifiable<int>
3232
{
3333
/// <inheritdoc />

src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77

88
namespace JsonApiDotNetCore.Services.Operations.Processors
99
{
10-
public interface IRemoveOpProcessor<T> : IOpProcessor
10+
public interface IRemoveOpProcessor<T> : IRemoveOpProcessor<T, int>
1111
where T : class, IIdentifiable<int>
1212
{ }
1313

1414
public interface IRemoveOpProcessor<T, TId> : IOpProcessor
1515
where T : class, IIdentifiable<TId>
1616
{ }
1717

18-
public class RemoveOpProcessor<T> : RemoveOpProcessor<T, int>
18+
public class RemoveOpProcessor<T> : RemoveOpProcessor<T, int>, IRemoveOpProcessor<T>
1919
where T : class, IIdentifiable<int>
2020
{
2121
public RemoveOpProcessor(

src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs

+7-6
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77

88
namespace JsonApiDotNetCore.Services.Operations.Processors
99
{
10-
public interface IUpdateOpProcessor<T> : IOpProcessor
10+
public interface IUpdateOpProcessor<T> : IUpdateOpProcessor<T, int>
1111
where T : class, IIdentifiable<int>
1212
{ }
1313

1414
public interface IUpdateOpProcessor<T, TId> : IOpProcessor
1515
where T : class, IIdentifiable<TId>
1616
{ }
1717

18-
public class UpdateOpProcessor<T> : UpdateOpProcessor<T, int>
18+
public class UpdateOpProcessor<T> : UpdateOpProcessor<T, int>, IUpdateOpProcessor<T>
1919
where T : class, IIdentifiable<int>
2020
{
2121
public UpdateOpProcessor(
@@ -27,7 +27,7 @@ IContextGraph contextGraph
2727
{ }
2828
}
2929

30-
public class UpdateOpProcessor<T, TId> : ICreateOpProcessor<T, TId>
30+
public class UpdateOpProcessor<T, TId> : IUpdateOpProcessor<T, TId>
3131
where T : class, IIdentifiable<TId>
3232
{
3333
private readonly IUpdateService<T, TId> _service;
@@ -53,16 +53,17 @@ public async Task<Operation> ProcessAsync(Operation operation)
5353
throw new JsonApiException(400, "The data.id parameter is required for replace operations");
5454

5555
var model = (T)_deSerializer.DocumentToObject(operation.DataObject);
56+
5657
var result = await _service.UpdateAsync(model.Id, model);
58+
if (result == null)
59+
throw new JsonApiException(404, $"Could not find an instance of '{operation.DataObject.Type}' with id {operation.DataObject.Id}");
5760

5861
var operationResult = new Operation
5962
{
6063
Op = OperationCode.update
6164
};
6265

63-
operationResult.Data = _documentBuilder.GetData(
64-
_contextGraph.GetContextEntity(operation.GetResourceTypeName()),
65-
result);
66+
operationResult.Data = _documentBuilder.GetData(_contextGraph.GetContextEntity(operation.GetResourceTypeName()), result);
6667

6768
return operationResult;
6869
}

0 commit comments

Comments
 (0)