Skip to content

Commit 534822c

Browse files
committed
reset state between operations
1 parent 6cd41b5 commit 534822c

File tree

5 files changed

+45
-12
lines changed

5 files changed

+45
-12
lines changed

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/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/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'

test/UnitTests/Services/Operations/OperationsProcessorTests.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using JsonApiDotNetCore.Data;
55
using JsonApiDotNetCore.Models.Operations;
6+
using JsonApiDotNetCore.Services;
67
using JsonApiDotNetCore.Services.Operations;
78
using Microsoft.EntityFrameworkCore;
89
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -18,12 +19,14 @@ public class OperationsProcessorTests
1819
private readonly Mock<IOperationProcessorResolver> _resolverMock;
1920
public readonly Mock<DbContext> _dbContextMock;
2021
public readonly Mock<IDbContextResolver> _dbContextResolverMock;
22+
public readonly Mock<IJsonApiContext> _jsonApiContextMock;
2123

2224
public OperationsProcessorTests()
2325
{
2426
_resolverMock = new Mock<IOperationProcessorResolver>();
2527
_dbContextMock = new Mock<DbContext>();
2628
_dbContextResolverMock = new Mock<IDbContextResolver>();
29+
_jsonApiContextMock = new Mock<IJsonApiContext>();
2730
}
2831

2932
[Fact]
@@ -90,7 +93,7 @@ public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_Relationship
9093
.Returns(opProcessorMock.Object);
9194

9295
_dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object);
93-
var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object);
96+
var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object);
9497

9598
// act
9699
var results = await operationsProcessor.ProcessAsync(operations);
@@ -173,7 +176,7 @@ public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_References()
173176
.Returns(updateOpProcessorMock.Object);
174177

175178
_dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object);
176-
var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object);
179+
var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object);
177180

178181
// act
179182
var results = await operationsProcessor.ProcessAsync(operations);

0 commit comments

Comments
 (0)