Skip to content

Commit 7a95169

Browse files
authored
Merge pull request #61 from Research-Institute/feature/request-meta
Feature/request meta
2 parents 3d51f1c + 5c1a148 commit 7a95169

35 files changed

+627
-241
lines changed

.vscode/launch.json

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,11 @@
11
{
22
"version": "0.2.0",
33
"configurations": [
4-
{
5-
"name": ".NET Core Launch (web)",
6-
"type": "coreclr",
7-
"request": "launch",
8-
"preLaunchTask": "build",
9-
"program": "${workspaceRoot}/src/JsonApiDotNetCoreExample/bin/Debug/netcoreapp1.0/JsonApiDotNetCoreExample.dll",
10-
"args": [],
11-
"cwd": "${workspaceRoot}/src/JsonApiDotNetCoreExample",
12-
"stopAtEntry": false,
13-
"launchBrowser": {
14-
"enabled": false,
15-
"args": "${auto-detect-url}",
16-
"windows": {
17-
"command": "cmd.exe",
18-
"args": "/C start ${auto-detect-url}"
19-
},
20-
"osx": {
21-
"command": "open"
22-
},
23-
"linux": {
24-
"command": "xdg-open"
25-
}
26-
},
27-
"env": {
28-
"ASPNETCORE_ENVIRONMENT": "Development"
29-
},
30-
"sourceFileMap": {
31-
"/Views": "${workspaceRoot}/Views"
32-
}
33-
},
344
{
355
"name": ".NET Core Attach",
366
"type": "coreclr",
377
"request": "attach",
38-
"processId": "${command.pickProcess}"
8+
"processId": "${command:pickProcess}"
399
}
4010
]
4111
}

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ Install-Package JsonApiDotnetCore
4444

4545
- project.json
4646
```json
47-
"JsonApiDotNetCore": "1.1.0"
47+
"JsonApiDotNetCore": "1.2.0"
4848
```
4949

5050
- *.csproj
5151
```xml
5252
<ItemGroup>
5353
<!-- ... -->
54-
<PackageReference Include="JsonApiDotNetCore" Version="1.1.0" />
54+
<PackageReference Include="JsonApiDotNetCore" Version="1.2.0" />
5555
</ItemGroup>
5656
```
5757

@@ -326,6 +326,10 @@ Resources can be sorted by an attribute:
326326

327327
### Meta
328328

329+
Meta objects can be assigned in two ways:
330+
- Resource meta
331+
- Request Meta
332+
329333
Resource meta can be defined by implementing `IHasMeta` on the model class:
330334

331335
```csharp
@@ -343,6 +347,9 @@ public class Person : Identifiable<int>, IHasMeta
343347
}
344348
```
345349

350+
Request Meta can be added by injecting a service that implements `IRequestMeta`.
351+
In the event of a key collision, the Request Meta will take precendence.
352+
346353
### Client Generated Ids
347354

348355
By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,25 @@
88

