Skip to content

Commit ee5b34a

Browse files
committed
feat(fetch): add get all operation
1 parent f54dc4a commit ee5b34a

File tree

11 files changed

+251
-9
lines changed

11 files changed

+251
-9
lines changed

src/JsonApiDotNetCore/Controllers/JsonApiOperationsController.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ public class JsonApiOperationsController : Controller
99
{
1010
private readonly IOperationsProcessor _operationsProcessor;
1111

12-
public JsonApiOperationsController(IOperationsProcessor operationsProcessor)
12+
public JsonApiOperationsController(
13+
IOperationsProcessor operationsProcessor)
1314
{
1415
_operationsProcessor = operationsProcessor;
1516
}
@@ -18,7 +19,8 @@ public JsonApiOperationsController(IOperationsProcessor operationsProcessor)
1819
public async Task<IActionResult> PatchAsync([FromBody] OperationsDocument doc)
1920
{
2021
var results = await _operationsProcessor.ProcessAsync(doc.Operations);
21-
return Ok(results);
22+
23+
return Ok(new OperationsDocument(results));
2224
}
2325
}
2426
}

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,13 @@ public static void AddJsonApiInternals(
100100
services.AddScoped<IDbContextResolver, DbContextResolver>();
101101
services.AddScoped(typeof(IEntityRepository<>), typeof(DefaultEntityRepository<>));
102102
services.AddScoped(typeof(IEntityRepository<,>), typeof(DefaultEntityRepository<,>));
103+
103104
services.AddScoped(typeof(ICreateService<>), typeof(EntityResourceService<>));
104105
services.AddScoped(typeof(ICreateService<,>), typeof(EntityResourceService<,>));
106+
107+
services.AddScoped(typeof(IGetAllService<>), typeof(EntityResourceService<>));
108+
services.AddScoped(typeof(IGetAllService<,>), typeof(EntityResourceService<,>));
109+
105110
services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>));
106111
services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>));
107112
services.AddSingleton<JsonApiOptions>(jsonApiOptions);
@@ -127,8 +132,13 @@ public static void AddJsonApiInternals(
127132
private static void AddOperationServices(IServiceCollection services)
128133
{
129134
services.AddScoped<IOperationsProcessor, OperationsProcessor>();
135+
130136
services.AddScoped(typeof(ICreateOpProcessor<>), typeof(CreateOpProcessor<>));
131137
services.AddScoped(typeof(ICreateOpProcessor<,>), typeof(CreateOpProcessor<,>));
138+
139+
services.AddScoped(typeof(IGetOpProcessor<>), typeof(GetOpProcessor<>));
140+
services.AddScoped(typeof(IGetOpProcessor<,>), typeof(GetOpProcessor<,>));
141+
132142
services.AddSingleton<IOperationProcessorResolver, OperationProcessorResolver>();
133143
services.AddSingleton<IGenericProcessorFactory, GenericProcessorFactory>();
134144
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
using System.Collections.Generic;
2+
using Newtonsoft.Json;
23

34
namespace JsonApiDotNetCore.Models.Operations
45
{
56
public class OperationsDocument
67
{
8+
public OperationsDocument() { }
9+
public OperationsDocument(List<Operation> operations)
10+
{
11+
Operations = operations;
12+
}
13+
14+
[JsonProperty("operations")]
715
public List<Operation> Operations { get; set; }
816
}
917
}

src/JsonApiDotNetCore/Services/IJsonApiContext.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Reflection;
43
using JsonApiDotNetCore.Builders;
54
using JsonApiDotNetCore.Configuration;
65
using JsonApiDotNetCore.Data;

src/JsonApiDotNetCore/Services/Operations/OperationProcessorResolver.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ namespace JsonApiDotNetCore.Services.Operations
88
public interface IOperationProcessorResolver
99
{
1010
IOpProcessor LocateCreateService(Operation operation);
11+
IOpProcessor LocateGeteService(Operation operation);
1112
}
1213

1314
public class OperationProcessorResolver : IOperationProcessorResolver
1415
{
1516
private readonly IGenericProcessorFactory _processorFactory;
1617
private readonly IJsonApiContext _context;
17-
private ConcurrentDictionary<string, IOpProcessor> _cachedProcessors = new ConcurrentDictionary<string, IOpProcessor>();
18+
private ConcurrentDictionary<string, IOpProcessor> _createOpProcessors = new ConcurrentDictionary<string, IOpProcessor>();
19+
private ConcurrentDictionary<string, IOpProcessor> _getOpProcessors = new ConcurrentDictionary<string, IOpProcessor>();
1820

1921
public OperationProcessorResolver(
2022
IGenericProcessorFactory processorFactory,
@@ -30,15 +32,32 @@ public IOpProcessor LocateCreateService(Operation operation)
3032
{
3133
var resource = operation.GetResourceTypeName();
3234

33-
if (_cachedProcessors.TryGetValue(resource, out IOpProcessor cachedProcessor))
35+
if (_createOpProcessors.TryGetValue(resource, out IOpProcessor cachedProcessor))
3436
return cachedProcessor;
3537

3638
var contextEntity = _context.ContextGraph.GetContextEntity(resource);
3739
var processor = _processorFactory.GetProcessor<IOpProcessor>(
3840
typeof(ICreateOpProcessor<,>), contextEntity.EntityType, contextEntity.IdentityType
3941
);
4042

41-
_cachedProcessors[resource] = processor;
43+
_createOpProcessors[resource] = processor;
44+
45+
return processor;
46+
}
47+
48+
public IOpProcessor LocateGeteService(Operation operation)
49+
{
50+
var resource = operation.GetResourceTypeName();
51+
52+
if (_getOpProcessors.TryGetValue(resource, out IOpProcessor cachedProcessor))
53+
return cachedProcessor;
54+
55+
var contextEntity = _context.ContextGraph.GetContextEntity(resource);
56+
var processor = _processorFactory.GetProcessor<IOpProcessor>(
57+
typeof(IGetOpProcessor<,>), contextEntity.EntityType, contextEntity.IdentityType
58+
);
59+
60+
_getOpProcessors[resource] = processor;
4261

4362
return processor;
4463
}

src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Internal;
5+
using JsonApiDotNetCore.Models;
36
using JsonApiDotNetCore.Models.Operations;
47
using JsonApiDotNetCore.Models.Pointers;
58

@@ -28,10 +31,11 @@ public async Task<List<Operation>> ProcessAsync(List<Operation> inputOps)
2831
// TODO: parse pointers:
2932
// locate all objects within the document and replace them
3033
var operationsPointer = new OperationsPointer();
31-
var replacer = new DocumentDataPointerReplacement<OperationsPointer, Operation>(op.DataObject);
32-
replacer.ReplacePointers(outputOps);
3334

34-
var processor = _processorResolver.LocateCreateService(op);
35+
ReplaceDataPointers(op.DataObject, outputOps);
36+
ReplaceRefPointers(op.Ref, outputOps);
37+
38+
var processor = GetOperationsProcessor(op);
3539
var resultOp = await processor.ProcessAsync(op);
3640

3741
if (resultOp != null)
@@ -40,5 +44,34 @@ public async Task<List<Operation>> ProcessAsync(List<Operation> inputOps)
4044

4145
return outputOps;
4246
}
47+
48+
private void ReplaceDataPointers(DocumentData dataObject, List<Operation> outputOps)
49+
{
50+
if (dataObject == null) return;
51+
52+
var replacer = new DocumentDataPointerReplacement<OperationsPointer, Operation>(dataObject);
53+
replacer.ReplacePointers(outputOps);
54+
}
55+
56+
private void ReplaceRefPointers(ResourceReference resourceRef, List<Operation> outputOps)
57+
{
58+
if (resourceRef == null) return;
59+
60+
var replacer = new ResourceRefPointerReplacement<OperationsPointer, Operation>(resourceRef);
61+
replacer.ReplacePointers(outputOps);
62+
}
63+
64+
private IOpProcessor GetOperationsProcessor(Operation op)
65+
{
66+
switch (op.Op)
67+
{
68+
case OperationCode.add:
69+
return _processorResolver.LocateCreateService(op);
70+
case OperationCode.get:
71+
return _processorResolver.LocateGeteService(op);
72+
default:
73+
throw new JsonApiException(400, $"'{op.Op}' is not a valid operation code");
74+
}
75+
}
4376
}
4477
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using JsonApiDotNetCore.Builders;
4+
using JsonApiDotNetCore.Internal;
5+
using JsonApiDotNetCore.Models;
6+
using JsonApiDotNetCore.Models.Operations;
7+
using JsonApiDotNetCore.Serialization;
8+
using JsonApiDotNetCore.Services;
9+
10+
namespace JsonApiDotNetCore.Services.Operations.Processors
11+
{
12+
public interface IGetOpProcessor<T> : IOpProcessor
13+
where T : class, IIdentifiable<int>
14+
{ }
15+
16+
public interface IGetOpProcessor<T, TId> : IOpProcessor
17+
where T : class, IIdentifiable<TId>
18+
{ }
19+
20+
public class GetOpProcessor<T> : GetOpProcessor<T, int>
21+
where T : class, IIdentifiable<int>
22+
{
23+
public GetOpProcessor(
24+
IGetAllService<T, int> service,
25+
IJsonApiDeSerializer deSerializer,
26+
IDocumentBuilder documentBuilder,
27+
IContextGraph contextGraph,
28+
IJsonApiContext jsonApiContext
29+
) : base(service, deSerializer, documentBuilder, contextGraph, jsonApiContext)
30+
{ }
31+
}
32+
33+
public class GetOpProcessor<T, TId> : IGetOpProcessor<T, TId>
34+
where T : class, IIdentifiable<TId>
35+
{
36+
private readonly IGetAllService<T, TId> _service;
37+
private readonly IJsonApiDeSerializer _deSerializer;
38+
private readonly IDocumentBuilder _documentBuilder;
39+
private readonly IContextGraph _contextGraph;
40+
private readonly IJsonApiContext _jsonApiContext;
41+
42+
public GetOpProcessor(
43+
IGetAllService<T, TId> service,
44+
IJsonApiDeSerializer deSerializer,
45+
IDocumentBuilder documentBuilder,
46+
IContextGraph contextGraph,
47+
IJsonApiContext jsonApiContext)
48+
{
49+
_service = service;
50+
_deSerializer = deSerializer;
51+
_documentBuilder = documentBuilder;
52+
_contextGraph = contextGraph;
53+
_jsonApiContext = jsonApiContext.ApplyContext<T>(this);
54+
}
55+
56+
public async Task<Operation> ProcessAsync(Operation operation)
57+
{
58+
var result = await _service.GetAsync();
59+
60+
var operationResult = new Operation
61+
{
62+
Op = OperationCode.add
63+
};
64+
65+
var operations = new List<DocumentData>();
66+
foreach (var resource in result)
67+
{
68+
var doc = _documentBuilder.GetData(
69+
_contextGraph.GetContextEntity(operation.GetResourceTypeName()),
70+
resource);
71+
operations.Add(doc);
72+
}
73+
74+
operationResult.Data = operations;
75+
76+
return operationResult;
77+
}
78+
}
79+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Models.Pointers;
3+
using Newtonsoft.Json.Linq;
4+
using JsonApiDotNetCore.Extensions;
5+
using JsonApiDotNetCore.Models.Operations;
6+
7+
namespace JsonApiDotNetCore.Services.Operations
8+
{
9+
public class ResourceRefPointerReplacement<TPointer, TPointerBase>
10+
where TPointer : Pointer<TPointerBase>, new()
11+
{
12+
private readonly ResourceReference _ref;
13+
14+
public ResourceRefPointerReplacement(ResourceReference data)
15+
{
16+
_ref = data;
17+
}
18+
19+
public void ReplacePointers(List<TPointerBase> parentDoc)
20+
{
21+
_ref.Id = GetPointerValue(_ref.Id, parentDoc);
22+
_ref.Type = GetPointerValue(_ref.Type, parentDoc);
23+
}
24+
25+
private object GetPointerValue(object reference, List<TPointerBase> parentDoc)
26+
{
27+
if (reference is JObject jObj)
28+
if (jObj.TryParse<TPointer, TPointerBase>(Pointer<TPointerBase>.JsonSchema, out Pointer<TPointerBase> pointer))
29+
return pointer.GetValue(parentDoc);
30+
31+
return reference;
32+
}
33+
}
34+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Collections.Generic;
2+
using System.Net;
3+
using System.Threading.Tasks;
4+
using JsonApiDotNetCore.Models.Operations;
5+
using Microsoft.EntityFrameworkCore;
6+
using OperationsExample.Data;
7+
using Xunit;
8+
9+
namespace OperationsExampleTests
10+
{
11+
[Collection("WebHostCollection")]
12+
public class GetTests
13+
{
14+
private readonly Fixture _fixture;
15+
16+
public GetTests(Fixture fixture)
17+
{
18+
_fixture = fixture;
19+
}
20+
21+
[Fact]
22+
public async Task Can_Get_Articles()
23+
{
24+
// arrange
25+
var context = _fixture.GetService<AppDbContext>();
26+
var articles = await context.Articles.ToListAsync();
27+
28+
var content = new
29+
{
30+
operations = new[] {
31+
new Dictionary<string, object> {
32+
{ "op", "get"},
33+
{ "ref", new { type = "articles" } }
34+
}
35+
}
36+
};
37+
38+
// act
39+
var result = await _fixture.PatchAsync<OperationsDocument>("api/bulk", content);
40+
41+
// assert
42+
Assert.NotNull(result.response);
43+
Assert.NotNull(result.data);
44+
Assert.Equal(HttpStatusCode.OK, result.response.StatusCode);
45+
Assert.Equal(1, result.data.Operations.Count);
46+
Assert.Equal(articles.Count, result.data.Operations[0].DataList.Count);
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)