Skip to content

Feature/request meta #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 1 addition & 31 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,41 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceRoot}/src/JsonApiDotNetCoreExample/bin/Debug/netcoreapp1.0/JsonApiDotNetCoreExample.dll",
"args": [],
"cwd": "${workspaceRoot}/src/JsonApiDotNetCoreExample",
"stopAtEntry": false,
"launchBrowser": {
"enabled": false,
"args": "${auto-detect-url}",
"windows": {
"command": "cmd.exe",
"args": "/C start ${auto-detect-url}"
},
"osx": {
"command": "open"
},
"linux": {
"command": "xdg-open"
}
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceRoot}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command.pickProcess}"
"processId": "${command:pickProcess}"
}
]
}
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ Install-Package JsonApiDotnetCore

- project.json
```json
"JsonApiDotNetCore": "1.1.0"
"JsonApiDotNetCore": "1.2.0"
```

- *.csproj
```xml
<ItemGroup>
<!-- ... -->
<PackageReference Include="JsonApiDotNetCore" Version="1.1.0" />
<PackageReference Include="JsonApiDotNetCore" Version="1.2.0" />
</ItemGroup>
```

Expand Down Expand Up @@ -326,6 +326,10 @@ Resources can be sorted by an attribute:

### Meta

Meta objects can be assigned in two ways:
- Resource meta
- Request Meta

Resource meta can be defined by implementing `IHasMeta` on the model class:

```csharp
Expand All @@ -343,6 +347,9 @@ public class Person : Identifiable<int>, IHasMeta
}
```

Request Meta can be added by injecting a service that implements `IRequestMeta`.
In the event of a key collision, the Request Meta will take precendence.

### Client Generated Ids

By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is
Expand Down
25 changes: 18 additions & 7 deletions src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,25 @@

