Skip to content

feat(DocumentBuilder): allow for output attribute filtering #324

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 1 commit into from
Jul 3, 2018
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using JsonApiDotNetCoreExample.Models;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCoreExample.Controllers
{
public class UsersController : JsonApiController<User>
{
public UsersController(
IJsonApiContext jsonApiContext,
IResourceService<User> resourceService,
ILoggerFactory loggerFactory)
: base(jsonApiContext, resourceService, loggerFactory)
{ }
}
}
2 changes: 1 addition & 1 deletion src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

public DbSet<Article> Articles { get; set; }
public DbSet<Author> Authors { get; set; }

public DbSet<NonJsonApiResource> NonJsonApiResources { get; set; }
public DbSet<User> Users { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;

namespace JsonApiDotNetCoreExample.Migrations
Expand Down
10 changes: 10 additions & 0 deletions src/Examples/JsonApiDotNetCoreExample/Models/User.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using JsonApiDotNetCore.Models;

namespace JsonApiDotNetCoreExample.Models
{
public class User : Identifiable
{
[Attr("username")] public string Username { get; set; }
[Attr("password")] public string Password { get; set; }
}
}
12 changes: 12 additions & 0 deletions src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCoreExample.Models;

namespace JsonApiDotNetCoreExample.Resources
{
public class UserResource : ResourceDefinition<User>
{
protected override List<AttrAttribute> OutputAttrs()
=> Remove(user => user.Password);
}
}
7 changes: 6 additions & 1 deletion src/Examples/JsonApiDotNetCoreExample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
using Microsoft.EntityFrameworkCore;
using JsonApiDotNetCore.Extensions;
using System;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCoreExample.Resources;
using JsonApiDotNetCoreExample.Models;

namespace JsonApiDotNetCoreExample
{
Expand Down Expand Up @@ -38,7 +41,9 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
options.Namespace = "api/v1";
options.DefaultPageSize = 5;
options.IncludeTotalRecordCount = true;
});
})
// TODO: this should be handled via auto-discovery
.AddScoped<ResourceDefinition<User>, UserResource>();

var provider = services.BuildServiceProvider();
var appContext = provider.GetRequiredService<AppDbContext>();
Expand Down
5 changes: 1 addition & 4 deletions src/Examples/ReportsExample/Controllers/ReportsController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
Expand Down
1 change: 0 additions & 1 deletion src/Examples/ReportsExample/Services/ReportService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using JsonApiDotNetCore.Services;
Expand Down
12 changes: 9 additions & 3 deletions src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public IContextGraph Build()
_entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType));

var graph = new ContextGraph(_entities, _usesDbContext, _validationResults);

return graph;
}

Expand All @@ -83,7 +82,8 @@ public IContextGraphBuilder AddResource<TResource, TId>(string pluralizedTypeNam
EntityType = entityType,
IdentityType = idType,
Attributes = GetAttributes(entityType),
Relationships = GetRelationships(entityType)
Relationships = GetRelationships(entityType),
ResourceType = GetResourceDefinitionType(entityType)
};

private Link GetLinkFlags(Type entityType)
Expand All @@ -104,8 +104,12 @@ protected virtual List<AttrAttribute> GetAttributes(Type entityType)
foreach (var prop in properties)
{
var attribute = (AttrAttribute)prop.GetCustomAttribute(typeof(AttrAttribute));
if (attribute == null) continue;
if (attribute == null)
continue;

attribute.InternalAttributeName = prop.Name;
attribute.PropertyInfo = prop;

attributes.Add(attribute);
}
return attributes;
Expand Down Expand Up @@ -136,6 +140,8 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope
return prop.PropertyType;
}

private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType);

public IContextGraphBuilder AddDbContext<T>() where T : DbContext
{
_usesDbContext = true;
Expand Down
28 changes: 21 additions & 7 deletions src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -14,22 +15,29 @@ public class DocumentBuilder : IDocumentBuilder
private readonly IContextGraph _contextGraph;
private readonly IRequestMeta _requestMeta;
private readonly DocumentBuilderOptions _documentBuilderOptions;
private readonly IScopedServiceProvider _scopedServiceProvider;

public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta = null, IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null)
public DocumentBuilder(
IJsonApiContext jsonApiContext,
IRequestMeta requestMeta = null,
IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null,
IScopedServiceProvider scopedServiceProvider = null)
{
_jsonApiContext = jsonApiContext;
_contextGraph = jsonApiContext.ContextGraph;
_requestMeta = requestMeta;
_documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions(); ;
_documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions();
_scopedServiceProvider = scopedServiceProvider;
}

