diff --git a/src/Examples/GettingStarted/Controllers/ArticlesController.cs b/src/Examples/GettingStarted/Controllers/ArticlesController.cs index 2d1567659b..135391e9eb 100644 --- a/src/Examples/GettingStarted/Controllers/ArticlesController.cs +++ b/src/Examples/GettingStarted/Controllers/ArticlesController.cs @@ -10,9 +10,8 @@ public class ArticlesController : JsonApiController
{ public ArticlesController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService
resourceService) - : base(jsonApiOptions, resourceGraph, resourceService) + : base(jsonApiOptions, resourceService) { } } } diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs index 8d8ee58a2a..0725bbebaa 100644 --- a/src/Examples/GettingStarted/Controllers/PeopleController.cs +++ b/src/Examples/GettingStarted/Controllers/PeopleController.cs @@ -10,9 +10,8 @@ public class PeopleController : JsonApiController { public PeopleController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService) - : base(jsonApiOptions, resourceGraph, resourceService) + : base(jsonApiOptions, resourceService) { } } } diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs index d31458250c..aa19ac3f71 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs @@ -6,7 +6,7 @@ namespace GettingStarted.ResourceDefinitionExample { public class ModelDefinition : ResourceDefinition { - public ModelDefinition(IResourceGraph graph) : base(graph) + public ModelDefinition(IContextEntityProvider provider) : base(provider) { } diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs index 1c066c28c9..6a92b4f8f3 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs @@ -9,9 +9,8 @@ public class ModelsController : JsonApiController { public ModelsController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService) - : base(jsonApiOptions, resourceGraph, resourceService) + : base(jsonApiOptions, resourceService) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs index abf6ce50ea..faa533093c 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -10,9 +9,8 @@ public class ArticlesController : JsonApiController
{ public ArticlesController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService
resourceService) - : base(jsonApiOptions, resourceGraph, resourceService) + : base(jsonApiOptions, resourceService) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs index c4ce48fc75..ee98b7f23d 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -11,10 +10,9 @@ public class CamelCasedModelsController : JsonApiController { public CamelCasedModelsController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs index f6733da236..a040ff21e4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -9,7 +8,10 @@ namespace JsonApiDotNetCoreExample.Controllers { public class PassportsController : JsonApiController { - public PassportsController(IJsonApiOptions jsonApiOptions, IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory = null) : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + public PassportsController(IJsonApiOptions jsonApiOptions, + IResourceService resourceService, + ILoggerFactory loggerFactory = null) + : base(jsonApiOptions, resourceService, loggerFactory) { } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs index d29cafe508..851b2cfc80 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -11,10 +10,9 @@ public class PeopleController : JsonApiController { public PeopleController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs index 0234eb6899..bee457a1cb 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -11,10 +10,9 @@ public class PersonRolesController : JsonApiController { public PersonRolesController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index 56bc6fda48..d300e24f46 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -19,11 +19,10 @@ public class TodoCollectionsController : JsonApiController resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { _dbResolver = contextResolver; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs index c7f3a6244e..818e082db9 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,11 +9,10 @@ namespace JsonApiDotNetCoreExample.Controllers public class TodoItemsController : JsonApiController { public TodoItemsController( - IJsonApiOptions jsonApiOPtions, - IResourceGraph resourceGraph, + IJsonApiOptions jsonApiOptions, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOPtions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs index bc174f102a..c4913689e4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -13,9 +14,10 @@ namespace JsonApiDotNetCoreExample.Controllers public class TodoItemsCustomController : CustomJsonApiController { public TodoItemsCustomController( + IJsonApiOptions options, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(resourceService, loggerFactory) + : base(options, resourceService, loggerFactory) { } } @@ -23,16 +25,19 @@ public class CustomJsonApiController : CustomJsonApiController where T : class, IIdentifiable { public CustomJsonApiController( + IJsonApiOptions options, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(resourceService, loggerFactory) - { } + : base(options, resourceService, loggerFactory) + { + } } public class CustomJsonApiController : ControllerBase where T : class, IIdentifiable { private readonly ILogger _logger; + private readonly IJsonApiOptions _options; private readonly IResourceService _resourceService; protected IActionResult Forbidden() @@ -41,11 +46,13 @@ protected IActionResult Forbidden() } public CustomJsonApiController( + IJsonApiOptions options, IResourceService resourceService, ILoggerFactory loggerFactory) { + _options = options; _resourceService = resourceService; - _logger = loggerFactory.CreateLogger>(); + _logger = loggerFactory.CreateLogger>(); } public CustomJsonApiController( @@ -95,8 +102,8 @@ public virtual async Task PostAsync([FromBody] T entity) if (entity == null) return UnprocessableEntity(); - //if (!_jsonApiContext.Options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId)) - // return Forbidden(); + if (_options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId)) + return Forbidden(); entity = await _resourceService.CreateAsync(entity); diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs index 18e566dc07..971f579b69 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -14,10 +13,9 @@ public abstract class AbstractTodoItemsController { protected AbstractTodoItemsController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, service, loggerFactory) + : base(jsonApiOptions, service, loggerFactory) { } } @@ -26,10 +24,9 @@ public class TodoItemsTestController : AbstractTodoItemsController { public TodoItemsTestController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, service, loggerFactory) + : base(jsonApiOptions, service, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs index 475b93b300..cc47e88d84 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -11,10 +10,9 @@ public class UsersController : JsonApiController { public UsersController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs index 1e3230c759..9a36eb27dc 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs @@ -11,7 +11,7 @@ namespace JsonApiDotNetCoreExample.Resources { public class ArticleResource : ResourceDefinition
{ - public ArticleResource(IResourceGraph graph) : base(graph) { } + public ArticleResource(IResourceGraph resourceGraph) : base(resourceGraph) { } public override IEnumerable
OnReturn(HashSet
entities, ResourcePipeline pipeline) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs index 96585a9458..c757191304 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs @@ -11,7 +11,7 @@ namespace JsonApiDotNetCoreExample.Resources { public abstract class LockableResource : ResourceDefinition where T : class, IIsLockable, IIdentifiable { - protected LockableResource(IResourceGraph graph) : base(graph) { } + protected LockableResource(IResourceGraph resourceGraph) : base(resourceGraph) { } protected void DisallowLocked(IEnumerable entities) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/PassportResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/PassportResource.cs index ba8b722277..25cc4afb72 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/PassportResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/PassportResource.cs @@ -11,7 +11,7 @@ namespace JsonApiDotNetCoreExample.Resources { public class PassportResource : ResourceDefinition { - public PassportResource(IResourceGraph graph) : base(graph) + public PassportResource(IResourceGraph resourceGraph) : base(resourceGraph) { } diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs index 445a990520..da8fc957b7 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCoreExample.Resources { public class PersonResource : LockableResource, IHasMeta { - public PersonResource(IResourceGraph graph) : base(graph) { } + public PersonResource(IResourceGraph resourceGraph) : base(resourceGraph) { } public override IEnumerable BeforeUpdate(IDiffableEntityHashSet entities, ResourcePipeline pipeline) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/TagResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/TagResource.cs index cd266303f3..1999936e34 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/TagResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/TagResource.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCoreExample.Resources { public class TagResource : ResourceDefinition { - public TagResource(IResourceGraph graph) : base(graph) { } + public TagResource(IResourceGraph resourceGraph) : base(resourceGraph) { } public override IEnumerable BeforeCreate(IEntityHashSet affected, ResourcePipeline pipeline) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/TodoResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/TodoResource.cs index 1c9f89a7fc..26f6c69c64 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/TodoResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/TodoResource.cs @@ -10,7 +10,7 @@ namespace JsonApiDotNetCoreExample.Resources { public class TodoResource : LockableResource { - public TodoResource(IResourceGraph graph) : base(graph) { } + public TodoResource(IResourceGraph resourceGraph) : base(resourceGraph) { } public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs index 52497df9a0..a8d6f039e4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCoreExample.Resources { public class UserResource : ResourceDefinition { - public UserResource(IResourceGraph graph, IFieldsExplorer fieldExplorer) : base(fieldExplorer, graph) + public UserResource(IResourceGraph resourceGraph) : base(resourceGraph) { HideFields(u => u.Password); } diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index e3b30c79b9..56f923aadc 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -20,11 +20,11 @@ public CustomArticleService(ISortService sortService, IIncludeService includeService, ISparseFieldsService sparseFieldsService, IPageService pageService, - IResourceGraph resourceGraph, + IContextEntityProvider provider, IResourceHookExecutor hookExecutor = null, ILoggerFactory loggerFactory = null) : base(sortService, filterService, repository, options, includeService, sparseFieldsService, - pageService, resourceGraph, hookExecutor, loggerFactory) + pageService, provider, hookExecutor, loggerFactory) { } diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index a784de13f6..2963a33ffa 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -32,14 +32,16 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) services .AddSingleton(loggerFactory) .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) - .AddJsonApi(options => { - options.Namespace = "api/v1"; - options.DefaultPageSize = 5; - options.IncludeTotalRecordCount = true; - options.EnableResourceHooks = true; - options.LoaDatabaseValues = true; - }, - discovery => discovery.AddCurrentAssembly()); + .AddJsonApi( + options => + { + options.Namespace = "api/v1"; + options.DefaultPageSize = 5; + options.IncludeTotalRecordCount = true; + options.EnableResourceHooks = true; + options.LoaDatabaseValues = true; + }, + discovery => discovery.AddCurrentAssembly()); return services.BuildServiceProvider(); } diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs index 4e93d26bea..9a593cfa9f 100644 --- a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs +++ b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs @@ -1,6 +1,5 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -11,10 +10,9 @@ public class CustomTodoItemsController : JsonApiController { public CustomTodoItemsController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { } } } diff --git a/src/Examples/NoEntityFrameworkExample/Startup.cs b/src/Examples/NoEntityFrameworkExample/Startup.cs old mode 100755 new mode 100644 index d42c44fa42..b83084e87a --- a/src/Examples/NoEntityFrameworkExample/Startup.cs +++ b/src/Examples/NoEntityFrameworkExample/Startup.cs @@ -33,12 +33,11 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) // Add framework services. var mvcBuilder = services.AddMvcCore(); - services.AddJsonApi(options => { - options.Namespace = "api/v1"; - options.BuildResourceGraph((builder) => { - builder.AddResource("custom-todo-items"); - }); - }, mvcBuilder); + services.AddJsonApi( + options => options.Namespace = "api/v1", + resources: resources => resources.AddResource("custom-todo-items"), + mvcBuilder: mvcBuilder + ); services.AddScoped, TodoItemService>(); diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index e421477c20..77099fe380 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -12,9 +12,8 @@ public class ReportsController : BaseJsonApiController { public ReportsController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IGetAllService getAll) - : base(jsonApiOptions, resourceGraph, getAll: getAll) + : base(jsonApiOptions, getAll: getAll) { } [HttpGet] diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs index 4f49e87db6..609847fa04 100644 --- a/src/Examples/ReportsExample/Startup.cs +++ b/src/Examples/ReportsExample/Startup.cs @@ -27,7 +27,7 @@ public virtual void ConfigureServices(IServiceCollection services) var mvcBuilder = services.AddMvcCore(); services.AddJsonApi( opt => opt.Namespace = "api", - discovery => discovery.AddCurrentAssembly(), mvcBuilder); + discovery => discovery.AddCurrentAssembly(), mvcBuilder: mvcBuilder); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) diff --git a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs index d03d4d3eab..6a3e7f2ae3 100644 --- a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs @@ -1,6 +1,5 @@ using System; using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; @@ -26,7 +25,6 @@ public interface IResourceGraphBuilder /// IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; - IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null); /// /// Add a json:api resource @@ -57,14 +55,6 @@ public interface IResourceGraphBuilder /// that also implement /// /// The implementation type. - IResourceGraphBuilder AddDbContext() where T : DbContext; - - /// - /// Specify the used to format resource names. - /// - /// Formatter used to define exposed resource names by convention. - IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter); - - + IResourceGraphBuilder AddDbContext() where T : DbContext; } } diff --git a/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs new file mode 100644 index 0000000000..71928fddd4 --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs @@ -0,0 +1,225 @@ +using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Formatters; +using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Generics; +using JsonApiDotNetCore.Managers; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Hooks; +using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Query; +using JsonApiDotNetCore.Serialization.Server.Builders; +using JsonApiDotNetCore.Serialization.Server; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace JsonApiDotNetCore.Builders +{ + /// + /// A utility class that builds a JsonApi application. It registers all required services + /// and allows the user to override parts of the startup configuration. + /// + public class JsonApiApplicationBuilder + { + public readonly JsonApiOptions JsonApiOptions = new JsonApiOptions(); + private IResourceGraphBuilder _resourceGraphBuilder; + private IServiceDiscoveryFacade _serviceDiscoveryFacade; + private bool _usesDbContext; + private readonly IServiceCollection _services; + private readonly IMvcCoreBuilder _mvcBuilder; + + public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder) + { + _services = services; + _mvcBuilder = mvcBuilder; + } + + /// + /// Executes the action provided by the user to configure + /// + public void ConfigureJsonApiOptions(Action configureOptions) => configureOptions(JsonApiOptions); + + /// + /// Configures built-in .net core MVC (things like middleware, routing). Most of this configuration can be adjusted for the developers need. + /// Before calling .AddJsonApi(), a developer can register their own implementation of the following services to customize startup: + /// , , , + /// , and . + /// + public void ConfigureMvc() + { + RegisterJsonApiStartupServices(); + + var intermediateProvider = _services.BuildServiceProvider(); + _resourceGraphBuilder = intermediateProvider.GetRequiredService(); + _serviceDiscoveryFacade = intermediateProvider.GetRequiredService(); + var exceptionFilterProvider = intermediateProvider.GetRequiredService(); + var typeMatchFilterProvider = intermediateProvider.GetRequiredService(); + + _mvcBuilder.AddMvcOptions(mvcOptions => + { + mvcOptions.Filters.Add(exceptionFilterProvider.Get()); + mvcOptions.Filters.Add(typeMatchFilterProvider.Get()); + mvcOptions.InputFormatters.Insert(0, new JsonApiInputFormatter()); + mvcOptions.OutputFormatters.Insert(0, new JsonApiOutputFormatter()); + }); + + var routingConvention = intermediateProvider.GetRequiredService(); + _mvcBuilder.AddMvcOptions(opt => opt.Conventions.Insert(0, routingConvention)); + _services.AddSingleton(routingConvention); + } + + /// + /// Executes autodiscovery of JADNC services. + /// + public void AutoDiscover(Action autoDiscover) + { + autoDiscover(_serviceDiscoveryFacade); + } + + /// + /// Executes the action provided by the user to configure the resources using + /// + /// + public void ConfigureResources(Action resourceGraphBuilder) + { + resourceGraphBuilder(_resourceGraphBuilder); + } + + /// + /// Executes the action provided by the user to configure the resources using . + /// Additionally, inspects the EF core database context for models that implement IIdentifiable. + /// + public void ConfigureResources(Action resourceGraphBuilder) where TContext : DbContext + { + _resourceGraphBuilder.AddDbContext(); + _usesDbContext = true; + _services.AddScoped>(); + resourceGraphBuilder?.Invoke(_resourceGraphBuilder); + } + + /// + /// Registers the remaining internals. + /// + public void ConfigureServices() + { + var resourceGraph = _resourceGraphBuilder.Build(); + + if (!_usesDbContext) + { + _services.AddScoped(); + _services.AddSingleton(new DbContextOptionsBuilder().Options); + } + + _services.AddScoped(typeof(IEntityRepository<>), typeof(DefaultEntityRepository<>)); + _services.AddScoped(typeof(IEntityRepository<,>), typeof(DefaultEntityRepository<,>)); + + _services.AddScoped(typeof(IEntityReadRepository<,>), typeof(DefaultEntityRepository<,>)); + _services.AddScoped(typeof(IEntityWriteRepository<,>), typeof(DefaultEntityRepository<,>)); + + _services.AddScoped(typeof(ICreateService<>), typeof(EntityResourceService<>)); + _services.AddScoped(typeof(ICreateService<,>), typeof(EntityResourceService<,>)); + + _services.AddScoped(typeof(IGetAllService<>), typeof(EntityResourceService<>)); + _services.AddScoped(typeof(IGetAllService<,>), typeof(EntityResourceService<,>)); + + _services.AddScoped(typeof(IGetByIdService<>), typeof(EntityResourceService<>)); + _services.AddScoped(typeof(IGetByIdService<,>), typeof(EntityResourceService<,>)); + + _services.AddScoped(typeof(IGetRelationshipService<,>), typeof(EntityResourceService<>)); + _services.AddScoped(typeof(IGetRelationshipService<,>), typeof(EntityResourceService<,>)); + + _services.AddScoped(typeof(IUpdateService<>), typeof(EntityResourceService<>)); + _services.AddScoped(typeof(IUpdateService<,>), typeof(EntityResourceService<,>)); + + _services.AddScoped(typeof(IDeleteService<>), typeof(EntityResourceService<>)); + _services.AddScoped(typeof(IDeleteService<,>), typeof(EntityResourceService<,>)); + + _services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>)); + _services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); + + _services.AddSingleton(JsonApiOptions); + _services.AddSingleton(resourceGraph); + _services.AddSingleton(); + _services.AddSingleton(resourceGraph); + _services.AddSingleton(resourceGraph); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(typeof(GenericProcessor<>)); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + + AddServerSerialization(); + AddQueryParameterServices(); + if (JsonApiOptions.EnableResourceHooks) + AddResourceHooks(); + + _services.AddScoped(); + } + + private void AddQueryParameterServices() + { + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + + _services.AddScoped(sp => sp.GetService()); + _services.AddScoped(sp => sp.GetService()); + _services.AddScoped(sp => sp.GetService()); + _services.AddScoped(sp => sp.GetService()); + _services.AddScoped(sp => sp.GetService()); + _services.AddScoped(sp => sp.GetService()); + _services.AddScoped(sp => sp.GetService()); + } + + private void AddResourceHooks() + { + _services.AddSingleton(typeof(IHooksDiscovery<>), typeof(HooksDiscovery<>)); + _services.AddScoped(typeof(IResourceHookContainer<>), typeof(ResourceDefinition<>)); + _services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor)); + _services.AddTransient(); + _services.AddTransient(); + } + + private void AddServerSerialization() + { + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(typeof(IMetaBuilder<>), typeof(MetaBuilder<>)); + _services.AddScoped(typeof(ResponseSerializer<>)); + _services.AddScoped(sp => sp.GetRequiredService().GetSerializer()); + _services.AddScoped(); + } + + private void RegisterJsonApiStartupServices() + { + _services.AddSingleton(JsonApiOptions); + _services.TryAddSingleton(new KebabCaseFormatter()); + _services.TryAddSingleton(); + _services.TryAddSingleton(); + _services.TryAddSingleton(sp => new ServiceDiscoveryFacade(_services, sp.GetRequiredService())); + _services.TryAddScoped(); + _services.TryAddScoped(); + } + } +} diff --git a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs index 21b3058233..14144d3ae8 100644 --- a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs @@ -17,41 +17,23 @@ namespace JsonApiDotNetCore.Builders { public class ResourceGraphBuilder : IResourceGraphBuilder { - private List _entities = new List(); - private List _validationResults = new List(); - private Dictionary> _controllerMapper = new Dictionary>() { }; - private List _undefinedMapper = new List() { }; - private bool _usesDbContext; - private IResourceNameFormatter _resourceNameFormatter; - - public ResourceGraphBuilder(IResourceNameFormatter formatter = null) + private readonly List _entities = new List(); + private readonly List _validationResults = new List(); + private readonly IResourceNameFormatter _resourceNameFormatter = new KebabCaseFormatter(); + + public ResourceGraphBuilder() { } + + public ResourceGraphBuilder(IResourceNameFormatter formatter) { - _resourceNameFormatter = formatter ?? new KebabCaseFormatter(); + _resourceNameFormatter = formatter; } /// public IResourceGraph Build() { _entities.ForEach(SetResourceLinksOptions); - - List controllerContexts = new List() { }; - foreach (var cm in _controllerMapper) - { - var model = cm.Key; - foreach (var controller in cm.Value) - { - var controllerName = controller.Name.Replace("Controller", ""); - - controllerContexts.Add(new ControllerResourceMap - { - Resource = model, - ControllerName = controllerName, - }); - - } - } - var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults, controllerContexts); - return graph; + var resourceGraph = new ResourceGraph(_entities, _validationResults); + return resourceGraph; } private void SetResourceLinksOptions(ContextEntity resourceContext) @@ -199,25 +181,17 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope /// public IResourceGraphBuilder AddDbContext() where T : DbContext { - _usesDbContext = true; - var contextType = typeof(T); - var contextProperties = contextType.GetProperties(); - foreach (var property in contextProperties) { var dbSetType = property.PropertyType; - if (dbSetType.GetTypeInfo().IsGenericType && dbSetType.GetGenericTypeDefinition() == typeof(DbSet<>)) { var entityType = dbSetType.GetGenericArguments()[0]; - AssertEntityIsNotAlreadyDefined(entityType); - var (isJsonApiResource, idType) = GetIdType(entityType); - if (isJsonApiResource) _entities.Add(GetEntity(GetResourceNameFromDbSetProperty(property, entityType), entityType, idType)); } @@ -261,32 +235,7 @@ private string GetResourceNameFromDbSetProperty(PropertyInfo property, Type reso private void AssertEntityIsNotAlreadyDefined(Type entityType) { if (_entities.Any(e => e.EntityType == entityType)) - throw new InvalidOperationException($"Cannot add entity type {entityType} to context graph, there is already an entity of that type configured."); - } - - /// - public IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter) - { - _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() { controller }); - } - return this; + throw new InvalidOperationException($"Cannot add entity type {entityType} to context resourceGraph, there is already an entity of that type configured."); } } } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 2fd5984118..661341b25a 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -23,7 +23,6 @@ public interface IJsonApiOptions : ILinksConfiguration, ISerializerOptions int DefaultPageSize { get; } bool ValidateModelState { get; } bool AllowClientGeneratedIds { get; } - IResourceGraph ResourceGraph { get; set; } bool AllowCustomQueryParameters { get; set; } string Namespace { get; set; } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 631f7e2b63..dfa0591e18 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -1,11 +1,8 @@ -using System; using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Graph; -using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Links; -using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; namespace JsonApiDotNetCore.Configuration @@ -95,12 +92,6 @@ public class JsonApiOptions : IJsonApiOptions /// public bool AllowClientGeneratedIds { get; set; } - /// - /// The graph of all resources exposed by this application. - /// - [Obsolete("Use the standalone resourcegraph")] - public IResourceGraph ResourceGraph { get; set; } - /// /// Whether or not to allow all custom query parameters. /// @@ -139,28 +130,9 @@ public class JsonApiOptions : IJsonApiOptions NullValueHandling = NullValueHandling.Ignore }; - public void BuildResourceGraph(Action builder) where TContext : DbContext - { - BuildResourceGraph(builder); - - ResourceGraphBuilder.AddDbContext(); - - ResourceGraph = ResourceGraphBuilder.Build(); - } - - public void BuildResourceGraph(Action builder) - { - if (builder == null) return; - - builder(ResourceGraphBuilder); - - ResourceGraph = ResourceGraphBuilder.Build(); - } - public void EnableExtension(JsonApiExtension extension) => EnabledExtensions.Add(extension); - internal IResourceGraphBuilder ResourceGraphBuilder { get; } = new ResourceGraphBuilder(); internal List EnabledExtensions { get; set; } = new List(); } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 67e5a45107..1b2477dc79 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,3 +1,5 @@ +using System; +using System.Reflection; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Extensions; @@ -24,11 +26,9 @@ public class BaseJsonApiController private readonly IDeleteService _delete; private readonly ILogger> _logger; private readonly IJsonApiOptions _jsonApiOptions; - private readonly IResourceGraph _resourceGraph; public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraphManager, IResourceService resourceService, ILoggerFactory loggerFactory) { @@ -41,7 +41,6 @@ public BaseJsonApiController( _logger = new Logger>(new LoggerFactory()); } _jsonApiOptions = jsonApiOptions; - _resourceGraph = resourceGraphManager; _getAll = resourceService; _getById = resourceService; _getRelationship = resourceService; @@ -84,7 +83,6 @@ public BaseJsonApiController( /// public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -94,7 +92,6 @@ public BaseJsonApiController( IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null) { - _resourceGraph = resourceGraph; _jsonApiOptions = jsonApiOptions; _getAll = getAll; _getById = getById; @@ -118,18 +115,15 @@ public virtual async Task GetAsync(TId id) if (_getById == null) throw Exceptions.UnSupportedRequestMethod; var entity = await _getById.GetAsync(id); if (entity == null) - { return NotFound(); - } + return Ok(entity); } public virtual async Task GetRelationshipsAsync(TId id, string relationshipName) { if (_getRelationships == null) - { throw Exceptions.UnSupportedRequestMethod; - } var relationship = await _getRelationships.GetRelationshipsAsync(id, relationshipName); if (relationship == null) return NotFound(); @@ -156,9 +150,7 @@ public virtual async Task PostAsync([FromBody] T entity) return Forbidden(); if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) - { - return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _resourceGraph)); - } + return UnprocessableEntity(ModelState.ConvertToErrorCollection(GetAssociatedResource())); entity = await _create.CreateAsync(entity); @@ -172,9 +164,7 @@ public virtual async Task PatchAsync(TId id, [FromBody] T entity) return UnprocessableEntity(); if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) - { - return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _resourceGraph)); - } + return UnprocessableEntity(ModelState.ConvertToErrorCollection(GetAssociatedResource())); var updatedEntity = await _update.UpdateAsync(id, entity); @@ -199,6 +189,13 @@ public virtual async Task DeleteAsync(TId id) return NotFound(); return NoContent(); } + + internal Type GetAssociatedResource() + { + return GetType().GetMethod(nameof(GetAssociatedResource), BindingFlags.Instance | BindingFlags.NonPublic) + .DeclaringType + .GetGenericArguments()[0]; + } } public class BaseJsonApiController : BaseJsonApiController @@ -218,7 +215,6 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -227,6 +223,6 @@ public BaseJsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index b960e94a39..058ef5c313 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -20,15 +20,13 @@ public class JsonApiController : BaseJsonApiController where T : /// public JsonApiController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory = null) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory = null) + : base(jsonApiOptions, resourceService, loggerFactory = null) { } public JsonApiController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -37,7 +35,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } [HttpGet] public override async Task GetAsync() => await base.GetAsync(); @@ -87,16 +85,14 @@ public class JsonApiController : JsonApiController where T : class, I /// public JsonApiController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory = null ) - : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) + : base(jsonApiOptions, resourceService, loggerFactory) { } public JsonApiController( IJsonApiOptions jsonApiOptions, - IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -105,7 +101,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 866c398be7..47c6bca132 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -85,17 +85,15 @@ public virtual async Task CreateAsync(TEntity entity) { foreach (var relationshipAttr in _targetedFields.Relationships) { - object trackedRelationshipValue = GetTrackedRelationshipValue(relationshipAttr, entity, out bool wasAlreadyTracked); + object trackedRelationshipValue = GetTrackedRelationshipValue(relationshipAttr, entity, out bool relationshipWasAlreadyTracked); LoadInverseRelationships(trackedRelationshipValue, relationshipAttr); - if (wasAlreadyTracked) + if (relationshipWasAlreadyTracked || relationshipAttr is HasManyThroughAttribute) /// We only need to reassign the relationship value to the to-be-added - /// entity when we're using a different instance (because this different one + /// entity when we're using a different instance of the relationship (because this different one /// was already tracked) than the one assigned to the to-be-created entity. - AssignRelationshipValue(entity, trackedRelationshipValue, relationshipAttr); - else if (relationshipAttr is HasManyThroughAttribute throughAttr) - /// even if we don't have to reassign anything because of already tracked + /// Alternatively, even if we don't have to reassign anything because of already tracked /// entities, we still need to assign the "through" entities in the case of many-to-many. - AssignHasManyThrough(entity, throughAttr, (IList)trackedRelationshipValue); + relationshipAttr.SetValue(entity, trackedRelationshipValue); } _dbSet.Add(entity); await _context.SaveChangesAsync(); @@ -139,7 +137,7 @@ private void LoadInverseRelationships(object trackedRelationshipValue, Relations private bool IsHasOneRelationship(string internalRelationshipName, Type type) { - var relationshipAttr = _resourceGraph.GetContextEntity(type).Relationships.FirstOrDefault(r => r.InternalRelationshipName == internalRelationshipName); + var relationshipAttr = _resourceGraph.GetRelationships(type).FirstOrDefault(r => r.InternalRelationshipName == internalRelationshipName); if (relationshipAttr != null) { if (relationshipAttr is HasOneAttribute) @@ -149,7 +147,7 @@ private bool IsHasOneRelationship(string internalRelationshipName, Type type) } // relationshipAttr is null when we don't put a [RelationshipAttribute] on the inverse navigation property. // In this case we use relfection to figure out what kind of relationship is pointing back. - return !(type.GetProperty(internalRelationshipName).PropertyType.Inherits(typeof(IEnumerable))); + return !type.GetProperty(internalRelationshipName).PropertyType.Inherits(typeof(IEnumerable)); } private void DetachRelationships(TEntity entity) @@ -199,7 +197,8 @@ public virtual async Task UpdateAsync(TEntity updatedEntity) /// to the todoItems in trackedRelationshipValue LoadInverseRelationships(trackedRelationshipValue, relationshipAttr); /// assigns the updated relationship to the database entity - AssignRelationshipValue(databaseEntity, trackedRelationshipValue, relationshipAttr); + //AssignRelationshipValue(databaseEntity, trackedRelationshipValue, relationshipAttr); + relationshipAttr.SetValue(databaseEntity, trackedRelationshipValue); } await _context.SaveChangesAsync(); @@ -357,7 +356,6 @@ protected void LoadCurrentRelationships(TEntity oldEntity, RelationshipAttribute if (relationshipAttribute is HasManyThroughAttribute throughAttribute) { _context.Entry(oldEntity).Collection(throughAttribute.InternalThroughName).Load(); - } else if (relationshipAttribute is HasManyAttribute hasManyAttribute) { @@ -365,22 +363,6 @@ protected void LoadCurrentRelationships(TEntity oldEntity, RelationshipAttribute } } - /// - /// Assigns the to - /// - private void AssignRelationshipValue(TEntity targetEntity, object relationshipValue, RelationshipAttribute relationshipAttribute) - { - if (relationshipAttribute is HasManyThroughAttribute throughAttribute) - { - // todo: this logic should be put in the HasManyThrough attribute - AssignHasManyThrough(targetEntity, throughAttribute, (IList)relationshipValue); - } - else - { - relationshipAttribute.SetValue(targetEntity, relationshipValue); - } - } - /// /// The relationshipValue parameter contains the dependent side of the relationship (Tags). /// We can't directly add them to the principal entity (Article): we need to @@ -437,18 +419,18 @@ public class DefaultEntityRepository : DefaultEntityRepository _noopConfig = opt => { }; - public static IServiceCollection AddJsonApi(this IServiceCollection services, - IMvcCoreBuilder mvcBuilder = null) - where TContext : DbContext - { - return AddJsonApi(services, _noopConfig, mvcBuilder); - } - /// /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. /// /// /// - /// + /// + /// /// - public static IServiceCollection AddJsonApi(this IServiceCollection services, - Action configureOptions, + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action options = null, + Action resources = null, IMvcCoreBuilder mvcBuilder = null) - where TContext : DbContext - { - var options = new JsonApiOptions(); - // add basic Mvc functionality - mvcBuilder = mvcBuilder ?? services.AddMvcCore(); - // configures JsonApiOptions; - configureOptions(options); - // ResourceGraphBuilder should not be exposed on JsonApiOptions. - // Instead, ResourceGraphBuilder should consume JsonApiOptions - // build the resource graph using ef core DbContext - options.BuildResourceGraph(builder => builder.AddDbContext()); - ConfigureMvc(services, mvcBuilder, options); - // register services - AddJsonApiInternals(services, options); + where TEfCoreDbContext : DbContext + { + var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore()); + if (options != null) + application.ConfigureJsonApiOptions(options); + application.ConfigureMvc(); + application.ConfigureResources(resources); + application.ConfigureServices(); return services; } - /// /// Enabling JsonApiDotNetCore using manual declaration to build the ResourceGraph. - /// - /// - /// - /// - public static IServiceCollection AddJsonApi(this IServiceCollection services, - Action configureOptions, - IMvcCoreBuilder mvcBuilder = null) - { - var options = new JsonApiOptions(); - // add basic Mvc functionality - mvcBuilder = mvcBuilder ?? services.AddMvcCore(); - // configures JsonApiOptions; - configureOptions(options); - ConfigureMvc(services, mvcBuilder, options); - // register services - AddJsonApiInternals(services, options); - return services; - } - - /// - /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. - /// + /// z /// - /// - /// + /// + /// /// public static IServiceCollection AddJsonApi(this IServiceCollection services, - Action configureOptions, - Action autoDiscover, + Action options = null, + Action discovery = null, + Action resources = null, IMvcCoreBuilder mvcBuilder = null) { - var options = new JsonApiOptions(); - // add basic Mvc functionality - mvcBuilder = mvcBuilder ?? services.AddMvcCore(); - // configures JsonApiOptions; - configureOptions(options); - // build the resource graph using auto discovery. - var facade = new ServiceDiscoveryFacade(services, options.ResourceGraphBuilder); - autoDiscover(facade); - ConfigureMvc(services, mvcBuilder, options); - // register services - AddJsonApiInternals(services, options); + var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore()); + if (options != null) + application.ConfigureJsonApiOptions(options); + application.ConfigureMvc(); + if (discovery != null) + application.AutoDiscover(discovery); + if (resources != null) + application.ConfigureResources(resources); + application.ConfigureServices(); return services; } - private static void ConfigureMvc(IServiceCollection services, IMvcCoreBuilder mvcBuilder, JsonApiOptions options) - { - // add JsonApi filters and serializers - mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options)); - // register services that allow user to override behaviour that is configured on startup, like routing conventions - AddStartupConfigurationServices(services, options); - var intermediateProvider = services.BuildServiceProvider(); - mvcBuilder.AddMvcOptions(opt => opt.Conventions.Insert(0, intermediateProvider.GetRequiredService())); - } - - private static void AddMvcOptions(MvcOptions options, JsonApiOptions config) - { - options.Filters.Add(typeof(JsonApiExceptionFilter)); - options.Filters.Add(typeof(TypeMatchFilter)); - options.SerializeAsJsonApi(config); - } - - public static void AddJsonApiInternals( - this IServiceCollection services, - JsonApiOptions jsonApiOptions) where TContext : DbContext - { - if (jsonApiOptions.ResourceGraph == null) - jsonApiOptions.BuildResourceGraph(null); - - services.AddScoped>(); - - AddJsonApiInternals(services, jsonApiOptions); - } - - /// - /// Adds services to the container that need to be retrieved with an intermediate provider during Startup. - /// - private static void AddStartupConfigurationServices(this IServiceCollection services, JsonApiOptions jsonApiOptions) - { - services.AddSingleton(jsonApiOptions); - services.TryAddSingleton(new KebabCaseFormatter()); - services.TryAddSingleton(); - } - - public static void AddJsonApiInternals( - this IServiceCollection services, - JsonApiOptions jsonApiOptions) - { - var graph = jsonApiOptions.ResourceGraph ?? jsonApiOptions.ResourceGraphBuilder.Build(); - - if (graph.UsesDbContext == false) - { - services.AddScoped(); - services.AddSingleton(new DbContextOptionsBuilder().Options); - } - - services.AddScoped(typeof(IEntityRepository<>), typeof(DefaultEntityRepository<>)); - services.AddScoped(typeof(IEntityRepository<,>), typeof(DefaultEntityRepository<,>)); - - services.AddScoped(typeof(IEntityReadRepository<,>), typeof(DefaultEntityRepository<,>)); - services.AddScoped(typeof(IEntityWriteRepository<,>), typeof(DefaultEntityRepository<,>)); - - services.AddScoped(typeof(ICreateService<>), typeof(EntityResourceService<>)); - services.AddScoped(typeof(ICreateService<,>), typeof(EntityResourceService<,>)); - - services.AddScoped(typeof(IGetAllService<>), typeof(EntityResourceService<>)); - services.AddScoped(typeof(IGetAllService<,>), typeof(EntityResourceService<,>)); - - services.AddScoped(typeof(IGetByIdService<>), typeof(EntityResourceService<>)); - services.AddScoped(typeof(IGetByIdService<,>), typeof(EntityResourceService<,>)); - - services.AddScoped(typeof(IGetRelationshipService<,>), typeof(EntityResourceService<>)); - services.AddScoped(typeof(IGetRelationshipService<,>), typeof(EntityResourceService<,>)); - - services.AddScoped(typeof(IUpdateService<>), typeof(EntityResourceService<>)); - services.AddScoped(typeof(IUpdateService<,>), typeof(EntityResourceService<,>)); - - services.AddScoped(typeof(IDeleteService<>), typeof(EntityResourceService<>)); - services.AddScoped(typeof(IDeleteService<,>), typeof(EntityResourceService<,>)); - - services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>)); - services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); - - services.AddSingleton(jsonApiOptions); - services.AddSingleton(graph); - services.AddSingleton(); - services.AddSingleton(graph); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(typeof(GenericProcessor<>)); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - AddServerSerialization(services); - AddQueryParameterServices(services); - if (jsonApiOptions.EnableResourceHooks) - AddResourceHooks(services); - - services.AddScoped(); - } - - private static void AddQueryParameterServices(IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - - services.AddScoped(sp => sp.GetService()); - services.AddScoped(sp => sp.GetService()); - services.AddScoped(sp => sp.GetService()); - services.AddScoped(sp => sp.GetService()); - services.AddScoped(sp => sp.GetService()); - services.AddScoped(sp => sp.GetService()); - services.AddScoped(sp => sp.GetService()); - } - - - private static void AddResourceHooks(IServiceCollection services) - { - services.AddSingleton(typeof(IHooksDiscovery<>), typeof(HooksDiscovery<>)); - services.AddScoped(typeof(IResourceHookContainer<>), typeof(ResourceDefinition<>)); - services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor)); - services.AddTransient(); - services.AddTransient(); - } - - private static void AddServerSerialization(IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(typeof(IMetaBuilder<>), typeof(MetaBuilder<>)); - services.AddScoped(typeof(ResponseSerializer<>)); - services.AddScoped(sp => sp.GetRequiredService().GetSerializer()); - services.AddScoped(); - } /// /// Enables client serializers for sending requests and receiving responses /// in json:api format. Internally only used for testing. /// Will be extended in the future to be part of a JsonApiClientDotNetCore package. /// - public static void AddClientSerialization(this IServiceCollection services) + public static IServiceCollection AddClientSerialization(this IServiceCollection services) { services.AddScoped(); services.AddScoped(sp => { - var resourceObjectBuilder = new ResourceObjectBuilder(sp.GetService(), sp.GetService(), sp.GetService().Get()); - return new RequestSerializer(sp.GetService(), sp.GetService(), resourceObjectBuilder); + var resourceObjectBuilder = new ResourceObjectBuilder(sp.GetService(), sp.GetService().Get()); + return new RequestSerializer(sp.GetService(), resourceObjectBuilder); }); - - } - - public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions jsonApiOptions) - { - options.InputFormatters.Insert(0, new JsonApiInputFormatter()); - options.OutputFormatters.Insert(0, new JsonApiOutputFormatter()); + return services; } /// diff --git a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs index 587f3749f3..3bbfbbb09d 100644 --- a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs @@ -1,6 +1,7 @@ using System; +using System.Reflection; using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore.Internal; @@ -8,7 +9,7 @@ namespace JsonApiDotNetCore.Extensions { public static class ModelStateExtensions { - public static ErrorCollection ConvertToErrorCollection(this ModelStateDictionary modelState, IResourceGraph resourceGraph) + public static ErrorCollection ConvertToErrorCollection(this ModelStateDictionary modelState, Type resourceType) { ErrorCollection collection = new ErrorCollection(); foreach (var entry in modelState) @@ -16,7 +17,8 @@ public static ErrorCollection ConvertToErrorCollection(this ModelStateDiction if (entry.Value.Errors.Any() == false) continue; - var attrName = resourceGraph.GetPublicAttributeName(entry.Key); + var targetedProperty = resourceType.GetProperty(entry.Key); + var attrName = targetedProperty.GetCustomAttribute().PublicAttributeName; foreach (var modelError in entry.Value.Errors) { diff --git a/src/JsonApiDotNetCore/Graph/IServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/IServiceDiscoveryFacade.cs new file mode 100644 index 0000000000..53e4e80cc5 --- /dev/null +++ b/src/JsonApiDotNetCore/Graph/IServiceDiscoveryFacade.cs @@ -0,0 +1,10 @@ +using System.Reflection; + +namespace JsonApiDotNetCore.Graph +{ + public interface IServiceDiscoveryFacade + { + ServiceDiscoveryFacade AddAssembly(Assembly assembly); + ServiceDiscoveryFacade AddCurrentAssembly(); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs index 468537ec1a..d197d55ecc 100644 --- a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs @@ -15,7 +15,7 @@ namespace JsonApiDotNetCore.Graph { - public class ServiceDiscoveryFacade + public class ServiceDiscoveryFacade : IServiceDiscoveryFacade { internal static HashSet ServiceInterfaces = new HashSet { typeof(IResourceService<>), @@ -49,15 +49,13 @@ public class ServiceDiscoveryFacade typeof(IEntityReadRepository<,>) }; private readonly IServiceCollection _services; - private readonly IResourceGraphBuilder _graphBuilder; + private readonly IResourceGraphBuilder _resourceGraphBuilder; private readonly List _identifiables = new List(); - public ServiceDiscoveryFacade( - IServiceCollection services, - IResourceGraphBuilder graphBuilder) + public ServiceDiscoveryFacade(IServiceCollection services, IResourceGraphBuilder resourceGraphBuilder) { _services = services; - _graphBuilder = graphBuilder; + _resourceGraphBuilder = resourceGraphBuilder; } /// @@ -80,38 +78,9 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly) AddServices(assembly, resourceDescriptor); AddRepositories(assembly, resourceDescriptor); } - - ScanControllers(assembly); - return this; } - private void ScanControllers(Assembly assembly) - { - var baseTypes = new List() { typeof(ControllerBase), typeof(JsonApiControllerMixin), typeof(JsonApiController<>), typeof(BaseJsonApiController<>) }; - List baseTypesSeen = new List() { }; - var types = assembly.GetTypes().ToList(); - //types.ForEach(t => baseTypesSeen.Add(t.BaseType.Name)); - - var controllerMapper = new Dictionary>() { }; - var undefinedMapper = new List() { }; - var sdf = assembly.GetTypes() - .Where(type => typeof(ControllerBase).IsAssignableFrom(type) & !type.IsGenericType).ToList(); - foreach (var controllerType in sdf) - { - // get generic parameter - var genericParameters = controllerType.BaseType.GetGenericArguments(); - if (genericParameters.Count() > 0) - { - - _graphBuilder.AddControllerPairing(controllerType, genericParameters[0]); - } - else - { - _graphBuilder.AddControllerPairing(controllerType); - } - } - } public IEnumerable FindDerivedTypes(Type baseType) { @@ -138,7 +107,7 @@ private void AddDbContextResolvers(Assembly assembly) } /// - /// Adds resources to the graph and registers types on the container. + /// Adds resources to the resourceGraph and registers types on the container. /// /// The assembly to search for resources in. public ServiceDiscoveryFacade AddResources(Assembly assembly) @@ -175,7 +144,7 @@ private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor id private void AddResourceToGraph(ResourceDescriptor identifiable) { var resourceName = FormatResourceName(identifiable.ResourceType); - _graphBuilder.AddResource(identifiable.ResourceType, identifiable.IdType, resourceName); + _resourceGraphBuilder.AddResource(identifiable.ResourceType, identifiable.IdType, resourceName); } private string FormatResourceName(Type resourceType) diff --git a/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs b/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs index ce8d1dd138..b92619b691 100644 --- a/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs @@ -22,20 +22,15 @@ internal class HookExecutorHelper : IHookExecutorHelper private readonly IdentifiableComparer _comparer = new IdentifiableComparer(); private readonly IJsonApiOptions _options; protected readonly IGenericProcessorFactory _genericProcessorFactory; - protected readonly IResourceGraph _graph; protected readonly Dictionary _hookContainers; protected readonly Dictionary _hookDiscoveries; protected readonly List _targetedHooksForRelatedEntities; - public HookExecutorHelper( - IGenericProcessorFactory genericProcessorFactory, - IResourceGraph graph, - IJsonApiOptions options - ) + public HookExecutorHelper(IGenericProcessorFactory genericProcessorFactory, + IJsonApiOptions options) { _options = options; _genericProcessorFactory = genericProcessorFactory; - _graph = graph; _hookContainers = new Dictionary(); _hookDiscoveries = new Dictionary(); _targetedHooksForRelatedEntities = new List(); diff --git a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs index dda22e0762..b351a2a92d 100644 --- a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs @@ -21,7 +21,7 @@ internal class ResourceHookExecutor : IResourceHookExecutor private readonly ITraversalHelper _traversalHelper; private readonly IIncludeService _includeService; private readonly ITargetedFields _targetedFields; - private readonly IResourceGraph _graph; + private readonly IResourceGraph _resourceGraph; public ResourceHookExecutor( IHookExecutorHelper executorHelper, ITraversalHelper traversalHelper, @@ -33,7 +33,7 @@ public ResourceHookExecutor( _traversalHelper = traversalHelper; _targetedFields = targetedFields; _includeService = includedRelationships; - _graph = resourceGraph; + _resourceGraph = resourceGraph; } /// @@ -324,7 +324,7 @@ Dictionary ReplaceKeysWithInverseRelationshi /// If it isn't, JADNC currently knows nothing about this relationship pointing back, and it /// currently cannot fire hooks for entities resolved through inverse relationships. var inversableRelationshipAttributes = entitiesByRelationship.Where(kvp => kvp.Key.InverseNavigation != null); - return inversableRelationshipAttributes.ToDictionary(kvp => _graph.GetInverseRelationship(kvp.Key), kvp => kvp.Value); + return inversableRelationshipAttributes.ToDictionary(kvp => _resourceGraph.GetInverse(kvp.Key), kvp => kvp.Value); } /// @@ -337,7 +337,7 @@ void FireForAffectedImplicits(Type entityTypeToInclude, Dictionary _graph.GetInverseRelationship(kvp.Key), kvp => kvp.Value); + var inverse = implicitAffected.ToDictionary(kvp => _resourceGraph.GetInverse(kvp.Key), kvp => kvp.Value); var resourcesByRelationship = CreateRelationshipHelper(entityTypeToInclude, inverse); CallHook(container, ResourceHook.BeforeImplicitUpdateRelationship, new object[] { resourcesByRelationship, pipeline, }); } diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs index b023870066..80c6d797ca 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs @@ -25,7 +25,7 @@ namespace JsonApiDotNetCore.Hooks internal class TraversalHelper : ITraversalHelper { private readonly IdentifiableComparer _comparer = new IdentifiableComparer(); - private readonly IContextEntityProvider _provider; + private readonly IResourceGraph _resourceGraph; private readonly ITargetedFields _targetedFields; /// /// Keeps track of which entities has already been traversed through, to prevent @@ -38,11 +38,11 @@ internal class TraversalHelper : ITraversalHelper /// private readonly Dictionary RelationshipProxies = new Dictionary(); public TraversalHelper( - IContextEntityProvider provider, + IResourceGraph resourceGraph, ITargetedFields targetedFields) { _targetedFields = targetedFields; - _provider = provider; + _resourceGraph = resourceGraph; } /// @@ -196,13 +196,12 @@ HashSet ProcessEntities(IEnumerable incomingEntities) /// /// Parses all relationships from to - /// other models in the resource graphs by constructing RelationshipProxies . + /// other models in the resource resourceGraphs by constructing RelationshipProxies . /// /// The type to parse void RegisterRelationshipProxies(DependentType type) { - var contextEntity = _provider.GetContextEntity(type); - foreach (RelationshipAttribute attr in contextEntity.Relationships) + foreach (RelationshipAttribute attr in _resourceGraph.GetRelationships(type)) { if (!attr.CanInclude) continue; if (!RelationshipProxies.TryGetValue(attr, out RelationshipProxy proxies)) diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs b/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs index 46782d8d19..c5bf26cc7a 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal.Contracts @@ -8,6 +11,11 @@ namespace JsonApiDotNetCore.Internal.Contracts /// public interface IContextEntityProvider { + /// + /// Gets all registered context entities + /// + ContextEntity[] GetContextEntities(); + /// /// Get the resource metadata by the DbSet property name /// diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs deleted file mode 100644 index e702b1981e..0000000000 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ /dev/null @@ -1,73 +0,0 @@ -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Internal.Contracts -{ - /// - /// A cache for the models in entity core - /// TODO: separate context entity getting part from relationship resolving part. - /// These are two deviating responsibilities that often do not need to be exposed - /// at the same time. - /// - public interface IResourceGraph : IContextEntityProvider - { - RelationshipAttribute GetInverseRelationship(RelationshipAttribute relationship); - /// - /// Gets the value of the navigation property, defined by the relationshipName, - /// on the provided instance. - /// - /// The resource instance - /// The navigation property name. - /// - /// - /// _graph.GetRelationship(todoItem, nameof(TodoItem.Owner)); - /// - /// - /// - /// In the case of a `HasManyThrough` relationship, it will not traverse the relationship - /// and will instead return the value of the shadow property (e.g. Articles.Tags). - /// If you want to traverse the relationship, you should use . - /// - object GetRelationship(TParent resource, string propertyName); - - /// - /// Gets the value of the navigation property (defined by the ) - /// on the provided instance. - /// In the case of `HasManyThrough` relationships, it will traverse the through entity and return the - /// value of the relationship on the other side of a join entity (e.g. Articles.ArticleTags.Tag). - /// - /// The resource instance - /// The attribute used to define the relationship. - /// - /// - /// _graph.GetRelationshipValue(todoItem, nameof(TodoItem.Owner)); - /// - /// - object GetRelationshipValue(TParent resource, RelationshipAttribute relationship) where TParent : IIdentifiable; - - /// - /// Get the internal navigation property name for the specified public - /// relationship name. - /// - /// The public relationship name specified by a or - /// - /// - /// _graph.GetRelationshipName<TodoItem>("achieved-date"); - /// // returns "AchievedDate" - /// - /// - string GetRelationshipName(string relationshipName); - - /// - /// Get the public attribute name for a type based on the internal attribute name. - /// - /// The internal attribute name for a . - string GetPublicAttributeName(string internalAttributeName); - - /// - /// Was built against an EntityFrameworkCore DbContext ? - /// - bool UsesDbContext { get; } - - ContextEntity GetEntityFromControllerName(string pathParsed); - } -} diff --git a/src/JsonApiDotNetCore/Services/Contract/IFieldExplorer.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraphExplorer.cs similarity index 85% rename from src/JsonApiDotNetCore/Services/Contract/IFieldExplorer.cs rename to src/JsonApiDotNetCore/Internal/Contracts/IResourceGraphExplorer.cs index a5db41cd44..550f351e8f 100644 --- a/src/JsonApiDotNetCore/Services/Contract/IFieldExplorer.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraphExplorer.cs @@ -3,13 +3,13 @@ using System.Linq.Expressions; using JsonApiDotNetCore.Models; -namespace JsonApiDotNetCore.Services +namespace JsonApiDotNetCore.Internal.Contracts { /// /// Responsible for retrieving the exposed resource fields (attributes and - /// relationships) of registered resources. + /// relationships) of registered resources in the resource resourceGraph. /// - public interface IFieldsExplorer + public interface IResourceGraph : IContextEntityProvider { /// /// Gets all fields (attributes and relationships) for @@ -50,5 +50,12 @@ public interface IFieldsExplorer /// /// The resource type. Must extend IIdentifiable. List GetRelationships(Type type); + + /// + /// Traverses the resource resourceGraph for the inverse relationship of the provided + /// ; + /// + /// + RelationshipAttribute GetInverse(RelationshipAttribute relationship); } } diff --git a/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs b/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs index 3335ca87aa..ca54f13f6c 100644 --- a/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Internal/DefaultRoutingConvention.cs @@ -6,7 +6,10 @@ using System.Reflection; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Models; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; namespace JsonApiDotNetCore.Internal @@ -32,22 +35,34 @@ namespace JsonApiDotNetCore.Internal /// public class SomeVeryCustomController{SomeResource} : JsonApiMixin { } /// // => /some-very-customs/relationship/related-resource /// - public class DefaultRoutingConvention : IJsonApiRoutingConvention + public class DefaultRoutingConvention : IJsonApiRoutingConvention, IControllerResourceMapping { private readonly string _namespace; private readonly IResourceNameFormatter _formatter; private readonly HashSet _registeredTemplates = new HashSet(); + private readonly Dictionary _registeredResources = new Dictionary(); public DefaultRoutingConvention(IJsonApiOptions options, IResourceNameFormatter formatter) { _namespace = options.Namespace; _formatter = formatter; } + /// + public Type GetAssociatedResource(string controllerName) + { + _registeredResources.TryGetValue(controllerName, out Type type); + return type; + } + /// public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { + var resourceType = GetResourceTypeFromController(controller.ControllerType); + if (resourceType != null) + _registeredResources.Add(controller.ControllerName, resourceType); + if (RoutingConventionDisabled(controller) == false) continue; @@ -74,12 +89,12 @@ private bool RoutingConventionDisabled(ControllerModel controller) /// private string TemplateFromResource(ControllerModel model) { - var resourceType = GetResourceTypeFromController(model.ControllerType); - if (resourceType != null) + if (_registeredResources.TryGetValue(model.ControllerName, out Type resourceType)) { var template = $"{_namespace}/{_formatter.FormatResourceName(resourceType)}"; - if (_registeredTemplates.Add(template)) + if (_registeredTemplates.Add(template)) return template; + } return null; } @@ -100,12 +115,25 @@ private string TemplateFromController(ControllerModel model) /// private Type GetResourceTypeFromController(Type type) { + var controllerBase = typeof(ControllerBase); + var jsonApiMixin = typeof(JsonApiControllerMixin); var target = typeof(BaseJsonApiController<,>); - var currentBaseType = type.BaseType; + var identifiable = typeof(IIdentifiable); + var currentBaseType = type; while (!currentBaseType.IsGenericType || currentBaseType.GetGenericTypeDefinition() != target) { - currentBaseType = currentBaseType.BaseType; - if (currentBaseType == null) break; + var nextBaseType = currentBaseType.BaseType; + + if ( (nextBaseType == controllerBase || nextBaseType == jsonApiMixin) && currentBaseType.IsGenericType) + { + var potentialResource = currentBaseType.GetGenericArguments().FirstOrDefault(t => t.Inherits(identifiable)); + if (potentialResource != null) + return potentialResource; + } + + currentBaseType = nextBaseType; + if (nextBaseType == null) + break; } return currentBaseType?.GetGenericArguments().First(); } diff --git a/src/JsonApiDotNetCore/Internal/IControllerResourceMapping.cs b/src/JsonApiDotNetCore/Internal/IControllerResourceMapping.cs new file mode 100644 index 0000000000..347a85650c --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/IControllerResourceMapping.cs @@ -0,0 +1,15 @@ +using System; + +namespace JsonApiDotNetCore.Internal +{ + /// + /// Registery of which resource is associated with which controller. + /// + public interface IControllerResourceMapping + { + /// + /// Get the associated resource with the controller with the provided controller name + /// + Type GetAssociatedResource(string controllerName); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Internal/IJsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Internal/IJsonApiRoutingConvention.cs index aba03b806b..00eed0b4c0 100644 --- a/src/JsonApiDotNetCore/Internal/IJsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Internal/IJsonApiRoutingConvention.cs @@ -6,5 +6,5 @@ namespace JsonApiDotNetCore.Internal /// Service for specifying which routing convention to use. This can be overriden to customize /// the relation between controllers and mapped routes. /// - public interface IJsonApiRoutingConvention : IApplicationModelConvention { } + public interface IJsonApiRoutingConvention : IApplicationModelConvention, IControllerResourceMapping { } } diff --git a/src/JsonApiDotNetCore/Internal/InverseRelationships.cs b/src/JsonApiDotNetCore/Internal/InverseRelationships.cs index fbfd4b9da7..ee6b9aa249 100644 --- a/src/JsonApiDotNetCore/Internal/InverseRelationships.cs +++ b/src/JsonApiDotNetCore/Internal/InverseRelationships.cs @@ -1,4 +1,3 @@ -using System; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; @@ -24,17 +23,18 @@ public interface IInverseRelationships /// deal with resolving the inverse relationships. /// void Resolve(); + } /// public class InverseRelationships : IInverseRelationships { - private readonly ResourceGraph _graph; + private readonly IContextEntityProvider _provider; private readonly IDbContextResolver _resolver; - public InverseRelationships(IResourceGraph graph, IDbContextResolver resolver = null) + public InverseRelationships(IContextEntityProvider provider, IDbContextResolver resolver = null) { - _graph = (ResourceGraph)graph; + _provider = provider; _resolver = resolver; } @@ -45,7 +45,7 @@ public void Resolve() { DbContext context = _resolver.GetContext(); - foreach (ContextEntity ce in _graph.Entities) + foreach (ContextEntity ce in _provider.GetContextEntities()) { IEntityType meta = context.Model.FindEntityType(ce.EntityType); if (meta == null) continue; @@ -63,10 +63,6 @@ public void Resolve() /// If EF Core is not being used, we're expecting the resolver to not be registered. /// /// true, if entity framework core was enabled, false otherwise. - /// Resolver. - private bool EntityFrameworkCoreIsEnabled() - { - return _resolver != null; - } + private bool EntityFrameworkCoreIsEnabled() => _resolver != null; } } diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index b1b408b3bd..dab6833fb1 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -1,153 +1,150 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal { - public class ControllerResourceMap - { - public string ControllerName { get; set; } - public Type Resource { get; set; } - } - /// /// keeps track of all the models/resources defined in JADNC /// public class ResourceGraph : IResourceGraph { - internal List Entities { get; } internal List ValidationResults { get; } + private List _entities { get; } - public List ControllerResourceMap { get; internal set; } - - [Obsolete("please instantiate properly, dont use the static constructor")] - internal static IResourceGraph Instance { get; set; } - - public ResourceGraph() { } - [Obsolete("Use new one")] - public ResourceGraph(List entities, bool usesDbContext) - { - Entities = entities; - UsesDbContext = usesDbContext; - ValidationResults = new List(); - Instance = this; - } - - // eventually, this is the planned public constructor - // to avoid breaking changes, we will be leaving the original constructor in place - // until the context graph validation process is completed - // you can track progress on this issue here: https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170 - internal ResourceGraph(List entities, bool usesDbContext, List validationResults, List controllerContexts) + public ResourceGraph(List entities, List validationResults = null) { - ControllerResourceMap = controllerContexts; - Entities = entities; - UsesDbContext = usesDbContext; + _entities = entities; ValidationResults = validationResults; - Instance = this; } /// - public bool UsesDbContext { get; } + public ContextEntity[] GetContextEntities() => _entities.ToArray(); /// - public object GetRelationship(TParent entity, string relationshipName) - { - var parentEntityType = entity.GetType(); - - var navigationProperty = parentEntityType - .GetProperties() - .SingleOrDefault(p => string.Equals(p.Name, relationshipName, StringComparison.OrdinalIgnoreCase)); + public ContextEntity GetContextEntity(string entityName) + => _entities.SingleOrDefault(e => string.Equals(e.EntityName, entityName, StringComparison.OrdinalIgnoreCase)); - if (navigationProperty == null) - throw new JsonApiException(400, $"{parentEntityType} does not contain a relationship named {relationshipName}"); + /// + public ContextEntity GetContextEntity(Type entityType) + => _entities.SingleOrDefault(e => e.EntityType == entityType); + /// + public ContextEntity GetContextEntity() where TResource : class, IIdentifiable + => GetContextEntity(typeof(TResource)); - return navigationProperty.GetValue(entity); + /// + public List GetFields(Expression> selector = null) where T : IIdentifiable + { + return Getter(selector).ToList(); } - - public object GetRelationshipValue(TParent resource, RelationshipAttribute relationship) where TParent : IIdentifiable + /// + public List GetAttributes(Expression> selector = null) where T : IIdentifiable { - if (relationship is HasManyThroughAttribute hasManyThroughRelationship) - { - return GetHasManyThrough(resource, hasManyThroughRelationship); - } - - return GetRelationship(resource, relationship.InternalRelationshipName); + return Getter(selector, FieldFilterType.Attribute).Cast().ToList(); } - - private IEnumerable GetHasManyThrough(IIdentifiable parent, HasManyThroughAttribute hasManyThrough) + /// + public List GetRelationships(Expression> selector = null) where T : IIdentifiable { - var throughProperty = GetRelationship(parent, hasManyThrough.InternalThroughName); - if (throughProperty is IEnumerable hasManyNavigationEntity) - { - // wrap "yield return" in a sub-function so we can correctly return null if the property is null. - return GetHasManyThroughIter(hasManyThrough, hasManyNavigationEntity); - } - return null; + return Getter(selector, FieldFilterType.Relationship).Cast().ToList(); } - - private IEnumerable GetHasManyThroughIter(HasManyThroughAttribute hasManyThrough, IEnumerable hasManyNavigationEntity) + /// + public List GetFields(Type type) { - foreach (var includedEntity in hasManyNavigationEntity) - { - var targetValue = hasManyThrough.RightProperty.GetValue(includedEntity) as IIdentifiable; - yield return targetValue; - } + return GetContextEntity(type).Fields.ToList(); } - - /// - public string GetRelationshipName(string relationshipName) + /// + public List GetAttributes(Type type) { - var entityType = typeof(TParent); - return Entities - .SingleOrDefault(e => e.EntityType == entityType) - ?.Relationships - .SingleOrDefault(r => r.Is(relationshipName)) - ?.InternalRelationshipName; + return GetContextEntity(type).Attributes.ToList(); } - - public string GetPublicAttributeName(string internalAttributeName) + /// + public List GetRelationships(Type type) { - return GetContextEntity(typeof(TParent)) - .Attributes - .SingleOrDefault(a => a.InternalAttributeName == internalAttributeName)? - .PublicAttributeName; + return GetContextEntity(type).Relationships.ToList(); } - public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relationship) + /// + public RelationshipAttribute GetInverse(RelationshipAttribute relationship) { if (relationship.InverseNavigation == null) return null; - return GetContextEntity(relationship.DependentType).Relationships.SingleOrDefault(r => r.InternalRelationshipName == relationship.InverseNavigation); + return GetContextEntity(relationship.DependentType) + .Relationships + .SingleOrDefault(r => r.InternalRelationshipName == relationship.InverseNavigation); } - public ContextEntity GetEntityFromControllerName(string controllerName) + private IEnumerable Getter(Expression> selector = null, FieldFilterType type = FieldFilterType.None) where T : IIdentifiable { + IEnumerable available; + if (type == FieldFilterType.Attribute) + available = GetContextEntity(typeof(T)).Attributes.Cast(); + else if (type == FieldFilterType.Relationship) + available = GetContextEntity(typeof(T)).Relationships.Cast(); + else + available = GetContextEntity(typeof(T)).Fields; + + if (selector == null) + return available; + + var targeted = new List(); + + if (selector.Body is MemberExpression memberExpression) + { // model => model.Field1 + try + { + targeted.Add(available.Single(f => f.ExposedInternalMemberName == memberExpression.Member.Name)); + return targeted; + } + catch (Exception ex) + { + ThrowNotExposedError(memberExpression.Member.Name, type); + } + } - if (ControllerResourceMap.Any()) - { - // Autodiscovery was used, so there is a well defined mapping between exposed resources and their associated controllers - var resourceType = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource; - if (resourceType == null) return null; - return Entities.First(e => e.EntityType == resourceType); - - } else - { - // No autodiscovery: try to guess contextentity from controller name. - return Entities.FirstOrDefault(e => e.EntityName.ToLower().Replace("-", "") == controllerName.ToLower()); + + if (selector.Body is NewExpression newExpression) + { // model => new { model.Field1, model.Field2 } + string memberName = null; + try + { + if (newExpression.Members == null) + return targeted; + + foreach (var member in newExpression.Members) + { + memberName = member.Name; + targeted.Add(available.Single(f => f.ExposedInternalMemberName == memberName)); + } + return targeted; + } + catch (Exception ex) + { + ThrowNotExposedError(memberName, type); + } } + + throw new ArgumentException($"The expression returned by '{selector}' for '{GetType()}' is of type {selector.Body.GetType()}" + + " and cannot be used to select resource attributes. The type must be a NewExpression.Example: article => new { article.Author };"); + + } + + private void ThrowNotExposedError(string memberName, FieldFilterType type) + { + throw new ArgumentException($"{memberName} is not an json:api exposed {type.ToString("g")}."); + } + + /// + /// internally used only by . + /// + private enum FieldFilterType + { + None, + Attribute, + Relationship } - /// - public ContextEntity GetContextEntity(string entityName) - => Entities.SingleOrDefault(e => string.Equals(e.EntityName, entityName, StringComparison.OrdinalIgnoreCase)); - /// - public ContextEntity GetContextEntity(Type entityType) - => Entities.SingleOrDefault(e => e.EntityType == entityType); - /// - public ContextEntity GetContextEntity() where TResource : class, IIdentifiable - => GetContextEntity(typeof(TResource)); } } diff --git a/src/JsonApiDotNetCore/Internal/TypeHelper.cs b/src/JsonApiDotNetCore/Internal/TypeHelper.cs index 4acee2d910..e5938472c1 100644 --- a/src/JsonApiDotNetCore/Internal/TypeHelper.cs +++ b/src/JsonApiDotNetCore/Internal/TypeHelper.cs @@ -80,7 +80,7 @@ public static T ConvertType(object value) public static Type GetTypeOfList(Type type) { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) + if (type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { return type.GetGenericArguments()[0]; } diff --git a/src/JsonApiDotNetCore/Internal/ValidationResults.cs b/src/JsonApiDotNetCore/Internal/ValidationResults.cs index fbaa6eb462..93fa32c74b 100644 --- a/src/JsonApiDotNetCore/Internal/ValidationResults.cs +++ b/src/JsonApiDotNetCore/Internal/ValidationResults.cs @@ -2,7 +2,7 @@ namespace JsonApiDotNetCore.Internal { - internal class ValidationResult + public class ValidationResult { public ValidationResult(LogLevel logLevel, string message) { diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiExceptionFilter.cs b/src/JsonApiDotNetCore/Middleware/DefaultExceptionFilter.cs similarity index 69% rename from src/JsonApiDotNetCore/Middleware/JsonApiExceptionFilter.cs rename to src/JsonApiDotNetCore/Middleware/DefaultExceptionFilter.cs index dda1fd3b89..b6c82b27e3 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiExceptionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/DefaultExceptionFilter.cs @@ -1,17 +1,20 @@ -using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Middleware { - public class JsonApiExceptionFilter : ActionFilterAttribute, IExceptionFilter + /// + /// Global exception filter that wraps any thrown error with a JsonApiException. + /// + public class DefaultExceptionFilter : ActionFilterAttribute, IExceptionFilter { private readonly ILogger _logger; - public JsonApiExceptionFilter(ILoggerFactory loggerFactory) + public DefaultExceptionFilter(ILoggerFactory loggerFactory) { - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); } public void OnException(ExceptionContext context) diff --git a/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs b/src/JsonApiDotNetCore/Middleware/DefaultTypeMatchFilter.cs similarity index 75% rename from src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs rename to src/JsonApiDotNetCore/Middleware/DefaultTypeMatchFilter.cs index e3e474740e..0465dd00b9 100644 --- a/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/DefaultTypeMatchFilter.cs @@ -1,25 +1,24 @@ -using System; +using System; using System.Linq; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; namespace JsonApiDotNetCore.Middleware { - public class TypeMatchFilter : IActionFilter + /// + /// Action filter used to verify the incoming type matches the target type, else return a 409 + /// + public class DefaultTypeMatchFilter : IActionFilter { - private readonly IResourceGraph _resourceGraph; + private readonly IContextEntityProvider _provider; - public TypeMatchFilter(IResourceGraph resourceGraph) + public DefaultTypeMatchFilter(IContextEntityProvider provider) { - _resourceGraph = resourceGraph; + _provider = provider; } - /// - /// Used to verify the incoming type matches the target type, else return a 409 - /// public void OnActionExecuting(ActionExecutingContext context) { var request = context.HttpContext.Request; @@ -30,7 +29,7 @@ public void OnActionExecuting(ActionExecutingContext context) if (deserializedType != null && targetType != null && deserializedType != targetType) { - var expectedJsonApiResource = _resourceGraph.GetContextEntity(targetType); + var expectedJsonApiResource = _provider.GetContextEntity(targetType); throw new JsonApiException(409, $"Cannot '{context.HttpContext.Request.Method}' type '{deserializedType.Name}' " diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiExceptionFilterProvider.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiExceptionFilterProvider.cs new file mode 100644 index 0000000000..6400fa3a50 --- /dev/null +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiExceptionFilterProvider.cs @@ -0,0 +1,20 @@ +using System; + +namespace JsonApiDotNetCore.Middleware +{ + /// + /// Provides the type of the global exception filter that is configured in MVC during startup. + /// This can be overridden to let JADNC use your own exception filter. The default exception filter used + /// is + /// + public interface IJsonApiExceptionFilterProvider + { + Type Get(); + } + + /// + public class JsonApiExceptionFilterProvider : IJsonApiExceptionFilterProvider + { + public Type Get() => typeof(DefaultExceptionFilter); + } +} diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiTypeMatchFilterProvider.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiTypeMatchFilterProvider.cs new file mode 100644 index 0000000000..50d2476890 --- /dev/null +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiTypeMatchFilterProvider.cs @@ -0,0 +1,20 @@ +using System; + +namespace JsonApiDotNetCore.Middleware +{ + /// + /// Provides the type of the global action filter that is configured in MVC during startup. + /// This can be overridden to let JADNC use your own action filter. The default action filter used + /// is + /// + public interface IJsonApiTypeMatchFilterProvider + { + Type Get(); + } + + /// + public class JsonApiTypeMatchFilterProvider : IJsonApiTypeMatchFilterProvider + { + public Type Get() => typeof(DefaultTypeMatchFilter); + } +} diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 53ace47531..13bffd4da0 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -23,6 +23,7 @@ public class CurrentRequestMiddleware private ICurrentRequest _currentRequest; private IResourceGraph _resourceGraph; private IJsonApiOptions _options; + private IControllerResourceMapping _controllerResourceMapping; public CurrentRequestMiddleware(RequestDelegate next) { @@ -30,12 +31,14 @@ public CurrentRequestMiddleware(RequestDelegate next) } public async Task Invoke(HttpContext httpContext, - IJsonApiOptions options, + IControllerResourceMapping controllerResourceMapping, + IJsonApiOptions options, ICurrentRequest currentRequest, IResourceGraph resourceGraph) - { + { _httpContext = httpContext; _currentRequest = currentRequest; + _controllerResourceMapping = controllerResourceMapping; _resourceGraph = resourceGraph; _options = options; var requestResource = GetCurrentEntity(); @@ -44,7 +47,7 @@ public async Task Invoke(HttpContext httpContext, _currentRequest.SetRequestResource(GetCurrentEntity()); _currentRequest.IsRelationshipPath = PathIsRelationship(); _currentRequest.BasePath = GetBasePath(_currentRequest.GetRequestResource().EntityName); - } + } if (IsValid()) { @@ -60,10 +63,7 @@ private string GetBasePath(string entityName) { return GetNamespaceFromPath(r.Path, entityName); } - else - { - return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; - } + return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; } internal static string GetNamespaceFromPath(string path, string entityName) { @@ -162,15 +162,15 @@ private void FlushResponse(HttpContext context, int statusCode) /// /// Gets the current entity that we need for serialization and deserialization. /// - /// - /// /// private ContextEntity GetCurrentEntity() { var controllerName = (string)_httpContext.GetRouteData().Values["controller"]; + var resourceType = _controllerResourceMapping.GetAssociatedResource(controllerName); + var requestResource = _resourceGraph.GetContextEntity(resourceType); + if (requestResource == null) + return requestResource; var rd = _httpContext.GetRouteData().Values; - var requestResource = _resourceGraph.GetEntityFromControllerName(controllerName); - if (rd.TryGetValue("relationshipName", out object relationshipName)) _currentRequest.RequestRelationship = requestResource.Relationships.Single(r => r.PublicRelationshipName == (string)relationshipName); return requestResource; diff --git a/src/JsonApiDotNetCore/Models/Annotation/HasManyAttribute.cs b/src/JsonApiDotNetCore/Models/Annotation/HasManyAttribute.cs index a69cb82c56..7a05dd8fe4 100644 --- a/src/JsonApiDotNetCore/Models/Annotation/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore/Models/Annotation/HasManyAttribute.cs @@ -31,18 +31,30 @@ public HasManyAttribute(string publicName = null, Link relationshipLinks = Link. InverseNavigation = inverseNavigationProperty; } + /// + /// Gets the value of the navigation property, defined by the relationshipName, + /// on the provided instance. + /// + public override object GetValue(object entity) + { + return entity?.GetType()? + .GetProperty(InternalRelationshipName)? + .GetValue(entity); + } + + /// /// Sets the value of the property identified by this attribute /// - /// The target object + /// The target object /// The new property value - public override void SetValue(object resource, object newValue) + public override void SetValue(object entity, object newValue) { - var propertyInfo = resource + var propertyInfo = entity .GetType() .GetProperty(InternalRelationshipName); - propertyInfo.SetValue(resource, newValue); + propertyInfo.SetValue(entity, newValue); } } } diff --git a/src/JsonApiDotNetCore/Models/Annotation/HasManyThroughAttribute.cs b/src/JsonApiDotNetCore/Models/Annotation/HasManyThroughAttribute.cs index 235607c6d3..6d60f4b660 100644 --- a/src/JsonApiDotNetCore/Models/Annotation/HasManyThroughAttribute.cs +++ b/src/JsonApiDotNetCore/Models/Annotation/HasManyThroughAttribute.cs @@ -1,5 +1,10 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Reflection; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models.Links; namespace JsonApiDotNetCore.Models @@ -65,6 +70,63 @@ public HasManyThroughAttribute(string publicName, string internalThroughName, Li InternalThroughName = internalThroughName; } + /// + /// Traverses the through the provided entity and returns the + /// value of the relationship on the other side of a join entity + /// (e.g. Articles.ArticleTags.Tag). + /// + public override object GetValue(object entity) + { + var throughNavigationProperty = entity.GetType() + .GetProperties() + .SingleOrDefault(p => string.Equals(p.Name, InternalThroughName, StringComparison.OrdinalIgnoreCase)); + + var throughEntities = throughNavigationProperty.GetValue(entity); + + if (throughEntities == null) + // return an empty list for the right-type of the property. + return TypeHelper.CreateListFor(DependentType); + + // the right entities are included on the navigation/through entities. Extract and return them. + var rightEntities = new List(); + foreach (var rightEntity in (IList)throughEntities) + rightEntities.Add((IIdentifiable)RightProperty.GetValue(rightEntity)); + + return rightEntities.Cast(DependentType); + } + + + /// + /// Sets the value of the property identified by this attribute + /// + /// The target object + /// The new property value + public override void SetValue(object entity, object newValue) + { + var propertyInfo = entity + .GetType() + .GetProperty(InternalRelationshipName); + propertyInfo.SetValue(entity, newValue); + + if (newValue == null) + { + ThroughProperty.SetValue(entity, null); + } + else + { + var throughRelationshipCollection = (IList)Activator.CreateInstance(ThroughProperty.PropertyType); + ThroughProperty.SetValue(entity, throughRelationshipCollection); + + foreach (IIdentifiable pointer in (IList)newValue) + { + var throughInstance = Activator.CreateInstance(ThroughType); + LeftProperty.SetValue(throughInstance, entity); + RightProperty.SetValue(throughInstance, pointer); + throughRelationshipCollection.Add(throughInstance); + } + } + } + /// /// The name of the join property on the parent resource. /// diff --git a/src/JsonApiDotNetCore/Models/Annotation/HasOneAttribute.cs b/src/JsonApiDotNetCore/Models/Annotation/HasOneAttribute.cs index 1d5ac7c2de..1037aa9882 100644 --- a/src/JsonApiDotNetCore/Models/Annotation/HasOneAttribute.cs +++ b/src/JsonApiDotNetCore/Models/Annotation/HasOneAttribute.cs @@ -37,6 +37,14 @@ public HasOneAttribute(string publicName = null, Link links = Link.NotConfigured InverseNavigation = inverseNavigationProperty; } + + public override object GetValue(object entity) + { + return entity?.GetType()? + .GetProperty(InternalRelationshipName)? + .GetValue(entity); + } + private readonly string _explicitIdentifiablePropertyName; /// @@ -49,23 +57,23 @@ public HasOneAttribute(string publicName = null, Link links = Link.NotConfigured /// /// Sets the value of the property identified by this attribute /// - /// The target object + /// The target object /// The new property value - public override void SetValue(object resource, object newValue) + public override void SetValue(object entity, object newValue) { string propertyName = InternalRelationshipName; // if we're deleting the relationship (setting it to null), // we set the foreignKey to null. We could also set the actual property to null, // but then we would first need to load the current relationship, which requires an extra query. if (newValue == null) propertyName = IdentifiablePropertyName; - var resourceType = resource.GetType(); + var resourceType = entity.GetType(); var propertyInfo = resourceType.GetProperty(propertyName); if (propertyInfo == null) { // we can't set the FK to null because there isn't any. propertyInfo = resourceType.GetProperty(RelationshipPath); } - propertyInfo.SetValue(resource, newValue); + propertyInfo.SetValue(entity, newValue); } // HACK: this will likely require boxing diff --git a/src/JsonApiDotNetCore/Models/Annotation/RelationshipAttribute.cs b/src/JsonApiDotNetCore/Models/Annotation/RelationshipAttribute.cs index 86625f0092..1a5bd5dea2 100644 --- a/src/JsonApiDotNetCore/Models/Annotation/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore/Models/Annotation/RelationshipAttribute.cs @@ -66,34 +66,9 @@ protected RelationshipAttribute(string publicName, Link relationshipLinks, bool public bool CanInclude { get; } public string EntityPropertyName { get; } - public bool TryGetHasOne(out HasOneAttribute result) - { - if (IsHasOne) - { - result = (HasOneAttribute)this; - return true; - } - result = null; - return false; - } - - public bool TryGetHasMany(out HasManyAttribute result) - { - if (IsHasMany) - { - result = (HasManyAttribute)this; - return true; - } - result = null; - return false; - } - public abstract void SetValue(object entity, object newValue); - - public object GetValue(object entity) => entity - ?.GetType()? - .GetProperty(InternalRelationshipName)? - .GetValue(entity); + + public abstract object GetValue(object entity); public override string ToString() { diff --git a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs index 9d1c65a4c9..d23df72e30 100644 --- a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs @@ -29,23 +29,17 @@ public interface IResourceDefinition public class ResourceDefinition : IResourceDefinition, IResourceHookContainer where TResource : class, IIdentifiable { private readonly ContextEntity _contextEntity; - private readonly IFieldsExplorer _fieldExplorer; + private readonly IResourceGraph _resourceGraph; private List _allowedAttributes; private List _allowedRelationships; - public ResourceDefinition(IFieldsExplorer fieldExplorer, IResourceGraph graph) + public ResourceDefinition(IResourceGraph resourceGraph) { - _contextEntity = graph.GetContextEntity(typeof(TResource)); + _contextEntity = resourceGraph.GetContextEntity(typeof(TResource)); _allowedAttributes = _contextEntity.Attributes; _allowedRelationships = _contextEntity.Relationships; - _fieldExplorer = fieldExplorer; + _resourceGraph = resourceGraph; } - public ResourceDefinition(IResourceGraph graph) - { - _contextEntity = graph.GetContextEntity(typeof(TResource)); - _allowedAttributes = _contextEntity.Attributes; - _allowedRelationships = _contextEntity.Relationships; - } public List GetAllowedRelationships() => _allowedRelationships; public List GetAllowedAttributes() => _allowedAttributes; @@ -57,7 +51,7 @@ public ResourceDefinition(IResourceGraph graph) /// Should be of the form: (TResource e) => new { e.Attribute1, e.Arttribute2, e.Relationship1, e.Relationship2 } public void HideFields(Expression> selector) { - var fieldsToHide = _fieldExplorer.GetFields(selector); + var fieldsToHide = _resourceGraph.GetFields(selector); _allowedAttributes = _allowedAttributes.Except(fieldsToHide.Where(f => f is AttrAttribute)).Cast().ToList(); _allowedRelationships = _allowedRelationships.Except(fieldsToHide.Where(f => f is RelationshipAttribute)).Cast().ToList(); } @@ -164,7 +158,7 @@ public class QueryFilters : Dictionary, Filte { var order = new List<(AttrAttribute, SortDirection)>(); foreach (var sortProp in defaultSortOrder) - order.Add((_fieldExplorer.GetAttributes(sortProp.Item1).Single(), sortProp.Item2)); + order.Add((_resourceGraph.GetAttributes(sortProp.Item1).Single(), sortProp.Item2)); return order; } diff --git a/src/JsonApiDotNetCore/QueryParameterServices/Common/QueryParameterService.cs b/src/JsonApiDotNetCore/QueryParameterServices/Common/QueryParameterService.cs index 9673271a94..fa57e1850f 100644 --- a/src/JsonApiDotNetCore/QueryParameterServices/Common/QueryParameterService.cs +++ b/src/JsonApiDotNetCore/QueryParameterServices/Common/QueryParameterService.cs @@ -14,12 +14,12 @@ namespace JsonApiDotNetCore.Query /// public abstract class QueryParameterService { - protected readonly IContextEntityProvider _contextEntityProvider; + protected readonly IResourceGraph _resourceGraph; protected readonly ContextEntity _requestResource; - protected QueryParameterService(IContextEntityProvider contextEntityProvider, ICurrentRequest currentRequest) + protected QueryParameterService(IResourceGraph resourceGraph, ICurrentRequest currentRequest) { - _contextEntityProvider = contextEntityProvider; + _resourceGraph = resourceGraph; _requestResource = currentRequest.GetRequestResource(); } @@ -48,15 +48,9 @@ protected AttrAttribute GetAttribute(string target, RelationshipAttribute relati { AttrAttribute attribute; if (relationship != null) - { - var relatedContextEntity = _contextEntityProvider.GetContextEntity(relationship.DependentType); - attribute = relatedContextEntity.Attributes - .FirstOrDefault(a => a.Is(target)); - } + attribute = _resourceGraph.GetAttributes(relationship.DependentType).FirstOrDefault(a => a.Is(target)); else - { attribute = _requestResource.Attributes.FirstOrDefault(attr => attr.Is(target)); - } if (attribute == null) throw new JsonApiException(400, $"'{target}' is not a valid attribute."); diff --git a/src/JsonApiDotNetCore/QueryParameterServices/FilterService.cs b/src/JsonApiDotNetCore/QueryParameterServices/FilterService.cs index 4c23ee3820..2984139fe7 100644 --- a/src/JsonApiDotNetCore/QueryParameterServices/FilterService.cs +++ b/src/JsonApiDotNetCore/QueryParameterServices/FilterService.cs @@ -16,7 +16,7 @@ public class FilterService : QueryParameterService, IFilterService private readonly List _filters; private IResourceDefinition _requestResourceDefinition; - public FilterService(IResourceDefinitionProvider resourceDefinitionProvider, IContextEntityProvider contextEntityProvider, ICurrentRequest currentRequest) : base(contextEntityProvider, currentRequest) + public FilterService(IResourceDefinitionProvider resourceDefinitionProvider, IResourceGraph resourceGraph, ICurrentRequest currentRequest) : base(resourceGraph, currentRequest) { _requestResourceDefinition = resourceDefinitionProvider.Get(_requestResource.EntityType); _filters = new List(); diff --git a/src/JsonApiDotNetCore/QueryParameterServices/IncludeService.cs b/src/JsonApiDotNetCore/QueryParameterServices/IncludeService.cs index c68c6f4634..816033eab1 100644 --- a/src/JsonApiDotNetCore/QueryParameterServices/IncludeService.cs +++ b/src/JsonApiDotNetCore/QueryParameterServices/IncludeService.cs @@ -14,7 +14,7 @@ public class IncludeService : QueryParameterService, IIncludeService /// todo: use read-only lists. private readonly List> _includedChains; - public IncludeService(IContextEntityProvider contextEntityProvider, ICurrentRequest currentRequest) : base(contextEntityProvider, currentRequest) + public IncludeService(IResourceGraph resourceGraph, ICurrentRequest currentRequest) : base(resourceGraph, currentRequest) { _includedChains = new List>(); } @@ -52,7 +52,7 @@ private void ParseChain(string chain) throw CannotIncludeError(resourceContext, relationshipName); parsedChain.Add(relationship); - resourceContext = _contextEntityProvider.GetContextEntity(relationship.DependentType); + resourceContext = _resourceGraph.GetContextEntity(relationship.DependentType); } _includedChains.Add(parsedChain); } diff --git a/src/JsonApiDotNetCore/QueryParameterServices/SortService.cs b/src/JsonApiDotNetCore/QueryParameterServices/SortService.cs index da2dbd7a34..45a8fca0bb 100644 --- a/src/JsonApiDotNetCore/QueryParameterServices/SortService.cs +++ b/src/JsonApiDotNetCore/QueryParameterServices/SortService.cs @@ -17,9 +17,9 @@ public class SortService : QueryParameterService, ISortService private bool _isProcessed; public SortService(IResourceDefinitionProvider resourceDefinitionProvider, - IContextEntityProvider contextEntityProvider, + IResourceGraph resourceGraph, ICurrentRequest currentRequest) - : base(contextEntityProvider, currentRequest) + : base(resourceGraph, currentRequest) { _resourceDefinitionProvider = resourceDefinitionProvider; _queries = new List(); diff --git a/src/JsonApiDotNetCore/QueryParameterServices/SparseFieldsService.cs b/src/JsonApiDotNetCore/QueryParameterServices/SparseFieldsService.cs index e7ea772582..e6b0fcf56b 100644 --- a/src/JsonApiDotNetCore/QueryParameterServices/SparseFieldsService.cs +++ b/src/JsonApiDotNetCore/QueryParameterServices/SparseFieldsService.cs @@ -24,7 +24,7 @@ public class SparseFieldsService : QueryParameterService, ISparseFieldsService public override string Name => "fields"; - public SparseFieldsService(IContextEntityProvider contextEntityProvider, ICurrentRequest currentRequest) : base(contextEntityProvider, currentRequest) + public SparseFieldsService(IResourceGraph resourceGraph, ICurrentRequest currentRequest) : base(resourceGraph, currentRequest) { _selectedFields = new List(); _selectedRelationshipFields = new Dictionary>(); @@ -75,7 +75,6 @@ public virtual void Parse(KeyValuePair queryParameter) foreach (var field in fields) RegisterRelatedResourceField(field, relationship); - } } @@ -84,7 +83,7 @@ public virtual void Parse(KeyValuePair queryParameter) /// private void RegisterRelatedResourceField(string field, RelationshipAttribute relationship) { - var relationProperty = _contextEntityProvider.GetContextEntity(relationship.DependentType); + var relationProperty = _resourceGraph.GetContextEntity(relationship.DependentType); var attr = relationProperty.Attributes.SingleOrDefault(a => a.Is(field)); if (attr == null) throw new JsonApiException(400, $"'{relationship.DependentType.Name}' does not contain '{field}'."); diff --git a/src/JsonApiDotNetCore/Serialization/Client/RequestSerializer.cs b/src/JsonApiDotNetCore/Serialization/Client/RequestSerializer.cs index a27009f456..7f5d827dbe 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/RequestSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/RequestSerializer.cs @@ -17,13 +17,12 @@ public class RequestSerializer : BaseDocumentBuilder, IRequestSerializer private readonly Dictionary> _attributesToSerializeCache; private readonly Dictionary> _relationshipsToSerializeCache; private Type _currentTargetedResource; - private readonly IFieldsExplorer _fieldExplorer; - public RequestSerializer(IFieldsExplorer fieldExplorer, - IContextEntityProvider provider, + private readonly IResourceGraph _resourceGraph; + public RequestSerializer(IResourceGraph resourceGraph, IResourceObjectBuilder resourceObjectBuilder) - : base(resourceObjectBuilder, provider) + : base(resourceObjectBuilder, resourceGraph) { - _fieldExplorer = fieldExplorer; + _resourceGraph = resourceGraph; _attributesToSerializeCache = new Dictionary>(); _relationshipsToSerializeCache = new Dictionary>(); } @@ -64,7 +63,7 @@ public string Serialize(IEnumerable entities) public void SetAttributesToSerialize(Expression> filter) where TResource : class, IIdentifiable { - var allowedAttributes = _fieldExplorer.GetAttributes(filter); + var allowedAttributes = _resourceGraph.GetAttributes(filter); _attributesToSerializeCache[typeof(TResource)] = allowedAttributes; } @@ -72,7 +71,7 @@ public void SetAttributesToSerialize(Expression(Expression> filter) where TResource : class, IIdentifiable { - var allowedRelationships = _fieldExplorer.GetRelationships(filter); + var allowedRelationships = _resourceGraph.GetRelationships(filter); _relationshipsToSerializeCache[typeof(TResource)] = allowedRelationships; } @@ -90,7 +89,7 @@ private List GetAttributesToSerialize(IIdentifiable entity) return new List(); if (!_attributesToSerializeCache.TryGetValue(resourceType, out var attributes)) - return _fieldExplorer.GetAttributes(resourceType); + return _resourceGraph.GetAttributes(resourceType); return attributes; } diff --git a/src/JsonApiDotNetCore/Serialization/Common/ResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Common/ResourceObjectBuilder.cs index f57d954f46..589d4ef4ac 100644 --- a/src/JsonApiDotNetCore/Serialization/Common/ResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Common/ResourceObjectBuilder.cs @@ -12,14 +12,12 @@ namespace JsonApiDotNetCore.Serialization /// public class ResourceObjectBuilder : IResourceObjectBuilder { - protected readonly IResourceGraph _resourceGraph; protected readonly IContextEntityProvider _provider; private readonly ResourceObjectBuilderSettings _settings; private const string _identifiablePropertyName = nameof(Identifiable.Id); - public ResourceObjectBuilder(IResourceGraph resourceGraph, IContextEntityProvider provider, ResourceObjectBuilderSettings settings) + public ResourceObjectBuilder(IContextEntityProvider provider, ResourceObjectBuilderSettings settings) { - _resourceGraph = resourceGraph; _provider = provider; _settings = settings; } @@ -69,10 +67,10 @@ protected object GetRelatedResourceLinkage(RelationshipAttribute relationship, I /// /// Builds a for a HasOne relationship /// - private ResourceIdentifierObject GetRelatedResourceLinkage(HasOneAttribute attr, IIdentifiable entity) + private ResourceIdentifierObject GetRelatedResourceLinkage(HasOneAttribute relationship, IIdentifiable entity) { - var relatedEntity = (IIdentifiable)_resourceGraph.GetRelationshipValue(entity, attr); - if (relatedEntity == null && IsRequiredToOneRelationship(attr, entity)) + var relatedEntity = (IIdentifiable)relationship.GetValue(entity); + if (relatedEntity == null && IsRequiredToOneRelationship(relationship, entity)) throw new NotSupportedException("Cannot serialize a required to one relationship that is not populated but was included in the set of relationships to be serialized."); if (relatedEntity != null) @@ -84,9 +82,9 @@ private ResourceIdentifierObject GetRelatedResourceLinkage(HasOneAttribute attr, /// /// Builds the s for a HasMany relationship /// - private List GetRelatedResourceLinkage(HasManyAttribute attr, IIdentifiable entity) + private List GetRelatedResourceLinkage(HasManyAttribute relationship, IIdentifiable entity) { - var relatedEntities = (IEnumerable)_resourceGraph.GetRelationshipValue(entity, attr); + var relatedEntities = (IEnumerable)relationship.GetValue(entity); var manyData = new List(); if (relatedEntities != null) foreach (IIdentifiable relatedEntity in relatedEntities) diff --git a/src/JsonApiDotNetCore/Serialization/Server/Builders/IncludedResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Server/Builders/IncludedResourceObjectBuilder.cs index 5d66cedfa9..c663b8ec5a 100644 --- a/src/JsonApiDotNetCore/Serialization/Server/Builders/IncludedResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Server/Builders/IncludedResourceObjectBuilder.cs @@ -16,10 +16,9 @@ public class IncludedResourceObjectBuilder : ResourceObjectBuilder, IIncludedRes public IncludedResourceObjectBuilder(IFieldsToSerialize fieldsToSerialize, ILinkBuilder linkBuilder, - IResourceGraph resourceGraph, IContextEntityProvider provider, IResourceObjectBuilderSettingsProvider settingsProvider) - : base(resourceGraph, provider, settingsProvider.Get()) + : base(provider, settingsProvider.Get()) { _included = new HashSet(new ResourceObjectComparer()); _fieldsToSerialize = fieldsToSerialize; @@ -55,7 +54,7 @@ public void IncludeRelationshipChain(List inclusionChain, /// starting from the first related entity. var relationship = inclusionChain.First(); var chainRemainder = ShiftChain(inclusionChain); - var related = _resourceGraph.GetRelationshipValue(rootEntity, relationship); + var related = relationship.GetValue(rootEntity); ProcessChain(relationship, related, chainRemainder); } @@ -88,7 +87,7 @@ private void ProcessRelationship(RelationshipAttribute originRelationship, IIden if (relationshipEntry.HasResource) { // if the relationship is set, continue parsing the chain. - var related = _resourceGraph.GetRelationshipValue(parent, nextRelationship); + var related = nextRelationship.GetValue(parent); ProcessChain(nextRelationship, related, chainRemainder); } } diff --git a/src/JsonApiDotNetCore/Serialization/Server/Builders/ResponseResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Server/Builders/ResponseResourceObjectBuilder.cs index 9256330fcb..c8e134d001 100644 --- a/src/JsonApiDotNetCore/Serialization/Server/Builders/ResponseResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Server/Builders/ResponseResourceObjectBuilder.cs @@ -17,10 +17,9 @@ public class ResponseResourceObjectBuilder : ResourceObjectBuilder, IResourceObj public ResponseResourceObjectBuilder(ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder, IIncludeService includeService, - IResourceGraph resourceGraph, IContextEntityProvider provider, IResourceObjectBuilderSettingsProvider settingsProvider) - : base(resourceGraph, provider, settingsProvider.Get()) + : base(provider, settingsProvider.Get()) { _linkBuilder = linkBuilder; _includedBuilder = includedBuilder; diff --git a/src/JsonApiDotNetCore/Serialization/Server/FieldsToSerialize.cs b/src/JsonApiDotNetCore/Serialization/Server/FieldsToSerialize.cs index 0f020600cb..43dbcd6417 100644 --- a/src/JsonApiDotNetCore/Serialization/Server/FieldsToSerialize.cs +++ b/src/JsonApiDotNetCore/Serialization/Server/FieldsToSerialize.cs @@ -9,24 +9,17 @@ namespace JsonApiDotNetCore.Serialization.Server { /// - /// TODO: explore option out caching so we don't have to recalculate the list - /// of allowed attributes and relationships all the time. This is more efficient - /// for documents with many resource objects. public class FieldsToSerialize : IFieldsToSerialize { - private readonly IContextEntityProvider _resourceContextProvider; + private readonly IResourceGraph _resourceGraph; private readonly ISparseFieldsService _sparseFieldsService ; - private readonly IServiceProvider _provider; - private readonly Dictionary _resourceDefinitionCache = new Dictionary(); - private readonly IFieldsExplorer _fieldExplorer; + private readonly IResourceDefinitionProvider _provider; - public FieldsToSerialize(IFieldsExplorer fieldExplorer, - IContextEntityProvider resourceContextProvider, + public FieldsToSerialize(IResourceGraph resourceGraph, ISparseFieldsService sparseFieldsService, - IServiceProvider provider) + IResourceDefinitionProvider provider) { - _fieldExplorer = fieldExplorer; - _resourceContextProvider = resourceContextProvider; + _resourceGraph = resourceGraph; _sparseFieldsService = sparseFieldsService; _provider = provider; } @@ -34,9 +27,9 @@ public FieldsToSerialize(IFieldsExplorer fieldExplorer, /// public List GetAllowedAttributes(Type type, RelationshipAttribute relationship = null) { // get the list of all exposed atttributes for the given type. - var allowed = _fieldExplorer.GetAttributes(type); + var allowed = _resourceGraph.GetAttributes(type); - var resourceDefinition = GetResourceDefinition(type); + var resourceDefinition = _provider.Get(type); if (resourceDefinition != null) // The set of allowed attribrutes to be exposed was defined on the resource definition allowed = allowed.Intersect(resourceDefinition.GetAllowedAttributes()).ToList(); @@ -58,27 +51,13 @@ public List GetAllowedAttributes(Type type, RelationshipAttribute /// public List GetAllowedRelationships(Type type) { - var resourceDefinition = GetResourceDefinition(type); + var resourceDefinition = _provider.Get(type); if (resourceDefinition != null) // The set of allowed attribrutes to be exposed was defined on the resource definition return resourceDefinition.GetAllowedRelationships(); // The set of allowed attribrutes to be exposed was NOT defined on the resource definition: return all - return _fieldExplorer.GetRelationships(type); - } - - - /// consider to implement and inject a `ResourceDefinitionProvider` service. - private IResourceDefinition GetResourceDefinition(Type resourceType) - { - - var resourceDefinitionType = _resourceContextProvider.GetContextEntity(resourceType).ResourceType; - if (!_resourceDefinitionCache.TryGetValue(resourceDefinitionType, out IResourceDefinition resourceDefinition)) - { - resourceDefinition = _provider.GetService(resourceDefinitionType) as IResourceDefinition; - _resourceDefinitionCache.Add(resourceDefinitionType, resourceDefinition); - } - return resourceDefinition; + return _resourceGraph.GetRelationships(type); } } } diff --git a/src/JsonApiDotNetCore/Serialization/Server/RequestDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Server/RequestDeserializer.cs index 7c57556e7c..7283707b42 100644 --- a/src/JsonApiDotNetCore/Serialization/Server/RequestDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Server/RequestDeserializer.cs @@ -11,8 +11,8 @@ public class RequestDeserializer : BaseDocumentParser, IJsonApiDeserializer { private readonly ITargetedFields _targetedFields; - public RequestDeserializer(IResourceGraph resourceGraph, - ITargetedFields targetedFields) : base(resourceGraph) + public RequestDeserializer(IContextEntityProvider provider, + ITargetedFields targetedFields) : base(provider) { _targetedFields = targetedFields; } diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 232ac7308b..58d7ad1b3d 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -24,7 +24,6 @@ public class EntityResourceService : { private readonly IPageService _pageManager; private readonly IJsonApiOptions _options; - private readonly IResourceGraph _resourceGraph; private readonly IFilterService _filterService; private readonly ISortService _sortService; private readonly IEntityRepository _repository; @@ -42,7 +41,7 @@ public EntityResourceService( IIncludeService includeService, ISparseFieldsService sparseFieldsService, IPageService pageManager, - IResourceGraph resourceGraph, + IContextEntityProvider provider, IResourceHookExecutor hookExecutor = null, ILoggerFactory loggerFactory = null) { @@ -50,13 +49,12 @@ public EntityResourceService( _sparseFieldsService = sparseFieldsService; _pageManager = pageManager; _options = options; - _resourceGraph = resourceGraph; _sortService = sortService; _filterService = filterService; _repository = repository; _hookExecutor = hookExecutor; _logger = loggerFactory?.CreateLogger>(); - _currentRequestResource = resourceGraph.GetContextEntity(); + _currentRequestResource = provider.GetContextEntity(); } public virtual async Task CreateAsync(TResource entity) @@ -157,7 +155,7 @@ public virtual async Task GetRelationshipAsync(TId id, string relationsh { var relationship = GetRelationship(relationshipName); var resource = await GetRelationshipsAsync(id, relationshipName); - return _resourceGraph.GetRelationship(resource, relationship.InternalRelationshipName); + return relationship.GetValue(resource); } public virtual async Task UpdateAsync(TId id, TResource entity) @@ -326,10 +324,10 @@ public class EntityResourceService : EntityResourceService { public EntityResourceService(ISortService sortService, IFilterService filterService, IEntityRepository repository, - IJsonApiOptions options,IIncludeService includeService, ISparseFieldsService sparseFieldsService, - IPageService pageManager, IResourceGraph resourceGraph, + IJsonApiOptions options, IIncludeService includeService, ISparseFieldsService sparseFieldsService, + IPageService pageManager, IContextEntityProvider provider, IResourceHookExecutor hookExecutor = null, ILoggerFactory loggerFactory = null) - : base(sortService, filterService, repository, options, includeService, sparseFieldsService, pageManager, resourceGraph, hookExecutor, loggerFactory) + : base(sortService, filterService, repository, options, includeService, sparseFieldsService, pageManager, provider, hookExecutor, loggerFactory) { } } diff --git a/src/JsonApiDotNetCore/Services/ExposedFieldsExplorer.cs b/src/JsonApiDotNetCore/Services/ExposedFieldsExplorer.cs deleted file mode 100644 index 4db35cd1e2..0000000000 --- a/src/JsonApiDotNetCore/Services/ExposedFieldsExplorer.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Services -{ - /// - public class FieldsExplorer : IFieldsExplorer - { - private readonly IContextEntityProvider _provider; - - public FieldsExplorer(IContextEntityProvider provider) - { - _provider = provider; - } - /// - public List GetFields(Expression> selector = null) where T : IIdentifiable - { - return Getter(selector).ToList(); - } - /// - public List GetAttributes(Expression> selector = null) where T : IIdentifiable - { - return Getter(selector, FieldFilterType.Attribute).Cast().ToList(); - } - /// - public List GetRelationships(Expression> selector = null) where T : IIdentifiable - { - return Getter(selector, FieldFilterType.Relationship).Cast().ToList(); - } - /// - public List GetFields(Type type) - { - return _provider.GetContextEntity(type).Fields.ToList(); - } - /// - public List GetAttributes(Type type) - { - return _provider.GetContextEntity(type).Attributes.ToList(); - } - /// - public List GetRelationships(Type type) - { - return _provider.GetContextEntity(type).Relationships.ToList(); - } - - private IEnumerable Getter(Expression> selector = null, FieldFilterType type = FieldFilterType.None) where T : IIdentifiable - { - IEnumerable available; - if (type == FieldFilterType.Attribute) - available = _provider.GetContextEntity(typeof(T)).Attributes.Cast(); - else if (type == FieldFilterType.Relationship) - available = _provider.GetContextEntity(typeof(T)).Relationships.Cast(); - else - available = _provider.GetContextEntity(typeof(T)).Fields; - - if (selector == null) - return available; - - var targeted = new List(); - - if (selector.Body is MemberExpression memberExpression) - { // model => model.Field1 - try - { - targeted.Add(available.Single(f => f.ExposedInternalMemberName == memberExpression.Member.Name)); - return targeted; - } - catch (Exception ex) - { - ThrowNotExposedError(memberExpression.Member.Name, type); - } - } - - - if (selector.Body is NewExpression newExpression) - { // model => new { model.Field1, model.Field2 } - string memberName = null; - try - { - if (newExpression.Members == null) - return targeted; - - foreach (var member in newExpression.Members) - { - memberName = member.Name; - targeted.Add(available.Single(f => f.ExposedInternalMemberName == memberName)); - } - return targeted; - } - catch (Exception ex) - { - ThrowNotExposedError(memberName, type); - } - } - - throw new ArgumentException($"The expression returned by '{selector}' for '{GetType()}' is of type {selector.Body.GetType()}" - + " and cannot be used to select resource attributes. The type must be a NewExpression.Example: article => new { article.Author };"); - - } - - private void ThrowNotExposedError(string memberName, FieldFilterType type) - { - throw new ArgumentException($"{memberName} is not an json:api exposed {type.ToString("g")}."); - } - - /// - /// internally used only by . - /// - private enum FieldFilterType - { - None, - Attribute, - Relationship - } - } -} diff --git a/src/JsonApiDotNetCore/Services/ResourceDefinitionProvider.cs b/src/JsonApiDotNetCore/Services/ResourceDefinitionProvider.cs index 61234125f5..32ddcaf37c 100644 --- a/src/JsonApiDotNetCore/Services/ResourceDefinitionProvider.cs +++ b/src/JsonApiDotNetCore/Services/ResourceDefinitionProvider.cs @@ -8,10 +8,10 @@ namespace JsonApiDotNetCore.Query /// internal class ResourceDefinitionProvider : IResourceDefinitionProvider { - private readonly IContextEntityProvider _resourceContextProvider; + private readonly IResourceGraph _resourceContextProvider; private readonly IScopedServiceProvider _serviceProvider; - public ResourceDefinitionProvider(IContextEntityProvider resourceContextProvider, IScopedServiceProvider serviceProvider) + public ResourceDefinitionProvider(IResourceGraph resourceContextProvider, IScopedServiceProvider serviceProvider) { _resourceContextProvider = resourceContextProvider; _serviceProvider = serviceProvider; diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 97dbc35eb8..c4cec73a15 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -21,7 +21,7 @@ namespace DiscoveryTests public class ServiceDiscoveryFacadeTests { private readonly IServiceCollection _services = new ServiceCollection(); - private readonly ResourceGraphBuilder _graphBuilder = new ResourceGraphBuilder(); + private readonly ResourceGraphBuilder _resourceGraphBuilder = new ResourceGraphBuilder(); public ServiceDiscoveryFacadeTests() { @@ -31,7 +31,7 @@ public ServiceDiscoveryFacadeTests() TestModelRepository._dbContextResolver = dbResolverMock.Object; } - private ServiceDiscoveryFacade _facade => new ServiceDiscoveryFacade(_services, _graphBuilder); + private ServiceDiscoveryFacade _facade => new ServiceDiscoveryFacade(_services, _resourceGraphBuilder); [Fact] public void AddAssembly_Adds_All_Resources_To_Graph() @@ -40,10 +40,10 @@ public void AddAssembly_Adds_All_Resources_To_Graph() _facade.AddAssembly(typeof(Person).Assembly); // assert - var graph = _graphBuilder.Build(); - var personResource = graph.GetContextEntity(typeof(Person)); - var articleResource = graph.GetContextEntity(typeof(Article)); - var modelResource = graph.GetContextEntity(typeof(Model)); + var resourceGraph = _resourceGraphBuilder.Build(); + var personResource = resourceGraph.GetContextEntity(typeof(Person)); + var articleResource = resourceGraph.GetContextEntity(typeof(Article)); + var modelResource = resourceGraph.GetContextEntity(typeof(Model)); Assert.NotNull(personResource); Assert.NotNull(articleResource); @@ -57,8 +57,8 @@ public void AddCurrentAssembly_Adds_Resources_To_Graph() _facade.AddCurrentAssembly(); // assert - var graph = _graphBuilder.Build(); - var testModelResource = graph.GetContextEntity(typeof(TestModel)); + var resourceGraph = _resourceGraphBuilder.Build(); + var testModelResource = resourceGraph.GetContextEntity(typeof(TestModel)); Assert.NotNull(testModelResource); } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs index 0c5161b00e..4fe635952d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs @@ -108,6 +108,71 @@ public async Task Can_Fetch_Many_To_Many_Through_GetById() Assert.Equal(tag.Name, tagResponse.Name); } + [Fact] + public async Task Can_Fetch_Many_To_Many_Through_GetById_Relationship_Link() + { + // arrange + var context = _fixture.GetService(); + var article = _articleFaker.Generate(); + var tag = _tagFaker.Generate(); + var articleTag = new ArticleTag + { + Article = article, + Tag = tag + }; + context.ArticleTags.Add(articleTag); + await context.SaveChangesAsync(); + + var route = $"/api/v1/articles/{article.Id}/tags"; + + // act + var response = await _fixture.Client.GetAsync(route); + + // assert + var body = await response.Content.ReadAsStringAsync(); + Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}"); + + var document = JsonConvert.DeserializeObject(body); + Assert.Null(document.Included); + + var tagResponse = _fixture.GetDeserializer().DeserializeList(body).Data.First(); + Assert.NotNull(tagResponse); + Assert.Equal(tag.Id, tagResponse.Id); + } + + + [Fact] + public async Task Can_Fetch_Many_To_Many_Through_Relationship_Link() + { + // arrange + var context = _fixture.GetService(); + var article = _articleFaker.Generate(); + var tag = _tagFaker.Generate(); + var articleTag = new ArticleTag + { + Article = article, + Tag = tag + }; + context.ArticleTags.Add(articleTag); + await context.SaveChangesAsync(); + + var route = $"/api/v1/articles/{article.Id}/relationships/tags"; + + // act + var response = await _fixture.Client.GetAsync(route); + + // assert + var body = await response.Content.ReadAsStringAsync(); + Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}"); + + var document = JsonConvert.DeserializeObject(body); + Assert.Null(document.Included); + + var tagResponse = _fixture.GetDeserializer().DeserializeList(body).Data.First(); + Assert.NotNull(tagResponse); + Assert.Equal(tag.Id, tagResponse.Id); + } + [Fact] public async Task Can_Fetch_Many_To_Many_Without_Include() { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index 3b6865e8c6..f2b4b68596 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -273,8 +273,8 @@ public async Task CreateResource_EntityTypeMismatch_IsConflict() // arrange var dbContext = PrepareTest(); var serializer = GetSerializer(e => new { }, e => new { e.Owner }); - var graph = new ResourceGraphBuilder().AddResource("todo-items").AddResource().AddResource().Build(); - var _deserializer = new ResponseDeserializer(graph); + var resourceGraph = new ResourceGraphBuilder().AddResource("todo-items").AddResource().AddResource().Build(); + var _deserializer = new ResponseDeserializer(resourceGraph); var content = serializer.Serialize(_todoItemFaker.Generate()).Replace("todo-items", "people"); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs index 57aec660cb..3490f6e949 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs @@ -38,8 +38,8 @@ public async Task Can_Include_Nested_Relationships() { // arrange const string route = "/api/v1/todo-items?include=collection.owner"; - var graph = new ResourceGraphBuilder().AddResource("todo-items").AddResource().AddResource().Build(); - var deserializer = new ResponseDeserializer(graph); + var resourceGraph = new ResourceGraphBuilder().AddResource("todo-items").AddResource().AddResource().Build(); + var deserializer = new ResponseDeserializer(resourceGraph); var todoItem = new TodoItem { Collection = new TodoItemCollection diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs index ea230a8261..67ec9b53ba 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs @@ -22,6 +22,7 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCoreExampleTests.Helpers.Models; using JsonApiDotNetCore.Services; +using JsonApiDotNetCore.Internal.Contracts; namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec { @@ -30,7 +31,7 @@ public class SparseFieldSetTests { private TestFixture _fixture; private readonly AppDbContext _dbContext; - private IFieldsExplorer _explorer; + private IResourceGraph _resourceGraph; private Faker _personFaker; private Faker _todoItemFaker; @@ -38,7 +39,7 @@ public SparseFieldSetTests(TestFixture fixture) { _fixture = fixture; _dbContext = fixture.GetService(); - _explorer = fixture.GetService(); + _resourceGraph = fixture.GetService(); _personFaker = new Faker() .RuleFor(p => p.FirstName, f => f.Name.FirstName()) .RuleFor(p => p.LastName, f => f.Name.LastName()) @@ -72,7 +73,7 @@ public async Task Can_Select_Sparse_Fieldsets() var query = _dbContext .TodoItems .Where(t => t.Id == todoItem.Id) - .Select(_explorer.GetAttributes(e => new { e.Id, e.Description, e.CreatedDate, e.AchievedDate } ).ToList()); + .Select(_resourceGraph.GetAttributes(e => new { e.Id, e.Description, e.CreatedDate, e.AchievedDate } ).ToList()); var resultSql = StringExtensions.Normalize(query.ToSql()); var result = await query.FirstAsync(); @@ -174,8 +175,8 @@ public async Task Fields_Query_Selects_All_Fieldset_With_HasOne() var route = $"/api/v1/todo-items?include=owner&fields[owner]=first-name,age"; var request = new HttpRequestMessage(httpMethod, route); - var graph = new ResourceGraphBuilder().AddResource().AddResource("todo-items").Build(); - var deserializer = new ResponseDeserializer(graph); + var resourceGraph = new ResourceGraphBuilder().AddResource().AddResource("todo-items").Build(); + var deserializer = new ResponseDeserializer(resourceGraph); // act var response = await client.SendAsync(request); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs index 2ba9fbcff5..74d9e670ef 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs @@ -92,7 +92,7 @@ public async Task Respond_404_If_EntityDoesNotExist() } [Fact] - public async Task Respond_400_If_IdNotInAttributeList() + public async Task Respond_422_If_IdNotInAttributeList() { // Arrange var maxPersonId = _context.TodoItems.LastOrDefault()?.Id ?? 0; diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs index e15ae242d8..a0def3595c 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs @@ -44,7 +44,7 @@ public IRequestSerializer GetSerializer(Expression() .AddResource
() .AddResource() @@ -55,7 +55,7 @@ public IResponseDeserializer GetDeserializer() .AddResource() .AddResource("todo-items") .AddResource().Build(); - return new ResponseDeserializer(graph); + return new ResponseDeserializer(resourceGraph); } public T GetService() => (T)_services.GetService(typeof(T)); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs index 8048738417..fb8920816b 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs @@ -33,7 +33,7 @@ public override IServiceProvider ConfigureServices(IServiceCollection services) options.AllowClientGeneratedIds = true; }, discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample))), - mvcBuilder); + mvcBuilder: mvcBuilder); return services.BuildServiceProvider(); diff --git a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs index bb2e2173fc..a2904bc8b7 100644 --- a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs +++ b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs @@ -28,13 +28,7 @@ public void Can_Build_ResourceGraph_Using_Builder() { // arrange var services = new ServiceCollection(); - services.AddJsonApi(opt => - { - opt.BuildResourceGraph(b => - { - b.AddResource("non-db-resources"); - }); - }); + services.AddJsonApi(resources: builder => builder.AddResource("non-db-resources")); // act var container = services.BuildServiceProvider(); @@ -56,10 +50,10 @@ public void Resources_Without_Names_Specified_Will_Use_Default_Formatter() builder.AddResource(); // act - var graph = builder.Build(); + var resourceGraph = builder.Build(); // assert - var resource = graph.GetContextEntity(typeof(TestResource)); + var resource = resourceGraph.GetContextEntity(typeof(TestResource)); Assert.Equal("test-resources", resource.EntityName); } @@ -71,10 +65,10 @@ public void Resources_Without_Names_Specified_Will_Use_Configured_Formatter() builder.AddResource(); // act - var graph = builder.Build(); + var resourceGraph = builder.Build(); // assert - var resource = graph.GetContextEntity(typeof(TestResource)); + var resource = resourceGraph.GetContextEntity(typeof(TestResource)); Assert.Equal("testResources", resource.EntityName); } @@ -86,10 +80,10 @@ public void Attrs_Without_Names_Specified_Will_Use_Default_Formatter() builder.AddResource(); // act - var graph = builder.Build(); + var resourceGraph = builder.Build(); // assert - var resource = graph.GetContextEntity(typeof(TestResource)); + var resource = resourceGraph.GetContextEntity(typeof(TestResource)); Assert.Contains(resource.Attributes, (i) => i.PublicAttributeName == "compound-attribute"); } @@ -101,10 +95,10 @@ public void Attrs_Without_Names_Specified_Will_Use_Configured_Formatter() builder.AddResource(); // act - var graph = builder.Build(); + var resourceGraph = builder.Build(); // assert - var resource = graph.GetContextEntity(typeof(TestResource)); + var resource = resourceGraph.GetContextEntity(typeof(TestResource)); Assert.Contains(resource.Attributes, (i) => i.PublicAttributeName == "compoundAttribute"); } @@ -116,10 +110,10 @@ public void Relationships_Without_Names_Specified_Will_Use_Default_Formatter() builder.AddResource(); // act - var graph = builder.Build(); + var resourceGraph = builder.Build(); // assert - var resource = graph.GetContextEntity(typeof(TestResource)); + var resource = resourceGraph.GetContextEntity(typeof(TestResource)); Assert.Equal("related-resource", resource.Relationships.Single(r => r.IsHasOne).PublicRelationshipName); Assert.Equal("related-resources", resource.Relationships.Single(r => r.IsHasMany).PublicRelationshipName); } diff --git a/test/UnitTests/Builders/LinkBuilderTests.cs b/test/UnitTests/Builders/LinkBuilderTests.cs index 4dc2ec44a8..661ea73ba7 100644 --- a/test/UnitTests/Builders/LinkBuilderTests.cs +++ b/test/UnitTests/Builders/LinkBuilderTests.cs @@ -16,7 +16,7 @@ namespace UnitTests public class LinkBuilderTests { private readonly IPageService _pageService; - private readonly Mock _provider = new Mock(); + private readonly Mock _provider = new Mock(); private const string _host = "http://www.example.com"; private const string _topSelf = "http://www.example.com/articles"; private const string _resourceSelf = "http://www.example.com/articles/123"; diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index 14bf06e3d5..f33ae0aa30 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -8,8 +8,7 @@ using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using JsonApiDotNetCore.Internal.Contracts; -using System.IO; + namespace UnitTests { @@ -19,22 +18,20 @@ public class Resource : Identifiable { [Attr("test-attribute")] public string TestAttribute { get; set; } } - private Mock _resourceGraph = new Mock(); - private Mock _resourceGraphMock = new Mock(); [Fact] public async Task GetAsync_Calls_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getAll: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, getAll: serviceMock.Object); // act await controller.GetAsync(); // assert serviceMock.Verify(m => m.GetAsync(), Times.Once); - + } [Fact] @@ -42,7 +39,7 @@ public async Task GetAsync_Throws_405_If_No_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, null); + var controller = new BaseJsonApiController(new Mock().Object, null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync()); @@ -57,14 +54,14 @@ public async Task GetAsyncById_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getById: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, getById: serviceMock.Object); // act await controller.GetAsync(id); // assert serviceMock.Verify(m => m.GetAsync(id), Times.Once); - + } [Fact] @@ -73,7 +70,7 @@ public async Task GetAsyncById_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getById: null); + var controller = new BaseJsonApiController(new Mock().Object, getById: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync(id)); @@ -88,7 +85,7 @@ public async Task GetRelationshipsAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, getRelationships: serviceMock.Object); // act await controller.GetRelationshipsAsync(id, string.Empty); @@ -103,7 +100,7 @@ public async Task GetRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, getRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipsAsync(id, string.Empty)); @@ -118,7 +115,7 @@ public async Task GetRelationshipAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationship: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, getRelationship: serviceMock.Object); // act await controller.GetRelationshipAsync(id, string.Empty); @@ -133,7 +130,7 @@ public async Task GetRelationshipAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationship: null); + var controller = new BaseJsonApiController(new Mock().Object, getRelationship: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipAsync(id, string.Empty)); @@ -149,10 +146,8 @@ public async Task PatchAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); - - var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new JsonApiOptions(), update: serviceMock.Object); // act await controller.PatchAsync(id, resource); @@ -168,9 +163,7 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateDisbled() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); - - var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new JsonApiOptions(), update: serviceMock.Object); // act var response = await controller.PatchAsync(id, resource); @@ -187,10 +180,8 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateEnabled() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); -// _resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); - var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, _resourceGraph.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, update: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -199,7 +190,7 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateEnabled() // assert serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny()), Times.Never); Assert.IsType(response); - Assert.IsType(((UnprocessableEntityObjectResult) response).Value); + Assert.IsType(((UnprocessableEntityObjectResult)response).Value); } [Fact] @@ -208,7 +199,7 @@ public async Task PatchAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, update: null); + var controller = new BaseJsonApiController(new Mock().Object, update: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchAsync(id, It.IsAny())); @@ -223,11 +214,10 @@ public async Task PostAsync_Calls_Service() // arrange var resource = new Resource(); var serviceMock = new Mock>(); -// _resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); - var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new JsonApiOptions(), create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); - controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext {HttpContext = new DefaultHttpContext()}; + controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; // act await controller.PostAsync(resource); @@ -242,7 +232,7 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, _resourceGraph.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, create: serviceMock.Object); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); @@ -261,9 +251,8 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateEnabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); - var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, _resourceGraph.Object, create: serviceMock.Object); - controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, create: serviceMock.Object); + controller.ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() }; controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); @@ -284,7 +273,7 @@ public async Task PatchRelationshipsAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, updateRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, updateRelationships: serviceMock.Object); // act await controller.PatchRelationshipsAsync(id, string.Empty, null); @@ -299,7 +288,7 @@ public async Task PatchRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, updateRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, updateRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchRelationshipsAsync(id, string.Empty, null)); @@ -315,7 +304,7 @@ public async Task DeleteAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, delete: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, delete: serviceMock.Object); // Act await controller.DeleteAsync(id); @@ -330,9 +319,9 @@ public async Task DeleteAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, - - _resourceGraph.Object, delete: null); + var controller = new BaseJsonApiController(new Mock().Object, + + delete: null); // act var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); diff --git a/test/UnitTests/Data/DefaultEntityRepository_Tests.cs b/test/UnitTests/Data/DefaultEntityRepository_Tests.cs index 4260618e26..6c0ce780c1 100644 --- a/test/UnitTests/Data/DefaultEntityRepository_Tests.cs +++ b/test/UnitTests/Data/DefaultEntityRepository_Tests.cs @@ -77,13 +77,13 @@ private DefaultEntityRepository GetRepository() .Setup(m => m.GetContext()) .Returns(_contextMock.Object); - var graph = new ResourceGraphBuilder().AddResource().Build(); + var resourceGraph = new ResourceGraphBuilder().AddResource().Build(); return new DefaultEntityRepository( _targetedFieldsMock.Object, _contextResolverMock.Object, - graph, null, null); + resourceGraph, null, null); } [Theory] diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index c0bfc9aee0..0f2ce10f96 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -29,13 +29,11 @@ public void AddJsonApiInternals_Adds_All_Required_Services() { // arrange var services = new ServiceCollection(); - var jsonApiOptions = new JsonApiOptions(); - services.AddSingleton(jsonApiOptions); services.AddDbContext(options => options.UseInMemoryDatabase("UnitTestDb"), ServiceLifetime.Transient); - services.AddScoped>(); + services.AddJsonApi(); + // act - services.AddJsonApiInternals(jsonApiOptions); // this is required because the DbContextResolver requires access to the current HttpContext // to get the request scoped DbContext instance services.AddScoped(); @@ -44,9 +42,9 @@ public void AddJsonApiInternals_Adds_All_Required_Services() // assert var currentRequest = provider.GetService(); Assert.NotNull(currentRequest); - var graph = provider.GetService(); - Assert.NotNull(graph); - currentRequest.SetRequestResource(graph.GetContextEntity()); + var resourceGraph = provider.GetService(); + Assert.NotNull(resourceGraph); + currentRequest.SetRequestResource(resourceGraph.GetContextEntity()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService(typeof(IEntityRepository))); @@ -130,8 +128,8 @@ public void AddJsonApi_With_Context_Uses_DbSet_PropertyName_If_NoOtherSpecified( // assert var provider = services.BuildServiceProvider(); - var graph = provider.GetService(); - var resource = graph.GetContextEntity(typeof(IntResource)); + var resourceGraph = provider.GetService(); + var resource = resourceGraph.GetContextEntity(typeof(IntResource)); Assert.Equal("resource", resource.EntityName); } diff --git a/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs b/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs index 122b2cd67b..d2f35dd454 100644 --- a/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs +++ b/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs @@ -19,7 +19,7 @@ public void AddDbContext_Does_Not_Throw_If_Context_Contains_Members_That_DoNot_I var resourceGraph = resourceGraphBuilder.Build() as ResourceGraph; // assert - Assert.Empty(resourceGraph.Entities); + Assert.Empty(resourceGraph.GetContextEntities()); } [Fact] diff --git a/test/UnitTests/Models/ResourceDefinitionTests.cs b/test/UnitTests/Models/ResourceDefinitionTests.cs index 78ec7ff73e..e863e55465 100644 --- a/test/UnitTests/Models/ResourceDefinitionTests.cs +++ b/test/UnitTests/Models/ResourceDefinitionTests.cs @@ -10,11 +10,11 @@ namespace UnitTests.Models { public class ResourceDefinition_Scenario_Tests { - private readonly IResourceGraph _graph; + private readonly IResourceGraph _resourceGraph; public ResourceDefinition_Scenario_Tests() { - _graph = new ResourceGraphBuilder() + _resourceGraph = new ResourceGraphBuilder() .AddResource("models") .Build(); } @@ -58,7 +58,7 @@ public class RequestFilteredResource : ResourceDefinition { // this constructor will be resolved from the container // that means you can take on any dependency that is also defined in the container - public RequestFilteredResource(bool isAdmin) : base(new FieldsExplorer(new ResourceGraphBuilder().AddResource().Build()), new ResourceGraphBuilder().AddResource().Build()) + public RequestFilteredResource(bool isAdmin) : base(new ResourceGraphBuilder().AddResource().Build()) { if (isAdmin) HideFields(m => m.AlwaysExcluded); diff --git a/test/UnitTests/QueryParameters/FilterServiceTests.cs b/test/UnitTests/QueryParameters/FilterServiceTests.cs index ab3b5d13ae..7e0e4d4462 100644 --- a/test/UnitTests/QueryParameters/FilterServiceTests.cs +++ b/test/UnitTests/QueryParameters/FilterServiceTests.cs @@ -11,7 +11,7 @@ public class FilterServiceTests : QueryParametersUnitTestCollection { public FilterService GetService() { - return new FilterService(MockResourceDefinitionProvider(), _graph, MockCurrentRequest(_articleResourceContext)); + return new FilterService(MockResourceDefinitionProvider(), _resourceGraph, MockCurrentRequest(_articleResourceContext)); } [Fact] diff --git a/test/UnitTests/QueryParameters/IncludeServiceTests.cs b/test/UnitTests/QueryParameters/IncludeServiceTests.cs index 8ed7b0c034..439dc98288 100644 --- a/test/UnitTests/QueryParameters/IncludeServiceTests.cs +++ b/test/UnitTests/QueryParameters/IncludeServiceTests.cs @@ -14,7 +14,7 @@ public class IncludeServiceTests : QueryParametersUnitTestCollection public IncludeService GetService(ContextEntity resourceContext = null) { - return new IncludeService(_graph, MockCurrentRequest(resourceContext ?? _articleResourceContext)); + return new IncludeService(_resourceGraph, MockCurrentRequest(resourceContext ?? _articleResourceContext)); } [Fact] @@ -58,7 +58,7 @@ public void Parse_ChainsOnWrongMainResource_ThrowsJsonApiException() // arrange const string chain = "author.blogs.reviewer.favorite-food,reviewer.blogs.author.favorite-song"; var query = new KeyValuePair("include", new StringValues(chain)); - var service = GetService(_graph.GetContextEntity()); + var service = GetService(_resourceGraph.GetContextEntity()); // act, assert var exception = Assert.Throws( () => service.Parse(query)); diff --git a/test/UnitTests/QueryParameters/QueryParametersUnitTestCollection.cs b/test/UnitTests/QueryParameters/QueryParametersUnitTestCollection.cs index 9cc6d80fc4..43bcf2e28f 100644 --- a/test/UnitTests/QueryParameters/QueryParametersUnitTestCollection.cs +++ b/test/UnitTests/QueryParameters/QueryParametersUnitTestCollection.cs @@ -13,7 +13,7 @@ namespace UnitTests.QueryParameters public class QueryParametersUnitTestCollection { protected readonly ContextEntity _articleResourceContext; - protected readonly IResourceGraph _graph; + protected readonly IResourceGraph _resourceGraph; public QueryParametersUnitTestCollection() { @@ -23,8 +23,8 @@ public QueryParametersUnitTestCollection() builder.AddResource(); builder.AddResource(); builder.AddResource(); - _graph = builder.Build(); - _articleResourceContext = _graph.GetContextEntity
(); + _resourceGraph = builder.Build(); + _articleResourceContext = _resourceGraph.GetContextEntity
(); } public ICurrentRequest MockCurrentRequest(ContextEntity requestResource = null) diff --git a/test/UnitTests/QueryParameters/SortServiceTests.cs b/test/UnitTests/QueryParameters/SortServiceTests.cs index 1ca38d192e..278812fa5e 100644 --- a/test/UnitTests/QueryParameters/SortServiceTests.cs +++ b/test/UnitTests/QueryParameters/SortServiceTests.cs @@ -10,7 +10,7 @@ public class SortServiceTests : QueryParametersUnitTestCollection { public SortService GetService() { - return new SortService(MockResourceDefinitionProvider(), _graph, MockCurrentRequest(_articleResourceContext)); + return new SortService(MockResourceDefinitionProvider(), _resourceGraph, MockCurrentRequest(_articleResourceContext)); } [Fact] diff --git a/test/UnitTests/QueryParameters/SparseFieldsServiceTests.cs b/test/UnitTests/QueryParameters/SparseFieldsServiceTests.cs index 03f855d901..a4d1994e6f 100644 --- a/test/UnitTests/QueryParameters/SparseFieldsServiceTests.cs +++ b/test/UnitTests/QueryParameters/SparseFieldsServiceTests.cs @@ -12,7 +12,7 @@ public class SparseFieldsServiceTests : QueryParametersUnitTestCollection { public SparseFieldsService GetService(ContextEntity contextEntity = null) { - return new SparseFieldsService(_graph, MockCurrentRequest(contextEntity ?? _articleResourceContext)); + return new SparseFieldsService(_resourceGraph, MockCurrentRequest(contextEntity ?? _articleResourceContext)); } [Fact] diff --git a/test/UnitTests/ResourceHooks/DiscoveryTests.cs b/test/UnitTests/ResourceHooks/DiscoveryTests.cs index fe6d798c37..b48adb1af6 100644 --- a/test/UnitTests/ResourceHooks/DiscoveryTests.cs +++ b/test/UnitTests/ResourceHooks/DiscoveryTests.cs @@ -33,7 +33,7 @@ public void Hook_Discovery() public class AnotherDummy : Identifiable { } public abstract class ResourceDefintionBase : ResourceDefinition where T : class, IIdentifiable { - protected ResourceDefintionBase(IResourceGraph graph) : base(graph) { } + protected ResourceDefintionBase(IResourceGraph resourceGraph) : base(resourceGraph) { } public override IEnumerable BeforeDelete(IEntityHashSet affected, ResourcePipeline pipeline) { return affected; } public override void AfterDelete(HashSet entities, ResourcePipeline pipeline, bool succeeded) { } diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs index a778e9ac3a..f697411b19 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs @@ -82,7 +82,7 @@ public void BeforeUpdate_Deleting_Relationship() var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var (_, ufMock, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); - ufMock.Setup(c => c.Relationships).Returns(_fieldExplorer.GetRelationships((TodoItem t) => t.ToOnePerson)); + ufMock.Setup(c => c.Relationships).Returns(_resourceGraph.GetRelationships((TodoItem t) => t.ToOnePerson)); // act var _todoList = new List() { new TodoItem { Id = this.todoList[0].Id } }; diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index 8fe8cfe346..0e5d84b08e 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -24,8 +24,7 @@ namespace UnitTests.ResourceHooks { public class HooksDummyData { - protected IFieldsExplorer _fieldExplorer; - protected IResourceGraph _graph; + protected IResourceGraph _resourceGraph; protected ResourceHook[] NoHooks = new ResourceHook[0]; protected ResourceHook[] EnableDbValues = { ResourceHook.BeforeUpdate, ResourceHook.BeforeUpdateRelationship }; protected ResourceHook[] DisableDbValues = new ResourceHook[0]; @@ -38,7 +37,7 @@ public class HooksDummyData protected readonly Faker _passportFaker; public HooksDummyData() { - _graph = new ResourceGraphBuilder() + _resourceGraph = new ResourceGraphBuilder() .AddResource() .AddResource() .AddResource() @@ -48,7 +47,7 @@ public HooksDummyData() .AddResource() .Build(); - _fieldExplorer = new FieldsExplorer(_graph); + _todoFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); _personFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); @@ -142,14 +141,13 @@ protected List CreateTodoWithOwner() public class HooksTestsSetup : HooksDummyData { - (IResourceGraph, Mock, Mock, Mock, IJsonApiOptions) CreateMocks() + (Mock, Mock, Mock, IJsonApiOptions) CreateMocks() { var pfMock = new Mock(); - var graph = _graph; var ufMock = new Mock(); var iqsMock = new Mock(); var optionsMock = new JsonApiOptions { LoaDatabaseValues = false }; - return (graph, ufMock, iqsMock, pfMock, optionsMock); + return (ufMock, iqsMock, pfMock, optionsMock); } internal (Mock, ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery mainDiscovery = null) @@ -159,13 +157,13 @@ public class HooksTestsSetup : HooksDummyData var mainResource = CreateResourceDefinition(mainDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (graph, ufMock, iqMock, gpfMock, options) = CreateMocks(); + var (ufMock, iqMock, gpfMock, options) = CreateMocks(); SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, null); - var execHelper = new HookExecutorHelper(gpfMock.Object, graph, options); - var traversalHelper = new TraversalHelper(graph, ufMock.Object); - var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, graph); + var execHelper = new HookExecutorHelper(gpfMock.Object, options); + var traversalHelper = new TraversalHelper(_resourceGraph, ufMock.Object); + var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, _resourceGraph); return (iqMock, hookExecutor, mainResource); } @@ -184,16 +182,16 @@ public class HooksTestsSetup : HooksDummyData var nestedResource = CreateResourceDefinition(nestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (graph, ufMock, iqMock, gpfMock, options) = CreateMocks(); + var (ufMock, iqMock, gpfMock, options) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, nestedResource.Object, nestedDiscovery, dbContext); - var execHelper = new HookExecutorHelper(gpfMock.Object, graph, options); - var traversalHelper = new TraversalHelper(graph, ufMock.Object); - var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, graph); + var execHelper = new HookExecutorHelper(gpfMock.Object, options); + var traversalHelper = new TraversalHelper(_resourceGraph, ufMock.Object); + var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, _resourceGraph); return (iqMock, ufMock, hookExecutor, mainResource, nestedResource); } @@ -215,7 +213,7 @@ public class HooksTestsSetup : HooksDummyData var secondNestedResource = CreateResourceDefinition(secondNestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (graph, ufMock, iqMock, gpfMock, options) = CreateMocks(); + var (ufMock, iqMock, gpfMock, options) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; @@ -223,9 +221,9 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, firstNestedResource.Object, firstNestedDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, secondNestedResource.Object, secondNestedDiscovery, dbContext); - var execHelper = new HookExecutorHelper(gpfMock.Object, graph, options); - var traversalHelper = new TraversalHelper(graph, ufMock.Object); - var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, graph); + var execHelper = new HookExecutorHelper(gpfMock.Object, options); + var traversalHelper = new TraversalHelper(_resourceGraph, ufMock.Object); + var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, _resourceGraph); return (iqMock, hookExecutor, mainResource, firstNestedResource, secondNestedResource); } @@ -359,7 +357,7 @@ IDbContextResolver CreateTestDbResolver(AppDbContext dbContext) where TM void ResolveInverseRelationships(AppDbContext context) { - new InverseRelationships(ResourceGraph.Instance, new DbContextResolver(context)).Resolve(); + new InverseRelationships(_resourceGraph, new DbContextResolver(context)).Resolve(); } Mock> CreateResourceDefinition @@ -385,13 +383,13 @@ protected List> GetIncludedRelationshipsChains(param protected List GetIncludedRelationshipsChain(string chain) { var parsedChain = new List(); - var resourceContext = _graph.GetContextEntity(); + var resourceContext = _resourceGraph.GetContextEntity(); var splittedPath = chain.Split(QueryConstants.DOT); foreach (var requestedRelationship in splittedPath) { var relationship = resourceContext.Relationships.Single(r => r.PublicRelationshipName == requestedRelationship); parsedChain.Add(relationship); - resourceContext = _graph.GetContextEntity(relationship.DependentType); + resourceContext = _resourceGraph.GetContextEntity(relationship.DependentType); } return parsedChain; } diff --git a/test/UnitTests/Serialization/Client/RequestSerializerTests.cs b/test/UnitTests/Serialization/Client/RequestSerializerTests.cs index bbb32cc04c..5c405b09ef 100644 --- a/test/UnitTests/Serialization/Client/RequestSerializerTests.cs +++ b/test/UnitTests/Serialization/Client/RequestSerializerTests.cs @@ -16,8 +16,8 @@ public class RequestSerializerTests : SerializerTestsSetup public RequestSerializerTests() { - var builder = new ResourceObjectBuilder(_resourceGraph, _resourceGraph, new ResourceObjectBuilderSettings()); - _serializer = new RequestSerializer(_fieldExplorer, _resourceGraph, builder); + var builder = new ResourceObjectBuilder(_resourceGraph, new ResourceObjectBuilderSettings()); + _serializer = new RequestSerializer(_resourceGraph, builder); } [Fact] diff --git a/test/UnitTests/Serialization/Common/ResourceObjectBuilderTests.cs b/test/UnitTests/Serialization/Common/ResourceObjectBuilderTests.cs index 708cf5055d..06df8ab1e2 100644 --- a/test/UnitTests/Serialization/Common/ResourceObjectBuilderTests.cs +++ b/test/UnitTests/Serialization/Common/ResourceObjectBuilderTests.cs @@ -16,7 +16,7 @@ public class ResourceObjectBuilderTests : SerializerTestsSetup public ResourceObjectBuilderTests() { - _builder = new ResourceObjectBuilder(_resourceGraph, _resourceGraph, new ResourceObjectBuilderSettings()); + _builder = new ResourceObjectBuilder(_resourceGraph, new ResourceObjectBuilderSettings()); } [Fact] @@ -58,7 +58,7 @@ public void EntityToResourceObject_ResourceWithIncludedAttrs_CanBuild(string str { // arrange var entity = new TestResource() { StringField = stringFieldValue, NullableIntField = intFieldValue }; - var attrs = _fieldExplorer.GetAttributes(tr => new { tr.StringField, tr.NullableIntField }); + var attrs = _resourceGraph.GetAttributes(tr => new { tr.StringField, tr.NullableIntField }); // act var resourceObject = _builder.Build(entity, attrs); @@ -114,7 +114,7 @@ public void EntityWithRelationshipsToResourceObject_WithIncludedRelationshipsAtt PopulatedToOne = new OneToOneDependent { Id = 10 }, PopulatedToManies = new List { new OneToManyDependent { Id = 20 } } }; - var relationships = _fieldExplorer.GetRelationships(tr => new { tr.PopulatedToManies, tr.PopulatedToOne, tr.EmptyToOne, tr.EmptyToManies }); + var relationships = _resourceGraph.GetRelationships(tr => new { tr.PopulatedToManies, tr.PopulatedToOne, tr.EmptyToOne, tr.EmptyToManies }); // act var resourceObject = _builder.Build(entity, relationships: relationships); @@ -138,7 +138,7 @@ public void EntityWithRelationshipsToResourceObject_DeviatingForeignKeyWhileRela { // arrange var entity = new OneToOneDependent { Principal = new OneToOnePrincipal { Id = 10 }, PrincipalId = 123 }; - var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + var relationships = _resourceGraph.GetRelationships(tr => tr.Principal); // act var resourceObject = _builder.Build(entity, relationships: relationships); @@ -155,7 +155,7 @@ public void EntityWithRelationshipsToResourceObject_DeviatingForeignKeyAndNoNavi { // arrange var entity = new OneToOneDependent { Principal = null, PrincipalId = 123 }; - var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + var relationships = _resourceGraph.GetRelationships(tr => tr.Principal); // act var resourceObject = _builder.Build(entity, relationships: relationships); @@ -169,7 +169,7 @@ public void EntityWithRequiredRelationshipsToResourceObject_DeviatingForeignKeyW { // arrange var entity = new OneToOneRequiredDependent { Principal = new OneToOnePrincipal { Id = 10 }, PrincipalId = 123 }; - var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + var relationships = _resourceGraph.GetRelationships(tr => tr.Principal); // act var resourceObject = _builder.Build(entity, relationships: relationships); @@ -186,7 +186,7 @@ public void EntityWithRequiredRelationshipsToResourceObject_DeviatingForeignKeyA { // arrange var entity = new OneToOneRequiredDependent { Principal = null, PrincipalId = 123 }; - var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + var relationships = _resourceGraph.GetRelationships(tr => tr.Principal); // act & assert Assert.ThrowsAny(() => _builder.Build(entity, relationships: relationships)); @@ -197,7 +197,7 @@ public void EntityWithRequiredRelationshipsToResourceObject_EmptyResourceWhileRe { // arrange var entity = new OneToOneRequiredDependent(); - var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + var relationships = _resourceGraph.GetRelationships(tr => tr.Principal); // act & assert Assert.ThrowsAny(() => _builder.Build(entity, relationships: relationships)); diff --git a/test/UnitTests/Serialization/SerializationTestsSetupBase.cs b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs index a286781ffb..c56e4a532e 100644 --- a/test/UnitTests/Serialization/SerializationTestsSetupBase.cs +++ b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs @@ -9,7 +9,6 @@ namespace UnitTests.Serialization public class SerializationTestsSetupBase { protected IResourceGraph _resourceGraph; - protected IContextEntityProvider _provider; protected readonly Faker _foodFaker; protected readonly Faker _songFaker; protected readonly Faker
_articleFaker; diff --git a/test/UnitTests/Serialization/SerializerTestsSetup.cs b/test/UnitTests/Serialization/SerializerTestsSetup.cs index 54802bd5cb..d359d46a2c 100644 --- a/test/UnitTests/Serialization/SerializerTestsSetup.cs +++ b/test/UnitTests/Serialization/SerializerTestsSetup.cs @@ -18,13 +18,11 @@ namespace UnitTests.Serialization { public class SerializerTestsSetup : SerializationTestsSetupBase { - protected readonly IFieldsExplorer _fieldExplorer; protected readonly TopLevelLinks _dummyToplevelLinks; protected readonly ResourceLinks _dummyResourceLinks; protected readonly RelationshipLinks _dummyRelationshipLinks; public SerializerTestsSetup() { - _fieldExplorer = new FieldsExplorer(_resourceGraph); _dummyToplevelLinks = new TopLevelLinks { Self = "http://www.dummy.com/dummy-self-link", @@ -52,7 +50,7 @@ protected ResponseSerializer GetResponseSerializer(List(meta, link, includedBuilder, fieldsToSerialize, resourceObjectBuilder, provider); } @@ -61,12 +59,12 @@ protected ResponseResourceObjectBuilder GetResponseResourceObjectBuilder(List
  • (e => new { e.Dependents }); + _relationshipsForBuild = _resourceGraph.GetRelationships(e => new { e.Dependents }); } [Fact] diff --git a/test/UnitTests/Serialization/Server/ResponseSerializerTests.cs b/test/UnitTests/Serialization/Server/ResponseSerializerTests.cs index 7218c5ff50..bd806ab61b 100644 --- a/test/UnitTests/Serialization/Server/ResponseSerializerTests.cs +++ b/test/UnitTests/Serialization/Server/ResponseSerializerTests.cs @@ -89,7 +89,7 @@ public void SerializeSingle_ResourceWithIncludedRelationships_CanSerialize() PopulatedToOne = new OneToOneDependent { Id = 10 }, PopulatedToManies = new List { new OneToManyDependent { Id = 20 } } }; - var chain = _fieldExplorer.GetRelationships().Select(r => new List { r }).ToList(); + var chain = _resourceGraph.GetRelationships().Select(r => new List { r }).ToList(); var serializer = GetResponseSerializer(inclusionChains: chain); // act @@ -152,13 +152,13 @@ public void SerializeSingle_ResourceWithDeeplyIncludedRelationships_CanSerialize PopulatedToManies = new List { includedEntity } }; - var chains = _fieldExplorer.GetRelationships() + var chains = _resourceGraph.GetRelationships() .Select(r => { var chain = new List { r }; if (r.PublicRelationshipName != "populated-to-manies") return new List { r }; - chain.AddRange(_fieldExplorer.GetRelationships()); + chain.AddRange(_resourceGraph.GetRelationships()); return chain; }).ToList(); @@ -365,7 +365,7 @@ public void SerializeSingleWithRequestRelationship_NullToOneRelationship_CanSeri // arrange var entity = new OneToOnePrincipal() { Id = 2, Dependent = null }; var serializer = GetResponseSerializer(); - var requestRelationship = _fieldExplorer.GetRelationships((OneToOnePrincipal t) => t.Dependent).First(); + var requestRelationship = _resourceGraph.GetRelationships((OneToOnePrincipal t) => t.Dependent).First(); serializer.RequestRelationship = requestRelationship; // act @@ -383,7 +383,7 @@ public void SerializeSingleWithRequestRelationship_PopulatedToOneRelationship_Ca // arrange var entity = new OneToOnePrincipal() { Id = 2, Dependent = new OneToOneDependent { Id = 1 } }; var serializer = GetResponseSerializer(); - var requestRelationship = _fieldExplorer.GetRelationships((OneToOnePrincipal t) => t.Dependent).First(); + var requestRelationship = _resourceGraph.GetRelationships((OneToOnePrincipal t) => t.Dependent).First(); serializer.RequestRelationship = requestRelationship; @@ -410,7 +410,7 @@ public void SerializeSingleWithRequestRelationship_EmptyToManyRelationship_CanSe // arrange var entity = new OneToManyPrincipal() { Id = 2, Dependents = new List() }; var serializer = GetResponseSerializer(); - var requestRelationship = _fieldExplorer.GetRelationships((OneToManyPrincipal t) => t.Dependents).First(); + var requestRelationship = _resourceGraph.GetRelationships((OneToManyPrincipal t) => t.Dependents).First(); serializer.RequestRelationship = requestRelationship; @@ -427,9 +427,9 @@ public void SerializeSingleWithRequestRelationship_EmptyToManyRelationship_CanSe public void SerializeSingleWithRequestRelationship_PopulatedToManyRelationship_CanSerialize() { // arrange - var entity = new OneToManyPrincipal() { Id = 2, Dependents = new List { new OneToManyDependent { Id = 1 } } }; + var entity = new OneToManyPrincipal() { Id = 2, Dependents = new List { new OneToManyDependent { Id = 1 } } }; var serializer = GetResponseSerializer(); - var requestRelationship = _fieldExplorer.GetRelationships((OneToManyPrincipal t) => t.Dependents).First(); + var requestRelationship = _resourceGraph.GetRelationships((OneToManyPrincipal t) => t.Dependents).First(); serializer.RequestRelationship = requestRelationship;