99
namespace JsonApiDotNetCore.Builders
1010
{
11-
public class DocumentBuilder
11+
public class DocumentBuilder : IDocumentBuilder
1212
{
1313
private IJsonApiContext _jsonApiContext;
1414
private IContextGraph _contextGraph;
15+
private readonly IRequestMeta _requestMeta;
1516

1617
public DocumentBuilder(IJsonApiContext jsonApiContext)
1718
{
1819
_jsonApiContext = jsonApiContext;
1920
_contextGraph = jsonApiContext.ContextGraph;
2021
}
2122

23+
public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
24+
{
25+
_jsonApiContext = jsonApiContext;
26+
_contextGraph = jsonApiContext.ContextGraph;
27+
_requestMeta = requestMeta;
28+
}
29+
2230
public Document Build(IIdentifiable entity)
2331
{
2432
var contextEntity = _contextGraph.GetContextEntity(entity.GetType());
@@ -62,16 +70,19 @@ public Documents Build(IEnumerable<IIdentifiable> entities)
6270
private Dictionary<string, object> _getMeta(IIdentifiable entity)
6371
{
6472
if (entity == null) return null;
65-
66-
var meta = new Dictionary<string, object>();
67-
var metaEntity = entity as IHasMeta;
6873

69-
if(metaEntity != null)
70-
meta = metaEntity.GetMeta(_jsonApiContext);
74+
var builder = _jsonApiContext.MetaBuilder;
75+
76+
if(entity is IHasMeta metaEntity)
77+
builder.Add(metaEntity.GetMeta(_jsonApiContext));
7178

7279
if(_jsonApiContext.Options.IncludeTotalRecordCount)
73-
meta["total-records"] = _jsonApiContext.PageManager.TotalRecords;
80+
builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords);
7481

82+
if(_requestMeta != null)
83+
builder.Add(_requestMeta.GetMeta());
84+
85+
var meta = builder.Build();
7586
if(meta.Count > 0) return meta;
7687
return null;
7788
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Models;
3+
4+
namespace JsonApiDotNetCore.Builders
5+
{
6+
public interface IDocumentBuilder
7+
{
8+
Document Build(IIdentifiable entity);
9+
Documents Build(IEnumerable<IIdentifiable> entities);
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
3+
namespace JsonApiDotNetCore.Builders
4+
{
5+
public interface IMetaBuilder
6+
{
7+
void Add(string key, object value);
8+
void Add(Dictionary<string,object> values);
9+
Dictionary<string, object> Build();
10+
}
11+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace JsonApiDotNetCore.Builders
5+
{
6+
public class MetaBuilder : IMetaBuilder
7+
{
8+
private Dictionary<string, object> _meta = new Dictionary<string, object>();
9+
10+
public void Add(string key, object value)
11+
{
12+
_meta[key] = value;
13+
}
14+
15+
/// <summary>
16+
/// Joins the new dictionary with the current one. In the event of a key collision,
17+
/// the new value will override the old.
18+
/// </summary>
19+
public void Add(Dictionary<string,object> values)
20+
{
21+
_meta = values.Keys.Union(_meta.Keys)
22+
.ToDictionary(key => key,
23+
key => values.ContainsKey(key) ? values[key] : _meta[key]);
24+
}
25+
26+
public Dictionary<string, object> Build()
27+
{
28+
return _meta;
29+
}
30+
}
31+
}

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class DefaultEntityRepository<TEntity, TId>
3232
private readonly DbSet<TEntity> _dbSet;
3333
private readonly ILogger _logger;
3434
private readonly IJsonApiContext _jsonApiContext;
35+
private readonly IGenericProcessorFactory _genericProcessorFactory;
3536

3637
public DefaultEntityRepository(
3738
DbContext context,
@@ -42,6 +43,7 @@ public DefaultEntityRepository(
4243
_dbSet = context.GetDbSet<TEntity>();
4344
_jsonApiContext = jsonApiContext;
4445
_logger = loggerFactory.CreateLogger<DefaultEntityRepository<TEntity, TId>>();
46+
_genericProcessorFactory = _jsonApiContext.GenericProcessorFactory;
4547
}
4648

4749
public virtual IQueryable<TEntity> Get()
@@ -110,7 +112,7 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
110112

111113
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds)
112114
{
113-
var genericProcessor = GenericProcessorFactory.GetProcessor(relationship.Type, _context);
115+
var genericProcessor = _genericProcessorFactory.GetProcessor(relationship.Type);
114116
await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds);
115117
}
116118

src/JsonApiDotNetCore/Extensions/ServiceProviderExtensions.cs renamed to src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
2+
using JsonApiDotNetCore.Builders;
23
using JsonApiDotNetCore.Configuration;
34
using JsonApiDotNetCore.Data;
45
using JsonApiDotNetCore.Formatters;
56
using JsonApiDotNetCore.Internal;
7+
using JsonApiDotNetCore.Serialization;
68
using JsonApiDotNetCore.Services;
79
using Microsoft.AspNetCore.Http;
810
using Microsoft.AspNetCore.Mvc;
@@ -11,7 +13,7 @@
1113

1214
namespace JsonApiDotNetCore.Extensions
1315
{
14-
public static class ServiceProviderExtensions
16+
public static class IServiceCollectionExtensions
1517
{
1618
public static void AddJsonApi<TContext>(this IServiceCollection services)
1719
where TContext : DbContext
@@ -54,6 +56,15 @@ public static void AddJsonApiInternals<TContext>(this IServiceCollection service
5456
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
5557

5658
services.AddScoped<JsonApiRouteHandler>();
59+
60+
services.AddScoped<IMetaBuilder, MetaBuilder>();
61+
services.AddScoped<IDocumentBuilder, DocumentBuilder>();
62+
services.AddScoped<IJsonApiSerializer, JsonApiSerializer>();
63+
services.AddScoped<IJsonApiWriter, JsonApiWriter>();
64+
services.AddScoped<IJsonApiDeSerializer, JsonApiDeSerializer>();
65+
services.AddScoped<IJsonApiReader, JsonApiReader>();
66+
services.AddScoped<IGenericProcessorFactory, GenericProcessorFactory>();
67+
services.AddScoped(typeof(GenericProcessor<>));
5768
}
5869

5970
public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions jsonApiOptions)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.AspNetCore.Mvc.Formatters;
3+
4+
namespace JsonApiDotNetCore.Formatters
5+
{
6+
public interface IJsonApiReader
7+
{
8+
Task<InputFormatterResult> ReadAsync(InputFormatterContext context);
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.AspNetCore.Mvc.Formatters;
3+
4+
namespace JsonApiDotNetCore.Formatters
5+
{
6+
public interface IJsonApiWriter
7+
{
8+
Task WriteAsync(OutputFormatterWriteContext context);
9+
}
10+
}
Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
using System;
2-
using System.IO;
32
using System.Threading.Tasks;
4-
using JsonApiDotNetCore.Serialization;
5-
using JsonApiDotNetCore.Services;
63
using Microsoft.AspNetCore.Mvc.Formatters;
74
using Microsoft.Extensions.DependencyInjection;
8-
using Microsoft.Extensions.Logging;
9-
using Newtonsoft.Json;
10-
using Microsoft.EntityFrameworkCore;
115

126
namespace JsonApiDotNetCore.Formatters
137
{
@@ -23,55 +17,10 @@ public bool CanRead(InputFormatterContext context)
2317
return contentTypeString == "application/vnd.api+json";
2418
}
2519

26-
public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
20+
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
2721
{
28-
if (context == null)
29-
throw new ArgumentNullException(nameof(context));
30-
31-
var request = context.HttpContext.Request;
32-
33-
if (request.ContentLength == 0)
34-
{
35-
return InputFormatterResult.SuccessAsync(null);
36-
}
37-
38-
var loggerFactory = GetService<ILoggerFactory>(context);
39-
var logger = loggerFactory?.CreateLogger<JsonApiInputFormatter>();
40-
41-
var dbContext = GetService<DbContext>(context);
42-
43-
try
44-
{
45-
var body = GetRequestBody(context.HttpContext.Request.Body);
46-
var jsonApiContext = GetService<IJsonApiContext>(context);
47-
var model = jsonApiContext.IsRelationshipPath ?
48-
JsonApiDeSerializer.DeserializeRelationship(body, jsonApiContext) :
49-
JsonApiDeSerializer.Deserialize(body, jsonApiContext, dbContext);
50-
51-
if(model == null)
52-
logger?.LogError("An error occurred while de-serializing the payload");
53-
54-
return InputFormatterResult.SuccessAsync(model);
55-
}
56-
catch (JsonSerializationException ex)
57-
{
58-
logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload");
59-
context.HttpContext.Response.StatusCode = 422;
60-
return InputFormatterResult.FailureAsync();
61-
}
62-
}
63-
64-
private string GetRequestBody(Stream body)
65-
{
66-
using (var reader = new StreamReader(body))
67-
{
68-
return reader.ReadToEnd();
69-
}
70-
}
71-
72-
private TService GetService<TService>(InputFormatterContext context)
73-
{
74-
return context.HttpContext.RequestServices.GetService<TService>();
22+
var reader = context.HttpContext.RequestServices.GetService<IJsonApiReader>();
23+
return await reader.ReadAsync(context);
7524
}
7625
}
7726
}

0 commit comments

Comments
 (0)