namespace JsonApiDotNetCore.Builders
{
public class DocumentBuilder
public class DocumentBuilder : IDocumentBuilder
{
private IJsonApiContext _jsonApiContext;
private IContextGraph _contextGraph;
private readonly IRequestMeta _requestMeta;

public DocumentBuilder(IJsonApiContext jsonApiContext)
{
_jsonApiContext = jsonApiContext;
_contextGraph = jsonApiContext.ContextGraph;
}

public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
{
_jsonApiContext = jsonApiContext;
_contextGraph = jsonApiContext.ContextGraph;
_requestMeta = requestMeta;
}

public Document Build(IIdentifiable entity)
{
var contextEntity = _contextGraph.GetContextEntity(entity.GetType());
Expand Down Expand Up @@ -62,16 +70,19 @@ public Documents Build(IEnumerable<IIdentifiable> entities)
private Dictionary<string, object> _getMeta(IIdentifiable entity)
{
if (entity == null) return null;

var meta = new Dictionary<string, object>();
var metaEntity = entity as IHasMeta;

if(metaEntity != null)
meta = metaEntity.GetMeta(_jsonApiContext);
var builder = _jsonApiContext.MetaBuilder;

if(entity is IHasMeta metaEntity)
builder.Add(metaEntity.GetMeta(_jsonApiContext));

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

if(_requestMeta != null)
builder.Add(_requestMeta.GetMeta());

var meta = builder.Build();
if(meta.Count > 0) return meta;
return null;
}
Expand Down
11 changes: 11 additions & 0 deletions src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using JsonApiDotNetCore.Models;

namespace JsonApiDotNetCore.Builders
{
public interface IDocumentBuilder
{
Document Build(IIdentifiable entity);
Documents Build(IEnumerable<IIdentifiable> entities);
}
}
11 changes: 11 additions & 0 deletions src/JsonApiDotNetCore/Builders/IMetaBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;

namespace JsonApiDotNetCore.Builders
{
public interface IMetaBuilder
{
void Add(string key, object value);
void Add(Dictionary<string,object> values);
Dictionary<string, object> Build();
}
}
31 changes: 31 additions & 0 deletions src/JsonApiDotNetCore/Builders/MetaBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;

namespace JsonApiDotNetCore.Builders
{
public class MetaBuilder : IMetaBuilder
{
private Dictionary<string, object> _meta = new Dictionary<string, object>();

public void Add(string key, object value)
{
_meta[key] = value;
}

/// <summary>
/// Joins the new dictionary with the current one. In the event of a key collision,
/// the new value will override the old.
/// </summary>
public void Add(Dictionary<string,object> values)
{
_meta = values.Keys.Union(_meta.Keys)
.ToDictionary(key => key,
key => values.ContainsKey(key) ? values[key] : _meta[key]);
}

public Dictionary<string, object> Build()
{
return _meta;
}
}
}
4 changes: 3 additions & 1 deletion src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class DefaultEntityRepository<TEntity, TId>
private readonly DbSet<TEntity> _dbSet;
private readonly ILogger _logger;
private readonly IJsonApiContext _jsonApiContext;
private readonly IGenericProcessorFactory _genericProcessorFactory;

public DefaultEntityRepository(
DbContext context,
Expand All @@ -42,6 +43,7 @@ public DefaultEntityRepository(
_dbSet = context.GetDbSet<TEntity>();
_jsonApiContext = jsonApiContext;
_logger = loggerFactory.CreateLogger<DefaultEntityRepository<TEntity, TId>>();
_genericProcessorFactory = _jsonApiContext.GenericProcessorFactory;
}

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

public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds)
{
var genericProcessor = GenericProcessorFactory.GetProcessor(relationship.Type, _context);
var genericProcessor = _genericProcessorFactory.GetProcessor(relationship.Type);
await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using JsonApiDotNetCore.Builders;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Data;
using JsonApiDotNetCore.Formatters;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -11,7 +13,7 @@

namespace JsonApiDotNetCore.Extensions
{
public static class ServiceProviderExtensions
public static class IServiceCollectionExtensions
{
public static void AddJsonApi<TContext>(this IServiceCollection services)
where TContext : DbContext
Expand Down Expand Up @@ -54,6 +56,15 @@ public static void AddJsonApiInternals<TContext>(this IServiceCollection service
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

services.AddScoped<JsonApiRouteHandler>();

services.AddScoped<IMetaBuilder, MetaBuilder>();
services.AddScoped<IDocumentBuilder, DocumentBuilder>();
services.AddScoped<IJsonApiSerializer, JsonApiSerializer>();
services.AddScoped<IJsonApiWriter, JsonApiWriter>();
services.AddScoped<IJsonApiDeSerializer, JsonApiDeSerializer>();
services.AddScoped<IJsonApiReader, JsonApiReader>();
services.AddScoped<IGenericProcessorFactory, GenericProcessorFactory>();
services.AddScoped(typeof(GenericProcessor<>));
}

public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions jsonApiOptions)
Expand Down
10 changes: 10 additions & 0 deletions src/JsonApiDotNetCore/Formatters/IJsonApiReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace JsonApiDotNetCore.Formatters
{
public interface IJsonApiReader
{
Task<InputFormatterResult> ReadAsync(InputFormatterContext context);
}
}
10 changes: 10 additions & 0 deletions src/JsonApiDotNetCore/Formatters/IJsonApiWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace JsonApiDotNetCore.Formatters
{
public interface IJsonApiWriter
{
Task WriteAsync(OutputFormatterWriteContext context);
}
}
57 changes: 3 additions & 54 deletions src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.EntityFrameworkCore;

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

public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));

var request = context.HttpContext.Request;

if (request.ContentLength == 0)
{
return InputFormatterResult.SuccessAsync(null);
}

var loggerFactory = GetService<ILoggerFactory>(context);
var logger = loggerFactory?.CreateLogger<JsonApiInputFormatter>();

var dbContext = GetService<DbContext>(context);

try
{
var body = GetRequestBody(context.HttpContext.Request.Body);
var jsonApiContext = GetService<IJsonApiContext>(context);
var model = jsonApiContext.IsRelationshipPath ?
JsonApiDeSerializer.DeserializeRelationship(body, jsonApiContext) :
JsonApiDeSerializer.Deserialize(body, jsonApiContext, dbContext);

if(model == null)
logger?.LogError("An error occurred while de-serializing the payload");

return InputFormatterResult.SuccessAsync(model);
}
catch (JsonSerializationException ex)
{
logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload");
context.HttpContext.Response.StatusCode = 422;
return InputFormatterResult.FailureAsync();
}
}

private string GetRequestBody(Stream body)
{
using (var reader = new StreamReader(body))
{
return reader.ReadToEnd();
}
}

private TService GetService<TService>(InputFormatterContext context)
{
return context.HttpContext.RequestServices.GetService<TService>();
var reader = context.HttpContext.RequestServices.GetService<IJsonApiReader>();
return await reader.ReadAsync(context);
}
}
}
Loading