public Document Build(IIdentifiable entity)
{
var contextEntity = _contextGraph.GetContextEntity(entity.GetType());

var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition;
var document = new Document
{
Data = GetData(contextEntity, entity),
Data = GetData(contextEntity, entity, resourceDefinition),
Meta = GetMeta(entity)
};

Expand All @@ -44,8 +52,8 @@ public Document Build(IIdentifiable entity)
public Documents Build(IEnumerable<IIdentifiable> entities)
{
var entityType = entities.GetElementType();

var contextEntity = _contextGraph.GetContextEntity(entityType);
var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition;

var enumeratedEntities = entities as IList<IIdentifiable> ?? entities.ToList();
var documents = new Documents
Expand All @@ -59,7 +67,7 @@ public Documents Build(IEnumerable<IIdentifiable> entities)

foreach (var entity in enumeratedEntities)
{
documents.Data.Add(GetData(contextEntity, entity));
documents.Data.Add(GetData(contextEntity, entity, resourceDefinition));
documents.Included = AppendIncludedObject(documents.Included, contextEntity, entity);
}

Expand Down Expand Up @@ -98,7 +106,11 @@ private List<DocumentData> AppendIncludedObject(List<DocumentData> includedObjec
return includedObject;
}

[Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")]
public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
=> GetData(contextEntity, entity, resourceDefinition: null);

public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null)
{
var data = new DocumentData
{
Expand All @@ -111,7 +123,8 @@ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)

data.Attributes = new Dictionary<string, object>();

contextEntity.Attributes.ForEach(attr =>
var resourceAttributes = resourceDefinition?.GetOutputAttrs(entity) ?? contextEntity.Attributes;
resourceAttributes.ForEach(attr =>
{
var attributeValue = attr.GetValue(entity);
if (ShouldIncludeAttribute(attr, attributeValue))
Expand Down Expand Up @@ -219,8 +232,9 @@ private DocumentData GetIncludedEntity(IIdentifiable entity)
if (entity == null) return null;

var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(entity.GetType());
var resourceDefinition = _scopedServiceProvider.GetService(contextEntity.ResourceType) as IResourceDefinition;

var data = GetData(contextEntity, entity);
var data = GetData(contextEntity, entity, resourceDefinition);

data.Attributes = new Dictionary<string, object>();

Expand Down
6 changes: 5 additions & 1 deletion src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
Expand All @@ -8,6 +9,9 @@ public interface IDocumentBuilder
{
Document Build(IIdentifiable entity);
Documents Build(IEnumerable<IIdentifiable> entities);

[Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")]
DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity);
DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null);
}
}
}
1 change: 0 additions & 1 deletion src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCore.Controllers
{
Expand Down
2 changes: 0 additions & 2 deletions src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCore.Controllers
{
Expand Down
18 changes: 18 additions & 0 deletions src/JsonApiDotNetCore/DependencyInjection/ServiceLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Threading;

namespace JsonApiDotNetCore.DependencyInjection
{
internal class ServiceLocator
{
public static AsyncLocal<IServiceProvider> _scopedProvider = new AsyncLocal<IServiceProvider>();
public static void Initialize(IServiceProvider serviceProvider) => _scopedProvider.Value = serviceProvider;

public static object GetService(Type type)
=> _scopedProvider.Value != null
? _scopedProvider.Value.GetService(type)
: throw new InvalidOperationException(
$"Service locator has not been initialized for the current asynchronous flow. Call {nameof(Initialize)} first."
);
}
}
1 change: 0 additions & 1 deletion src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Linq;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using Microsoft.EntityFrameworkCore;

Expand Down
29 changes: 29 additions & 0 deletions src/JsonApiDotNetCore/Internal/ContextEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,40 @@ namespace JsonApiDotNetCore.Internal
{
public class ContextEntity
{
/// <summary>
/// The exposed resource name
/// </summary>
public string EntityName { get; set; }

/// <summary>
/// The data model type
/// </summary>
public Type EntityType { get; set; }

/// <summary>
/// The identity member type
/// </summary>
public Type IdentityType { get; set; }

/// <summary>
/// The concrete <see cref="ResourceDefinition{T}"/> type.
/// We store this so that we don't need to re-compute the generic type.
/// </summary>
public Type ResourceType { get; set; }

/// <summary>
/// Exposed resource attributes
/// </summary>
public List<AttrAttribute> Attributes { get; set; }

/// <summary>
/// Exposed resource relationships
/// </summary>
public List<RelationshipAttribute> Relationships { get; set; }

/// <summary>
/// Links to include in resource responses
/// </summary>
public Link Links { get; set; } = Link.All;
}
}
4 changes: 3 additions & 1 deletion src/JsonApiDotNetCore/Internal/ContextGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ public class ContextGraph : IContextGraph
{
internal List<ContextEntity> Entities { get; }
internal List<ValidationResult> ValidationResults { get; }
internal static IContextGraph Instance { get; set; }

public ContextGraph() { }

public ContextGraph(List<ContextEntity> entities, bool usesDbContext)
{
Entities = entities;
UsesDbContext = usesDbContext;
ValidationResults = new List<ValidationResult>();
Instance = this;
}

// eventually, this is the planned public constructor
Expand All @@ -36,6 +37,7 @@ internal ContextGraph(List<ContextEntity> entities, bool usesDbContext, List<Val
Entities = entities;
UsesDbContext = usesDbContext;
ValidationResults = validationResults;
Instance = this;
}

public bool UsesDbContext { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Linq;
using System.Threading.Tasks;
using JsonApiDotNetCore.Data;
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Models;
using Microsoft.EntityFrameworkCore;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Http;
using System;

namespace JsonApiDotNetCore.Internal.Generics
Expand Down
1 change: 0 additions & 1 deletion src/JsonApiDotNetCore/Internal/JsonApiRouteHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using JsonApiDotNetCore.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing;

namespace JsonApiDotNetCore.Internal
Expand Down
1 change: 0 additions & 1 deletion src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Linq;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Linq;
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;

Expand Down
Loading