Skip to content
Prev Previous commit
Next Next commit
chore: renamed resourcegraphbuilder, took out some extensions, made b…
…enchmarks work again
  • Loading branch information
Harro van der Kroft committed May 27, 2019
commit 4a53c0056bba74c8967547ec365cb10ebe2cc4b4
11 changes: 6 additions & 5 deletions benchmarks/Query/QueryParser_Benchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using BenchmarkDotNet.Attributes.Jobs;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Managers.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Http.Internal;
Expand All @@ -21,14 +22,14 @@ public class QueryParser_Benchmarks {
private const string DESCENDING_SORT = "-" + ATTRIBUTE;

public QueryParser_Benchmarks() {
var controllerContextMock = new Mock<IControllerContext>();
controllerContextMock.Setup(m => m.RequestEntity).Returns(new ContextEntity {
var requestMock = new Mock<IRequestManager>();
requestMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity {
Attributes = new List<AttrAttribute> {
new AttrAttribute(ATTRIBUTE, ATTRIBUTE)
}
});
var options = new JsonApiOptions();
_queryParser = new BenchmarkFacade(controllerContextMock.Object, options);
_queryParser = new BenchmarkFacade(requestMock.Object, options);
}

[Benchmark]
Expand Down Expand Up @@ -58,8 +59,8 @@ private void Run(int iterations, Action action) {
// this facade allows us to expose and micro-benchmark protected methods
private class BenchmarkFacade : QueryParser {
public BenchmarkFacade(
IControllerContext controllerContext,
JsonApiOptions options) : base(controllerContext, options) { }
IRequestManager requestManager,
JsonApiOptions options) : base(requestManager, options) { }

public void _ParseSortParameters(string value) => base.ParseSortParameters(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@

namespace JsonApiDotNetCoreExample.Controllers
{
[DisableRoutingConvention]
[Route("custom/route/todo-items")]
[DisableRoutingConvention, Route("custom/route/todo-items")]
public class TodoItemsCustomController : CustomJsonApiController<TodoItem>
{
public TodoItemsCustomController(
Expand Down
81 changes: 81 additions & 0 deletions src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Graph;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCore.Builders
{
public interface IResourceGraphBuilder
{
/// <summary>
/// Construct the <see cref="ResourceGraph"/>
/// </summary>
IResourceGraph Build();

/// <summary>
/// Add a json:api resource
/// </summary>
/// <typeparam name="TResource">The resource model type</typeparam>
/// <param name="pluralizedTypeName">
/// The pluralized name that should be exposed by the API.
/// If nothing is specified, the configured name formatter will be used.
/// See <see cref="JsonApiOptions.ResourceNameFormatter" />.
/// </param>
IResourceGraphBuilder AddResource<TResource>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<int>;

IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null);

/// <summary>
/// Add a json:api resource
/// </summary>
/// <typeparam name="TResource">The resource model type</typeparam>
/// <typeparam name="TId">The resource model identifier type</typeparam>
/// <param name="pluralizedTypeName">
/// The pluralized name that should be exposed by the API.
/// If nothing is specified, the configured name formatter will be used.
/// See <see cref="JsonApiOptions.ResourceNameFormatter" />.
/// </param>
IResourceGraphBuilder AddResource<TResource, TId>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<TId>;

/// <summary>
/// Add a Json:Api resource
/// </summary>
/// <param name="entityType">The resource model type</param>
/// <param name="idType">The resource model identifier type</param>
/// <param name="pluralizedTypeName">
/// The pluralized name that should be exposed by the API.
/// If nothing is specified, the configured name formatter will be used.
/// See <see cref="JsonApiOptions.ResourceNameFormatter" />.
/// </param>
IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null);

/// <summary>
/// Add all the models that are part of the provided <see cref="DbContext" />
/// that also implement <see cref="IIdentifiable"/>
/// </summary>
/// <typeparam name="T">The <see cref="DbContext"/> implementation type.</typeparam>
IResourceGraphBuilder AddDbContext<T>() where T : DbContext;

/// <summary>
/// Specify the <see cref="IResourceNameFormatter"/> used to format resource names.
/// </summary>
/// <param name="resourceNameFormatter">Formatter used to define exposed resource names by convention.</param>
IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter);

/// <summary>
/// Which links to include. Defaults to <see cref="Link.All"/>.
/// </summary>
Link DocumentLinks { get; set; }


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,18 @@
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCore.Builders
{
public interface IResourceGraphBuilder
{
/// <summary>
/// Construct the <see cref="ResourceGraph"/>
/// </summary>
IResourceGraph Build();

/// <summary>
/// Add a json:api resource
/// </summary>
/// <typeparam name="TResource">The resource model type</typeparam>
/// <param name="pluralizedTypeName">
/// The pluralized name that should be exposed by the API.
/// If nothing is specified, the configured name formatter will be used.
/// See <see cref="JsonApiOptions.ResourceNameFormatter" />.
/// </param>
IResourceGraphBuilder AddResource<TResource>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<int>;

/// <summary>
/// Add a json:api resource
/// </summary>
/// <typeparam name="TResource">The resource model type</typeparam>
/// <typeparam name="TId">The resource model identifier type</typeparam>
/// <param name="pluralizedTypeName">
/// The pluralized name that should be exposed by the API.
/// If nothing is specified, the configured name formatter will be used.
/// See <see cref="JsonApiOptions.ResourceNameFormatter" />.
/// </param>
IResourceGraphBuilder AddResource<TResource, TId>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<TId>;

/// <summary>
/// Add a json:api resource
/// </summary>
/// <param name="entityType">The resource model type</param>
/// <param name="idType">The resource model identifier type</param>
/// <param name="pluralizedTypeName">
/// The pluralized name that should be exposed by the API.
/// If nothing is specified, the configured name formatter will be used.
/// See <see cref="JsonApiOptions.ResourceNameFormatter" />.
/// </param>
IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null);

/// <summary>
/// Add all the models that are part of the provided <see cref="DbContext" />
/// that also implement <see cref="IIdentifiable"/>
/// </summary>
/// <typeparam name="T">The <see cref="DbContext"/> implementation type.</typeparam>
IResourceGraphBuilder AddDbContext<T>() where T : DbContext;

/// <summary>
/// Specify the <see cref="IResourceNameFormatter"/> used to format resource names.
/// </summary>
/// <param name="resourceNameFormatter">Formatter used to define exposed resource names by convention.</param>
IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter);

/// <summary>
/// Which links to include. Defaults to <see cref="Link.All"/>.
/// </summary>
Link DocumentLinks { get; set; }
}

public class ResourceGraphBuilder : IResourceGraphBuilder
{
private List<ContextEntity> _entities = new List<ContextEntity>();
private List<ValidationResult> _validationResults = new List<ValidationResult>();
private Dictionary<Type, List<Type>> _controllerMapper = new Dictionary<Type, List<Type>>() { };
private List<Type> _undefinedMapper = new List<Type>() { };
private bool _usesDbContext;
private IResourceNameFormatter _resourceNameFormatter = JsonApiOptions.ResourceNameFormatter;

Expand All @@ -91,7 +33,23 @@ public IResourceGraph Build()
// this must be done at build so that call order doesn't matter
_entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType));

var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults);
List<ControllerModelMap> controllerContexts = new List<ControllerModelMap>() { };
foreach(var cm in _controllerMapper)
{
var model = cm.Key;
foreach(var controller in cm.Value)
{
var routeAttribute = controller.GetCustomAttribute<RouteAttribute>();

controllerContexts.Add(new ControllerModelMap
{
Model = model,
Controller = controller,
Path = routeAttribute?.Template
});
}
}
var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults, controllerContexts);
return graph;
}

Expand Down Expand Up @@ -183,16 +141,17 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
attribute.Type = GetRelationshipType(attribute, prop);
attributes.Add(attribute);

if (attribute is HasManyThroughAttribute hasManyThroughAttribute) {
if (attribute is HasManyThroughAttribute hasManyThroughAttribute)
{
var throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.InternalThroughName);
if(throughProperty == null)
if (throughProperty == null)
throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}'. Type does not contain a property named '{hasManyThroughAttribute.InternalThroughName}'.");
if(throughProperty.PropertyType.Implements<IList>() == false)

if (throughProperty.PropertyType.Implements<IList>() == false)
throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}.{throughProperty.Name}'. Property type does not implement IList.");

// assumption: the property should be a generic collection, e.g. List<ArticleTag>
if(throughProperty.PropertyType.IsGenericType == false)
if (throughProperty.PropertyType.IsGenericType == false)
throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}'. Expected through entity to be a generic type, such as List<{prop.PropertyType}>.");

// Article → List<ArticleTag>
Expand All @@ -202,7 +161,7 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
hasManyThroughAttribute.ThroughType = throughProperty.PropertyType.GetGenericArguments()[0];

var throughProperties = hasManyThroughAttribute.ThroughType.GetProperties();

// ArticleTag.Article
hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType == entityType)
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {entityType}");
Expand All @@ -215,7 +174,7 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
// Article → ArticleTag.Tag
hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.Type)
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {hasManyThroughAttribute.Type}");

// ArticleTag.TagId
var rightIdPropertyName = JsonApiOptions.RelatedIdMapper.GetRelatedIdPropertyName(hasManyThroughAttribute.RightProperty.Name);
hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName)
Expand Down Expand Up @@ -305,5 +264,24 @@ public IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNam
_resourceNameFormatter = resourceNameFormatter;
return this;
}

public IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null)
{
if (model == null)
{
_undefinedMapper.Add(controller);
return this;

}
if (_controllerMapper.Keys.Contains(model))
{
_controllerMapper[model].Add(controller);
}
else
{
_controllerMapper.Add(model, new List<Type>() { controller });
}
return this;
}
}
}
1 change: 1 addition & 0 deletions src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class JsonApiOptions : IJsonApiOptions
/// <summary>
/// The graph of all resources exposed by this application.
/// </summary>
[Obsolete("Use the standalone resourcegraph")]
public IResourceGraph ResourceGraph { get; set; }

/// <summary>
Expand Down
Loading