From 1f8a3f8fb9dc4e07bd7d4e17a3fc8e01ddc50111 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 3 May 2019 17:31:39 +0200 Subject: [PATCH 01/10] feat: started work on decoupling --- JsonApiDotnetCore.sln | 29 ++++++--- .../Properties/launchSettings.json | 27 ++++++++ .../Services/CustomArticleService.cs | 32 ++++++++++ .../Builders/IDocumentBuilder.cs | 2 +- .../Configuration/IJsonApiOptions.cs | 19 ++++++ .../Services/EntityResourceService.cs | 22 ++++--- .../Services/IJsonApiContext.cs | 2 +- test/UnitTests/JsonApiContext/BasicTest.cs | 61 +++++++++++++++++++ 8 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 src/Examples/GettingStarted/Properties/launchSettings.json create mode 100644 src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs create mode 100644 src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs create mode 100644 test/UnitTests/JsonApiContext/BasicTest.cs diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index bb07f87439..06d0d25651 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -1,6 +1,7 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28606.126 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{C0EC9E70-EB2E-436F-9D94-FA16FA774123}" EndProject @@ -41,13 +42,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExample", "src\Ex EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -191,6 +192,18 @@ Global {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.Build.0 = Release|Any CPU {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.ActiveCfg = Release|Any CPU {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.Build.0 = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -203,8 +216,6 @@ Global {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.Build.0 = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.ActiveCfg = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.Build.0 = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Examples/GettingStarted/Properties/launchSettings.json b/src/Examples/GettingStarted/Properties/launchSettings.json new file mode 100644 index 0000000000..a6cbc3bd6b --- /dev/null +++ b/src/Examples/GettingStarted/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49299/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "GettingStarted": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:49300/" + } + } +} \ No newline at end of file diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs new file mode 100644 index 0000000000..3b0c581709 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -0,0 +1,32 @@ + +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExample.Models; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace JsonApiDotNetCoreExample.Services +{ + public class CustomArticleService : EntityResourceService
+ { + public CustomArticleService( + IJsonApiContext jsonApiContext, + IEntityRepository
repository, + IJsonApiOptions jsonApiOptions, + ILoggerFactory loggerFactory + ) : base(jsonApiContext, repository, jsonApiOptions, loggerFactory) + { } + + public override async Task
GetAsync(int id) + { + var newEntity = await base.GetAsync(id); + newEntity.Name = "None for you Glen Coco"; + return newEntity; + } + } + +} diff --git a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs index 45ba096447..db20954730 100644 --- a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Builders public interface IDocumentBuilder { /// - /// Builds a json:api document from the provided resource instance. + /// Builds a Json:Api document from the provided resource instance. /// /// The resource to convert. Document Build(IIdentifiable entity); diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs new file mode 100644 index 0000000000..1807afe67d --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Configuration +{ + public interface IJsonApiOptions + { + /// + /// Whether or not the total-record count should be included in all document + /// level meta objects. + /// Defaults to false. + /// + /// + /// options.IncludeTotalRecordCount = true; + /// + bool IncludeTotalRecordCount { get; set; } + } +} diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 815c34157b..ca12ae3a3a 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; @@ -16,6 +17,7 @@ public class EntityResourceService : EntityResourceService entityRepository, + IJsonApiOptions optionsFetcher, ILoggerFactory loggerFactory = null) : base(jsonApiContext, entityRepository, loggerFactory) { } @@ -28,8 +30,9 @@ public class EntityResourceService : EntityResourceService entityRepository, - ILoggerFactory loggerFactory = null) : - base(jsonApiContext, entityRepository, loggerFactory) + IJsonApiOptions apiOptions, + ILoggerFactory loggerFactory = null) + : base(jsonApiContext, entityRepository, apiOptions, loggerFactory) { } } @@ -39,6 +42,7 @@ public class EntityResourceService : where TEntity : class, IIdentifiable { private readonly IJsonApiContext _jsonApiContext; + private readonly IJsonApiOptions _options; private readonly IEntityRepository _entities; private readonly ILogger _logger; private readonly IResourceMapper _mapper; @@ -46,26 +50,25 @@ public class EntityResourceService : public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, - ILoggerFactory loggerFactory = null) + IJsonApiOptions apiOptions, + ILoggerFactory loggerFactory = null) : this(jsonApiContext,entityRepository,apiOptions,loggerFactory, null) { // no mapper provided, TResource & TEntity must be the same type if (typeof(TResource) != typeof(TEntity)) { throw new InvalidOperationException("Resource and Entity types are NOT the same. Please provide a mapper."); } - - _jsonApiContext = jsonApiContext; - _entities = entityRepository; - _logger = loggerFactory?.CreateLogger>(); } public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, + IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceMapper mapper) { _jsonApiContext = jsonApiContext; + _options = options; _entities = entityRepository; _logger = loggerFactory.CreateLogger>(); _mapper = mapper; @@ -82,7 +85,7 @@ public virtual async Task CreateAsync(TResource resource) // https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343 if (ShouldIncludeRelationships()) { - if(_entities is IEntityFrameworkRepository efRepository) + if (_entities is IEntityFrameworkRepository efRepository) efRepository.DetachRelationshipPointers(entity); return await GetWithRelationshipsAsync(entity.Id); @@ -105,7 +108,8 @@ public virtual async Task> GetAsync() if (ShouldIncludeRelationships()) entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); - if (_jsonApiContext.Options.IncludeTotalRecordCount) + + if (_options.IncludeTotalRecordCount) _jsonApiContext.PageManager.TotalRecords = await _entities.CountAsync(entities); entities = _entities.Select(entities, _jsonApiContext.QuerySet?.Fields); diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 2509d7124f..5806fae78b 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -58,7 +58,7 @@ public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRe /// relationship pointers to persist the relationship. /// /// The expected use case is POST-ing or PATCH-ing an entity with HasMany - /// relaitonships: + /// relationships: /// /// { /// "data": { diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs new file mode 100644 index 0000000000..0d4848e4ad --- /dev/null +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -0,0 +1,61 @@ +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExample.Controllers; +using JsonApiDotNetCoreExample.Models; +using Moq; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Microsoft.AspNetCore.Mvc; +using JsonApiDotNetCoreExample.Services; +using JsonApiDotNetCore.Data; +using Microsoft.Extensions.Logging; +using JsonApiDotNetCore.Configuration; + +namespace UnitTests.JsonApiContext +{ + public class BasicTest + { + [Fact] + public async Task CanTestController() + { + // Arrange + var jsonApiContext = new Mock(); + var serviceMock = new Mock>(); + var controller = new ArticlesController(jsonApiContext.Object,serviceMock.Object); + + // Act + var result = await controller.GetAsync(); + + // Assert + var okResult = Assert.IsType(result); + var value = okResult.Value as IEnumerable
; + + Assert.NotNull(value); + } + + [Fact] + public async Task CanTestService() + { + // Arrange + var jacMock = FetchContextMock(); + var loggerMock = new Mock(); + var jsonApiOptionsMock = new Mock(); + var repositoryMock = new Mock>(); + + var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptionsMock.Object, loggerMock.Object); + // Act + var result = await service.GetAsync(); + + // Assert + Assert.NotNull(result); + } + + public Mock FetchContextMock() + { + return new Mock(); + } + + } +} From 88235e8528bdad926097dd803310c236e6ff5a78 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 3 May 2019 17:33:53 +0200 Subject: [PATCH 02/10] feat: add total record count --- src/JsonApiDotNetCore/Services/EntityResourceService.cs | 7 +++++-- test/UnitTests/JsonApiContext/BasicTest.cs | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index ca12ae3a3a..8aa684e306 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -51,7 +51,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - ILoggerFactory loggerFactory = null) : this(jsonApiContext,entityRepository,apiOptions,loggerFactory, null) + ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, loggerFactory, null) { // no mapper provided, TResource & TEntity must be the same type if (typeof(TResource) != typeof(TEntity)) @@ -106,11 +106,14 @@ public virtual async Task> GetAsync() entities = ApplySortAndFilterQuery(entities); if (ShouldIncludeRelationships()) + { entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); - + } if (_options.IncludeTotalRecordCount) + { _jsonApiContext.PageManager.TotalRecords = await _entities.CountAsync(entities); + } entities = _entities.Select(entities, _jsonApiContext.QuerySet?.Fields); diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index 0d4848e4ad..cbe2b15759 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -41,7 +41,10 @@ public async Task CanTestService() // Arrange var jacMock = FetchContextMock(); var loggerMock = new Mock(); - var jsonApiOptionsMock = new Mock(); + var jsonApiOptionsMock = new JsonApiOptions + { + IncludeTotalRecordCount = false + }; var repositoryMock = new Mock>(); var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptionsMock.Object, loggerMock.Object); From 1a94a4987fcfa779b8640706977802605801b541 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Mon, 13 May 2019 11:33:02 +0200 Subject: [PATCH 03/10] feat: green tests, new managers --- README.md | 7 +- .../Controllers/TodoCollectionsController.cs | 16 +- .../Services/CustomArticleService.cs | 5 +- .../JsonApiDotNetCoreExample/Startup.cs | 11 +- .../Configuration/IJsonApiOptions.cs | 13 ++ .../Configuration/JsonApiOptions.cs | 2 +- .../Data/DefaultEntityRepository.cs | 3 + .../IServiceCollectionExtensions.cs | 10 +- .../Graph/ServiceDiscoveryFacade.cs | 19 ++- src/JsonApiDotNetCore/Graph/TypeLocator.cs | 31 ++-- .../Internal/JsonApiException.cs | 6 + src/JsonApiDotNetCore/Internal/PageManager.cs | 3 +- .../Managers/Contracts/IPageManager.cs | 11 ++ .../Managers/Contracts/IQueryManager.cs | 20 +++ .../Managers/QueryManager.cs | 28 ++++ .../Services/EntityResourceService.cs | 150 ++++++++++++------ .../Services/IJsonApiContext.cs | 2 +- .../Services/JsonApiContext.cs | 4 +- src/JsonApiDotNetCore/Services/QueryParser.cs | 4 +- .../ServiceDiscoveryFacadeTests.cs | 9 +- .../NullValuedAttributeHandlingTests.cs | 2 +- .../HttpReadOnlyTests.cs | 10 +- .../Acceptance/Spec/CreatingDataTests.cs | 3 +- test/UnitTests/JsonApiContext/BasicTest.cs | 72 ++++++--- .../Services/EntityResourceService_Tests.cs | 2 +- 25 files changed, 322 insertions(+), 121 deletions(-) create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/QueryManager.cs diff --git a/README.md b/README.md index 5f7a929bb1..84c10f928c 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,7 @@ Running tests locally requires access to a postgresql database. If you have docker installed, this can be propped up via: ```bash -docker run --rm --name jsonapi-dotnet-core-testing \ - -e POSTGRES_DB=JsonApiDotNetCoreExample \ - -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ - -p 5432:5432 \ - postgres +docker run --rm --name jsonapi-dotnet-core-testing -e POSTGRES_DB=JsonApiDotNetCoreExample -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres ``` And then to run the tests: diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index 6bac2ff6a0..a6063c6cc3 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -13,18 +13,16 @@ namespace JsonApiDotNetCoreExample.Controllers { public class TodoCollectionsController : JsonApiController { - readonly IDbContextResolver _dbResolver; - public TodoCollectionsController( - IDbContextResolver contextResolver, - IJsonApiContext jsonApiContext, - IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + public TodoCollectionsController( + IDbContextResolver contextResolver, + IJsonApiContext jsonApiContext, + IResourceService resourceService, + ILoggerFactory loggerFactory) + : base(jsonApiContext, resourceService, loggerFactory) { _dbResolver = contextResolver; - } [HttpPatch("{id}")] @@ -40,4 +38,4 @@ public override async Task PatchAsync(Guid id, [FromBody] TodoIte } } -} \ No newline at end of file +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index 3b0c581709..5244f3d46d 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -1,6 +1,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -17,8 +18,10 @@ public CustomArticleService( IJsonApiContext jsonApiContext, IEntityRepository
repository, IJsonApiOptions jsonApiOptions, + IQueryManager queryManager, + IPageManager pageManager, ILoggerFactory loggerFactory - ) : base(jsonApiContext, repository, jsonApiOptions, loggerFactory) + ) : base(jsonApiContext, repository, jsonApiOptions, queryManager, pageManager, loggerFactory) { } public override async Task
GetAsync(int id) diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 68ea93a7fc..960cc2f0b1 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -7,6 +7,9 @@ using Microsoft.EntityFrameworkCore; using JsonApiDotNetCore.Extensions; using System; +using System.ComponentModel.Design; +using JsonApiDotNetCoreExample.Models; +using JsonApiDotNetCore.Services; namespace JsonApiDotNetCoreExample { @@ -43,7 +46,10 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) mvcBuilder, discovery => discovery.AddCurrentAssembly()); - return services.BuildServiceProvider(); + var serviceProvider = services.BuildServiceProvider(); + + + return serviceProvider; } public virtual void Configure( @@ -53,9 +59,8 @@ public virtual void Configure( AppDbContext context) { context.Database.EnsureCreated(); - loggerFactory.AddConsole(Config.GetSection("Logging")); - + var serviceProvider = app.ApplicationServices; app.UseJsonApi(); } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 1807afe67d..bb338b2d19 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Text; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models; +using Newtonsoft.Json; namespace JsonApiDotNetCore.Configuration { @@ -15,5 +18,15 @@ public interface IJsonApiOptions /// options.IncludeTotalRecordCount = true; /// bool IncludeTotalRecordCount { get; set; } + int DefaultPageSize { get; } + bool ValidateModelState { get; } + bool AllowClientGeneratedIds { get; } + JsonSerializerSettings SerializerSettings { get; } + bool EnableOperations { get; set; } + Link DefaultRelationshipLinks { get; set; } + NullAttributeResponseBehavior NullAttributeResponseBehavior { get; set; } + bool RelativeLinks { get; set; } + IResourceGraph ResourceGraph { get; set; } + bool AllowCustomQueryParameters { get; set; } } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index e8e7d83be8..738b499210 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -14,7 +14,7 @@ namespace JsonApiDotNetCore.Configuration /// /// Global options /// - public class JsonApiOptions + public class JsonApiOptions : IJsonApiOptions { /// /// Provides an interface for formatting resource names by convention diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 9922c7c84f..59813d7c3d 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -160,8 +160,11 @@ public virtual async Task CreateAsync(TEntity entity) AttachRelationships(entity); _dbSet.Add(entity); + + await _context.SaveChangesAsync(); + return entity; } diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index d1c1de352c..3268278d0e 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -10,6 +10,8 @@ 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; @@ -64,7 +66,7 @@ public static IServiceCollection AddJsonApi( { var config = new JsonApiOptions(); configureOptions(config); - + if(autoDiscover != null) { var facade = new ServiceDiscoveryFacade(services, config.ResourceGraphBuilder); @@ -110,7 +112,9 @@ public static void AddJsonApiInternals( } if (jsonApiOptions.EnableOperations) + { AddOperationServices(services); + } services.AddScoped(typeof(IEntityRepository<>), typeof(DefaultEntityRepository<>)); services.AddScoped(typeof(IEntityRepository<,>), typeof(DefaultEntityRepository<,>)); @@ -136,7 +140,8 @@ public static void AddJsonApiInternals( services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>)); services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); - services.AddSingleton(jsonApiOptions); + services.AddSingleton(jsonApiOptions); + services.AddTransient(); services.AddSingleton(jsonApiOptions.ResourceGraph); services.AddScoped(); services.AddSingleton(); @@ -156,6 +161,7 @@ public static void AddJsonApiInternals( services.AddScoped(); // services.AddScoped(); + services.AddScoped(); } private static void AddOperationServices(IServiceCollection services) diff --git a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs index 99c3845108..f2b1e1aebf 100644 --- a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs @@ -152,8 +152,11 @@ public ServiceDiscoveryFacade AddServices(Assembly assembly) private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor) { + foreach(var serviceInterface in ServiceInterfaces) RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); + + } /// @@ -174,16 +177,30 @@ private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescr foreach(var serviceInterface in RepositoryInterfaces) RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); } - + public int i = 0; private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) { + + + + if (resourceDescriptor.IdType == typeof(Guid) && interfaceType.GetTypeInfo().GenericTypeParameters.Length == 1) + { + return ; + } var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 ? new [] { resourceDescriptor.ResourceType, resourceDescriptor.IdType } : new [] { resourceDescriptor.ResourceType }; var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + if(service.implementation?.Name == "CustomArticleService" && genericArguments[0].Name != "Article") + { + + service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + } if (service.implementation != null) + { _services.AddScoped(service.registrationInterface, service.implementation); + } } } } diff --git a/src/JsonApiDotNetCore/Graph/TypeLocator.cs b/src/JsonApiDotNetCore/Graph/TypeLocator.cs index f96e17ffe0..b211ad7a5d 100644 --- a/src/JsonApiDotNetCore/Graph/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Graph/TypeLocator.cs @@ -14,7 +14,7 @@ internal static class TypeLocator private static Dictionary _typeCache = new Dictionary(); private static Dictionary> _identifiableTypeCache = new Dictionary>(); - + /// /// Determine whether or not this is a json:api resource by checking if it implements . /// Returns the status and the resultant id type, either `(true, Type)` OR `(false, null)` @@ -48,10 +48,10 @@ private static Type[] GetAssemblyTypes(Assembly assembly) /// /// Get all implementations of in the assembly /// - public static IEnumerable GetIdentifableTypes(Assembly assembly) + public static IEnumerable GetIdentifableTypes(Assembly assembly) => (_identifiableTypeCache.TryGetValue(assembly, out var descriptors) == false) ? FindIdentifableTypes(assembly) - : _identifiableTypeCache[assembly]; + : _identifiableTypeCache[assembly]; private static IEnumerable FindIdentifableTypes(Assembly assembly) { @@ -60,7 +60,7 @@ private static IEnumerable FindIdentifableTypes(Assembly ass foreach (var type in assembly.GetTypes()) { - if (TryGetResourceDescriptor(type, out var descriptor)) + if (TryGetResourceDescriptor(type, out var descriptor)) { descriptors.Add(descriptor); yield return descriptor; @@ -77,15 +77,15 @@ private static IEnumerable FindIdentifableTypes(Assembly ass internal static bool TryGetResourceDescriptor(Type type, out ResourceDescriptor descriptor) { var possible = GetIdType(type); - if (possible.isJsonApiResource) { + if (possible.isJsonApiResource) + { descriptor = new ResourceDescriptor(type, possible.idType); return true; - } - + } + descriptor = ResourceDescriptor.Empty; return false; } - /// /// Get all implementations of the generic interface /// @@ -99,11 +99,11 @@ internal static bool TryGetResourceDescriptor(Type type, out ResourceDescriptor /// public static (Type implementation, Type registrationInterface) GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterfaceType, params Type[] genericInterfaceArguments) { - if(assembly == null) throw new ArgumentNullException(nameof(assembly)); - if(openGenericInterfaceType == null) throw new ArgumentNullException(nameof(openGenericInterfaceType)); - if(genericInterfaceArguments == null) throw new ArgumentNullException(nameof(genericInterfaceArguments)); - if(genericInterfaceArguments.Length == 0) throw new ArgumentException("No arguments supplied for the generic interface.", nameof(genericInterfaceArguments)); - if(openGenericInterfaceType.IsGenericType == false) throw new ArgumentException("Requested type is not a generic type.", nameof(openGenericInterfaceType)); + if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + if (openGenericInterfaceType == null) throw new ArgumentNullException(nameof(openGenericInterfaceType)); + if (genericInterfaceArguments == null) throw new ArgumentNullException(nameof(genericInterfaceArguments)); + if (genericInterfaceArguments.Length == 0) throw new ArgumentException("No arguments supplied for the generic interface.", nameof(genericInterfaceArguments)); + if (openGenericInterfaceType.IsGenericType == false) throw new ArgumentException("Requested type is not a generic type.", nameof(openGenericInterfaceType)); foreach (var type in assembly.GetTypes()) { @@ -113,7 +113,8 @@ public static (Type implementation, Type registrationInterface) GetGenericInterf if (interfaceType.IsGenericType) { var genericTypeDefinition = interfaceType.GetGenericTypeDefinition(); - if(genericTypeDefinition == openGenericInterfaceType.GetGenericTypeDefinition()) { + if (interfaceType.GetGenericArguments().First() == genericInterfaceArguments.First() &&genericTypeDefinition == openGenericInterfaceType.GetGenericTypeDefinition()) + { return ( type, genericTypeDefinition.MakeGenericType(genericInterfaceArguments) @@ -157,7 +158,7 @@ public static IEnumerable GetDerivedTypes(Assembly assembly, Type inherite { foreach (var type in assembly.GetTypes()) { - if(inheritedType.IsAssignableFrom(type)) + if (inheritedType.IsAssignableFrom(type)) yield return type; } } diff --git a/src/JsonApiDotNetCore/Internal/JsonApiException.cs b/src/JsonApiDotNetCore/Internal/JsonApiException.cs index 0852ac1e04..6c3690df3f 100644 --- a/src/JsonApiDotNetCore/Internal/JsonApiException.cs +++ b/src/JsonApiDotNetCore/Internal/JsonApiException.cs @@ -25,6 +25,12 @@ public JsonApiException(string statusCode, string message, string detail, string : base(message) => _errors.Add(new Error(statusCode, message, detail, GetMeta(), source)); + /// + /// + /// + /// the integer status code to throw + /// + /// public JsonApiException(int statusCode, string message, string source = null) : base(message) => _errors.Add(new Error(statusCode, message, null, GetMeta(), source)); diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index d27fc158fd..e486731052 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -1,10 +1,11 @@ using System; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal { - public class PageManager + public class PageManager : IPageManager { public int? TotalRecords { get; set; } public int PageSize { get; set; } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs new file mode 100644 index 0000000000..f7c7c9012c --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs @@ -0,0 +1,11 @@ +using JsonApiDotNetCore.Internal; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IPageManager + { + } +} diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs new file mode 100644 index 0000000000..d148127dcd --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IQueryManager + { + /// + /// Gets the relationships as set in the query parameters + /// + /// + List GetRelationships(); + /// + /// Gets the sparse fields + /// + /// + List GetFields(); + } +} diff --git a/src/JsonApiDotNetCore/Managers/QueryManager.cs b/src/JsonApiDotNetCore/Managers/QueryManager.cs new file mode 100644 index 0000000000..3e4dbdc66a --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/QueryManager.cs @@ -0,0 +1,28 @@ +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Services; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers +{ + class QueryManager : IQueryManager + { + private IJsonApiContext _jsonApiContext; + + public QueryManager(IJsonApiContext jsonApiContext) + { + _jsonApiContext = jsonApiContext; + } + + public List GetFields() + { + return _jsonApiContext.QuerySet?.Fields; + } + + public List GetRelationships() + { + return _jsonApiContext.QuerySet?.IncludedRelationships; + } + } +} diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 8aa684e306..19a12370c9 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -1,7 +1,9 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -17,9 +19,11 @@ public class EntityResourceService : EntityResourceService entityRepository, - IJsonApiOptions optionsFetcher, + IJsonApiOptions options, + IQueryManager queryManager, + IPageManager pageManager, ILoggerFactory loggerFactory = null) : - base(jsonApiContext, entityRepository, loggerFactory) + base(jsonApiContext, entityRepository, options, queryManager, pageManager, loggerFactory) { } } @@ -31,8 +35,10 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, + IQueryManager queryManager, + IPageManager pageManager, ILoggerFactory loggerFactory = null) - : base(jsonApiContext, entityRepository, apiOptions, loggerFactory) + : base(jsonApiContext, entityRepository, apiOptions, queryManager, pageManager, loggerFactory) { } } @@ -41,9 +47,11 @@ public class EntityResourceService : where TResource : class, IIdentifiable where TEntity : class, IIdentifiable { + private readonly IPageManager _pageManager; + private readonly IQueryManager _queryManager; private readonly IJsonApiContext _jsonApiContext; private readonly IJsonApiOptions _options; - private readonly IEntityRepository _entities; + private readonly IEntityRepository _repository; private readonly ILogger _logger; private readonly IResourceMapper _mapper; @@ -51,7 +59,9 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, loggerFactory, null) + IQueryManager queryManager, + IPageManager pageManager, + ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, null, queryManager, pageManager, loggerFactory ) { // no mapper provided, TResource & TEntity must be the same type if (typeof(TResource) != typeof(TEntity)) @@ -64,13 +74,20 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceMapper mapper) + IResourceMapper mapper, + IQueryManager queryManager, + IPageManager pageManager, + ILoggerFactory loggerFactory) { + _pageManager = pageManager; + _queryManager = queryManager; _jsonApiContext = jsonApiContext; _options = options; - _entities = entityRepository; - _logger = loggerFactory.CreateLogger>(); + _repository = entityRepository; + if(loggerFactory != null) + { + _logger = loggerFactory.CreateLogger>(); + } _mapper = mapper; } @@ -78,14 +95,22 @@ public virtual async Task CreateAsync(TResource resource) { var entity = MapIn(resource); - entity = await _entities.CreateAsync(entity); + try + { + entity = await _repository.CreateAsync(entity); + + } + catch(DbUpdateException ex) + { + throw new JsonApiException(500, "Database update exception", ex); + } // this ensures relationships get reloaded from the database if they have // been requested // https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343 - if (ShouldIncludeRelationships()) + if (AreRelationshipsIncluded()) { - if (_entities is IEntityFrameworkRepository efRepository) + if (_repository is IEntityFrameworkRepository efRepository) efRepository.DetachRelationshipPointers(entity); return await GetWithRelationshipsAsync(entity.Id); @@ -96,26 +121,25 @@ public virtual async Task CreateAsync(TResource resource) public virtual async Task DeleteAsync(TId id) { - return await _entities.DeleteAsync(id); + return await _repository.DeleteAsync(id); } - public virtual async Task> GetAsync() { - var entities = _entities.GetQueryable(); + var entities = _repository.GetQueryable(); entities = ApplySortAndFilterQuery(entities); - if (ShouldIncludeRelationships()) + if (AreRelationshipsIncluded()) { entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); } if (_options.IncludeTotalRecordCount) { - _jsonApiContext.PageManager.TotalRecords = await _entities.CountAsync(entities); + _jsonApiContext.PageManager.TotalRecords = await _repository.CountAsync(entities); } - entities = _entities.Select(entities, _jsonApiContext.QuerySet?.Fields); + entities = _repository.Select(entities, _jsonApiContext.QuerySet?.Fields); // pagination should be done last since it will execute the query var pagedEntities = await ApplyPageQueryAsync(entities); @@ -124,12 +148,21 @@ public virtual async Task> GetAsync() public virtual async Task GetAsync(TId id) { - if (ShouldIncludeRelationships()) - return await GetWithRelationshipsAsync(id); - - TEntity entity = await _entities.GetAsync(id); - - return MapOut(entity); + + TResource resource; + if (AreRelationshipsIncluded()) + { + resource = await GetWithRelationshipsAsync(id); + } + else + { + resource = MapOut(await _repository.GetAsync(id)); + } + if(resource == null) + { + throw new JsonApiException(404, $"That entity ({_jsonApiContext.RequestEntity.EntityName}) with id ({id}) was not found in the database"); + } + return resource; } public virtual async Task GetRelationshipsAsync(TId id, string relationshipName) @@ -137,7 +170,7 @@ public virtual async Task GetRelationshipsAsync(TId id, string relations public virtual async Task GetRelationshipAsync(TId id, string relationshipName) { - var entity = await _entities.GetAndIncludeAsync(id, relationshipName); + var entity = await _repository.GetAndIncludeAsync(id, relationshipName); // TODO: it would be better if we could distinguish whether or not the relationship was not found, // vs the relationship not being set on the instance of T @@ -161,14 +194,14 @@ public virtual async Task UpdateAsync(TId id, TResource resource) { var entity = MapIn(resource); - entity = await _entities.UpdateAsync(id, entity); + entity = await _repository.UpdateAsync(id, entity); return MapOut(entity); } public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipName, List relationships) { - var entity = await _entities.GetAndIncludeAsync(id, relationshipName); + var entity = await _repository.GetAndIncludeAsync(id, relationshipName); if (entity == null) { throw new JsonApiException(404, $"Entity with id {id} could not be found."); @@ -195,7 +228,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa var relationshipIds = relationships.Select(r => r?.Id?.ToString()); - await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds); + await _repository.UpdateRelationshipsAsync(entity, relationship, relationshipIds); relationship.Type = relationshipType; } @@ -205,7 +238,7 @@ protected virtual async Task> ApplyPageQueryAsync(IQuerya var pageManager = _jsonApiContext.PageManager; if (!pageManager.IsPaginated) { - var allEntities = await _entities.ToListAsync(entities); + var allEntities = await _repository.ToListAsync(entities); return (typeof(TResource) == typeof(TEntity)) ? allEntities as IEnumerable : _mapper.Map>(allEntities); } @@ -216,7 +249,7 @@ protected virtual async Task> ApplyPageQueryAsync(IQuerya $"with {pageManager.PageSize} entities"); } - var pagedEntities = await _entities.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage); + var pagedEntities = await _repository.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage); return MapOut(pagedEntities); } @@ -230,50 +263,77 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable 0) foreach (var filter in query.Filters) - entities = _entities.Filter(entities, filter); + entities = _repository.Filter(entities, filter); - entities = _entities.Sort(entities, query.SortParameters); + entities = _repository.Sort(entities, query.SortParameters); return entities; } + /// + /// actually include the relationships + /// + /// + /// + /// protected virtual IQueryable IncludeRelationships(IQueryable entities, List relationships) { _jsonApiContext.IncludedRelationships = relationships; foreach (var r in relationships) - entities = _entities.Include(entities, r); + { + entities = _repository.Include(entities, r); + } return entities; } + /// + /// Get the specified id with relationships (async) + /// + /// + /// private async Task GetWithRelationshipsAsync(TId id) { - var query = _entities.Select(_entities.GetQueryable(), _jsonApiContext.QuerySet?.Fields).Where(e => e.Id.Equals(id)); + var fields = _queryManager.GetFields(); + var query = _repository.Select(_repository.GetQueryable(), fields).Where(e => e.Id.Equals(id)); - _jsonApiContext.QuerySet.IncludedRelationships.ForEach(r => + _queryManager.GetRelationships().ForEach(r => { - query = _entities.Include(query, r); + query = _repository.Include(query, r); }); TEntity value; // https://github.com/aspnet/EntityFrameworkCore/issues/6573 - if (_jsonApiContext.QuerySet?.Fields?.Count > 0) + if(_queryManager.GetFields()?.Count() > 0) + { value = query.FirstOrDefault(); + } else - value = await _entities.FirstOrDefaultAsync(query); - + { + value = await _repository.FirstOrDefaultAsync(query); + } return MapOut(value); } - private bool ShouldIncludeRelationships() - => (_jsonApiContext.QuerySet?.IncludedRelationships != null && - _jsonApiContext.QuerySet.IncludedRelationships.Count > 0); + /// + /// Should the relationships be included? + /// + /// + private bool AreRelationshipsIncluded() + { + return _queryManager.GetRelationships()?.Count() > 0; + } + /// + /// Casts the entity given to `TResource` or maps it to its equal + /// + /// + /// private TResource MapOut(TEntity entity) - => (typeof(TResource) == typeof(TEntity)) - ? entity as TResource : - _mapper.Map(entity); + { + return (typeof(TResource) == typeof(TEntity)) ? entity as TResource : _mapper.Map(entity); + } private IEnumerable MapOut(IEnumerable entities) => (typeof(TResource) == typeof(TEntity)) diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 5806fae78b..94e13cb243 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -12,7 +12,7 @@ namespace JsonApiDotNetCore.Services { public interface IJsonApiApplication { - JsonApiOptions Options { get; set; } + IJsonApiOptions Options { get; set; } IResourceGraph ResourceGraph { get; set; } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 2553cbe451..33e3be2093 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -20,7 +20,7 @@ public class JsonApiContext : IJsonApiContext public JsonApiContext( IResourceGraph resourceGraph, IHttpContextAccessor httpContextAccessor, - JsonApiOptions options, + IJsonApiOptions options, IMetaBuilder metaBuilder, IGenericProcessorFactory genericProcessorFactory, IQueryParser queryParser, @@ -35,7 +35,7 @@ public JsonApiContext( _controllerContext = controllerContext; } - public JsonApiOptions Options { get; set; } + public IJsonApiOptions Options { get; set; } public IResourceGraph ResourceGraph { get; set; } [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index cd839ffa73..21a66e651b 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -18,11 +18,11 @@ public interface IQueryParser public class QueryParser : IQueryParser { private readonly IControllerContext _controllerContext; - private readonly JsonApiOptions _options; + private readonly IJsonApiOptions _options; public QueryParser( IControllerContext controllerContext, - JsonApiOptions options) + IJsonApiOptions options) { _controllerContext = controllerContext; _options = options; diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 6953b5f49c..dc51ef806e 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -1,8 +1,10 @@ using GettingStarted.Models; using GettingStarted.ResourceDefinitionExample; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.Extensions.DependencyInjection; @@ -73,8 +75,11 @@ public class TestModel : Identifiable { } public class TestModelService : EntityResourceService { private static IEntityRepository _repo = new Mock>().Object; - private static IJsonApiContext _jsonApiContext = new Mock().Object; - public TestModelService() : base(_jsonApiContext, _repo) { } + private static IJsonApiContext _jsonApiContext = new Mock().Object; + private static IJsonApiOptions _jsonApiOptions = new Mock().Object; + private static IQueryManager _queryManager = new Mock().Object; + private static IPageManager _pageManager = new Mock().Object; + public TestModelService() : base(_jsonApiContext, _repo, _jsonApiOptions, _queryManager, _pageManager) { } } public class TestModelRepository : DefaultEntityRepository diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs index 2030694918..40c74b4500 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs @@ -82,7 +82,7 @@ public async Task CheckNullBehaviorCombination(bool? omitNullValuedAttributes, b { nullAttributeResponseBehavior = new NullAttributeResponseBehavior(); } - var jsonApiOptions = _fixture.GetService(); + var jsonApiOptions = _fixture.GetService(); jsonApiOptions.NullAttributeResponseBehavior = nullAttributeResponseBehavior; jsonApiOptions.AllowCustomQueryParameters = true; diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs index 90496b3690..88906ba7ab 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using JsonApiDotNetCoreExample; @@ -14,14 +14,14 @@ public class HttpReadOnlyTests [Fact] public async Task Allows_GET_Requests() { - // arrange + // Arrange const string route = "readonly"; const string method = "GET"; - // act + // Act var statusCode = await MakeRequestAsync(route, method); - // assert + // Assert Assert.Equal(HttpStatusCode.OK, statusCode); } @@ -79,4 +79,4 @@ private async Task MakeRequestAsync(string route, string method) return response.StatusCode; } } -} \ No newline at end of file +} diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index a34312ca2f..cd52df5e9d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -78,6 +78,7 @@ public async Task Can_Create_Guid_Identifiable_Entity() // act var response = await client.SendAsync(request); + var sdfsd = await response.Content.ReadAsStringAsync(); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index cbe2b15759..08c41bd4da 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -12,47 +12,75 @@ using JsonApiDotNetCore.Data; using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using System.Net; +using JsonApiDotNetCore.Managers.Contracts; -namespace UnitTests.JsonApiContext +namespace UnitTests.Services { - public class BasicTest + public class EntityResourceServiceMore { [Fact] - public async Task CanTestController() + public async Task TestCanGetAll() { - // Arrange - var jsonApiContext = new Mock(); - var serviceMock = new Mock>(); - var controller = new ArticlesController(jsonApiContext.Object,serviceMock.Object); - // Act - var result = await controller.GetAsync(); + } - // Assert - var okResult = Assert.IsType(result); - var value = okResult.Value as IEnumerable
; + /// + /// we expect the service layer to give use a 404 if there is no entity returned + /// + /// + [Fact] + public async Task GetAsync_Throw404OnNoEntityFound() + { + // Arrange + var jacMock = FetchContextMock(); + var loggerMock = new Mock(); + var jsonApiOptions = new JsonApiOptions + { + IncludeTotalRecordCount = false + } as IJsonApiOptions; + var repositoryMock = new Mock>(); + var queryManagerMock = new Mock(); + var pageManagerMock = new Mock(); + var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); - Assert.NotNull(value); + // Act / Assert + var toExecute = new Func(() => + { + return service.GetAsync(4); + }); + var exception = await Assert.ThrowsAsync(toExecute); + Assert.Equal(404, exception.GetStatusCode()); } + /// + /// we expect the service layer to give use a 404 if there is no entity returned + /// + /// [Fact] - public async Task CanTestService() + public async Task GetAsync_ShouldThrow404OnNoEntityFoundWithRelationships() { // Arrange var jacMock = FetchContextMock(); var loggerMock = new Mock(); - var jsonApiOptionsMock = new JsonApiOptions + var jsonApiOptions = new JsonApiOptions { IncludeTotalRecordCount = false - }; + } as IJsonApiOptions; var repositoryMock = new Mock>(); + var queryManagerMock = new Mock(); + var pageManagerMock = new Mock(); + queryManagerMock.Setup(qm => qm.GetRelationships()).Returns(new List() { "cookies" }); + var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); - var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptionsMock.Object, loggerMock.Object); - // Act - var result = await service.GetAsync(); - - // Assert - Assert.NotNull(result); + // Act / Assert + var toExecute = new Func(() => + { + return service.GetAsync(4); + }); + var exception = await Assert.ThrowsAsync(toExecute); + Assert.Equal(404, exception.GetStatusCode()); } public Mock FetchContextMock() diff --git a/test/UnitTests/Services/EntityResourceService_Tests.cs b/test/UnitTests/Services/EntityResourceService_Tests.cs index 4380a6622b..7052641583 100644 --- a/test/UnitTests/Services/EntityResourceService_Tests.cs +++ b/test/UnitTests/Services/EntityResourceService_Tests.cs @@ -73,6 +73,6 @@ public async Task GetRelationshipAsync_Returns_Relationship_Value() } private EntityResourceService GetService() => - new EntityResourceService(_jsonApiContextMock.Object, _repositoryMock.Object, _loggerFactory); + new EntityResourceService(_jsonApiContextMock.Object, _repositoryMock.Object, null, null,null, _loggerFactory); } } From ab78ad222d98824052a29e1b57467e72b72bd300 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 17 May 2019 10:51:03 +0200 Subject: [PATCH 04/10] feat: changed startup --- src/Examples/JsonApiDotNetCoreExample/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 960cc2f0b1..ff77dd068c 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -42,7 +42,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) options.Namespace = "api/v1"; options.DefaultPageSize = 5; options.IncludeTotalRecordCount = true; - }, + }, mvcBuilder, discovery => discovery.AddCurrentAssembly()); From 5da1a867a8bdeee1d217d5d68b6d9190fab72a58 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 17 May 2019 16:28:45 +0200 Subject: [PATCH 05/10] feat: most annoying commit of my life, rewriting dozens of controllers --- .../Controllers/ArticlesController.cs | 9 ++-- .../Controllers/PeopleController.cs | 10 ++-- .../ModelsController.cs | 10 ++-- .../Controllers/ArticlesController.cs | 6 ++- .../Controllers/CamelCasedModelsController.cs | 4 +- .../Controllers/PeopleController.cs | 4 +- .../Controllers/PersonRolesController.cs | 4 +- .../Controllers/TodoCollectionsController.cs | 6 ++- .../Controllers/TodoItemsController.cs | 4 +- .../Controllers/TodoItemsTestController.cs | 7 ++- .../Controllers/UsersController.cs | 4 +- .../JsonApiDotNetCoreExample/Startup.cs | 6 --- .../Controllers/CustomTodoItemsController.cs | 6 ++- .../Controllers/ReportsController.cs | 10 ++-- .../Controllers/CoursesController.cs | 4 +- .../Controllers/DepartmentsController.cs | 4 +- .../Controllers/StudentsController.cs | 4 +- .../Controllers/BaseJsonApiController.cs | 46 ++++++++++++------- .../Controllers/JsonApiCmdController.cs | 11 +++-- .../Controllers/JsonApiController.cs | 37 ++++++++++----- .../Controllers/JsonApiQueryController.cs | 7 ++- .../Data/DefaultEntityRepository.cs | 5 -- .../Extensions/ModelStateExtensions.cs | 2 +- .../BaseJsonApiController_Tests.cs | 44 +++++++++--------- 24 files changed, 154 insertions(+), 100 deletions(-) diff --git a/src/Examples/GettingStarted/Controllers/ArticlesController.cs b/src/Examples/GettingStarted/Controllers/ArticlesController.cs index 53517540b1..93d370897b 100644 --- a/src/Examples/GettingStarted/Controllers/ArticlesController.cs +++ b/src/Examples/GettingStarted/Controllers/ArticlesController.cs @@ -1,4 +1,5 @@ using GettingStarted.Models; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -7,9 +8,9 @@ namespace GettingStarted public class ArticlesController : JsonApiController
{ public ArticlesController( - IJsonApiContext jsonApiContext, - IResourceService
resourceService) - : base(jsonApiContext, resourceService) + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService
resourceService) : base(jsonApiOptions, jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs index f3c0c4b868..b033ae172a 100644 --- a/src/Examples/GettingStarted/Controllers/PeopleController.cs +++ b/src/Examples/GettingStarted/Controllers/PeopleController.cs @@ -1,4 +1,5 @@ using GettingStarted.Models; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -7,9 +8,10 @@ namespace GettingStarted public class PeopleController : JsonApiController { public PeopleController( - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiContext, resourceService) + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService resourceService) + : base(jsonApiOptions,jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs index a14394e830..55138e7d71 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs @@ -1,4 +1,5 @@ using GettingStarted.Models; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -7,9 +8,10 @@ namespace GettingStarted.ResourceDefinitionExample public class ModelsController : JsonApiController { public ModelsController( - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiContext, resourceService) + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs index 95aa7d69f9..865f8454a2 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -7,9 +8,10 @@ namespace JsonApiDotNetCoreExample.Controllers public class ArticlesController : JsonApiController
{ public ArticlesController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService
resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs index e46b3f8efd..cf9c8bdf2e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -11,10 +12,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class CamelCasedModelsController : JsonApiController { public CamelCasedModelsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs index e249e2af53..236c946f89 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class PeopleController : JsonApiController { public PeopleController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs index dbc3b482f5..e00c5b906e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class PersonRolesController : JsonApiController { public PersonRolesController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index a6063c6cc3..04fe912e57 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Services; @@ -16,11 +17,12 @@ public class TodoCollectionsController : JsonApiController resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { _dbResolver = contextResolver; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs index 768dd1c37c..040ad38e20 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class TodoItemsController : JsonApiController { public TodoItemsController( + IJsonApiOptions jsonApiOPtions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOPtions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs index 9bab3cf544..caab1c1c85 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -11,10 +12,11 @@ public abstract class AbstractTodoItemsController : JsonApiController where T : class, IIdentifiable { protected AbstractTodoItemsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, jsonApiContext, service, loggerFactory) { } } @@ -22,10 +24,11 @@ protected AbstractTodoItemsController( public class TodoItemsTestController : AbstractTodoItemsController { public TodoItemsTestController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, jsonApiContext, service, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs index dbd144caa4..931db12325 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class UsersController : JsonApiController { public UsersController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index ff77dd068c..fd26e0274a 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -32,9 +32,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); loggerFactory.AddConsole(LogLevel.Warning); - var mvcBuilder = services.AddMvcCore(); - services .AddSingleton(loggerFactory) .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) @@ -45,10 +43,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) }, mvcBuilder, discovery => discovery.AddCurrentAssembly()); - var serviceProvider = services.BuildServiceProvider(); - - return serviceProvider; } @@ -60,7 +55,6 @@ public virtual void Configure( { context.Database.EnsureCreated(); loggerFactory.AddConsole(Config.GetSection("Logging")); - var serviceProvider = app.ApplicationServices; app.UseJsonApi(); } diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs index a6ded9749f..4027582a9c 100644 --- a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs +++ b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs @@ -1,4 +1,5 @@ -using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -8,10 +9,11 @@ namespace NoEntityFrameworkExample.Controllers public class CustomTodoItemsController : JsonApiController { public CustomTodoItemsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index 6f431d9291..95add7975c 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -1,17 +1,19 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; - +using JsonApiDotNetCore.Configuration; + namespace ReportsExample.Controllers { [Route("api/[controller]")] public class ReportsController : BaseJsonApiController { - public ReportsController( + public ReportsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll) - : base(jsonApiContext, getAll: getAll) + : base(jsonApiOptions, jsonApiContext, getAll: getAll) { } [HttpGet] diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs index 6809ace0bb..908776ebc5 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; @@ -8,10 +9,11 @@ namespace ResourceEntitySeparationExample.Controllers public class CoursesController : JsonApiController { public CoursesController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs index 08f3ab33ad..9edef6fa7c 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs @@ -1,4 +1,5 @@ using System; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; @@ -9,10 +10,11 @@ namespace ResourceEntitySeparationExample.Controllers public class DepartmentsController : JsonApiController { public DepartmentsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs index 34d5d33031..7ba8fde1af 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; @@ -8,10 +9,11 @@ namespace ResourceEntitySeparationExample.Controllers public class StudentsController : JsonApiController { public StudentsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 93fd4826e2..f2206667ec 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; @@ -13,17 +14,20 @@ public class BaseJsonApiController where T : class, IIdentifiable { public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService - ) : base(jsonApiContext, resourceService) { } + ) : base(jsonApiOptions, jsonApiContext, resourceService) { } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null - ) : base(jsonApiContext, queryService, cmdService) { } + ) : base(jsonApiOptions, jsonApiContext, queryService, cmdService) { } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, @@ -33,7 +37,7 @@ public BaseJsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } public class BaseJsonApiController @@ -48,12 +52,15 @@ public class BaseJsonApiController private readonly IUpdateService _update; private readonly IUpdateRelationshipService _updateRelationships; private readonly IDeleteService _delete; + private readonly IJsonApiOptions _jsonApiOptions; private readonly IJsonApiContext _jsonApiContext; public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) { + _jsonApiOptions = jsonApiOptions; _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = resourceService; _getById = resourceService; @@ -66,10 +73,12 @@ public BaseJsonApiController( } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null) { + _jsonApiOptions = jsonApiOptions; _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = queryService; _getById = queryService; @@ -82,6 +91,7 @@ public BaseJsonApiController( } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, @@ -92,6 +102,7 @@ public BaseJsonApiController( IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null) { + _jsonApiOptions = jsonApiOptions; _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = getAll; _getById = getById; @@ -106,28 +117,27 @@ public BaseJsonApiController( public virtual async Task GetAsync() { if (_getAll == null) throw Exceptions.UnSupportedRequestMethod; - var entities = await _getAll.GetAsync(); - return Ok(entities); } 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; - + if (_getRelationships == null) + { + throw Exceptions.UnSupportedRequestMethod; + } var relationship = await _getRelationships.GetRelationshipsAsync(id, relationshipName); if (relationship == null) return NotFound(); @@ -138,9 +148,7 @@ public virtual async Task GetRelationshipsAsync(TId id, string re public virtual async Task GetRelationshipAsync(TId id, string relationshipName) { if (_getRelationship == null) throw Exceptions.UnSupportedRequestMethod; - var relationship = await _getRelationship.GetRelationshipAsync(id, relationshipName); - return Ok(relationship); } @@ -152,11 +160,13 @@ public virtual async Task PostAsync([FromBody] T entity) if (entity == null) return UnprocessableEntity(); - if (!_jsonApiContext.Options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId)) + if (!_jsonApiOptions.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId)) return Forbidden(); - if (_jsonApiContext.Options.ValidateModelState && !ModelState.IsValid) - return UnprocessableEntity(ModelState.ConvertToErrorCollection(_jsonApiContext.ResourceGraph)); + if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) + { + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + } entity = await _create.CreateAsync(entity); @@ -170,8 +180,10 @@ public virtual async Task PatchAsync(TId id, [FromBody] T entity) if (entity == null) return UnprocessableEntity(); - if (_jsonApiContext.Options.ValidateModelState && !ModelState.IsValid) - return UnprocessableEntity(ModelState.ConvertToErrorCollection(_jsonApiContext.ResourceGraph)); + if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) + { + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + } var updatedEntity = await _update.UpdateAsync(id, entity); diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index 16ab4aa74a..b3d69060a0 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -1,18 +1,20 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; namespace JsonApiDotNetCore.Controllers { - public class JsonApiCmdController - : JsonApiCmdController where T : class, IIdentifiable + public class JsonApiCmdController : JsonApiCmdController + where T : class, IIdentifiable { public JsonApiCmdController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } @@ -20,9 +22,10 @@ public class JsonApiCmdController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiCmdController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } [HttpPost] diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index a77c03da06..4569048d6d 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -7,23 +8,30 @@ namespace JsonApiDotNetCore.Controllers { - public class JsonApiController - : JsonApiController where T : class, IIdentifiable + public class JsonApiController : JsonApiController where T : class, IIdentifiable { + private IJsonApiOptions jsonApiOptions; + private IJsonApiContext jsonApiContext; + private IResourceService resourceService; + private ILoggerFactory loggerFactory; + public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, @@ -33,27 +41,34 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + + public JsonApiController(IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + { + } } - public class JsonApiController - : BaseJsonApiController where T : class, IIdentifiable + public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } public JsonApiController( - IJsonApiContext jsonApiContext, + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -62,7 +77,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } [HttpGet] public override async Task GetAsync() => await base.GetAsync(); diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index 5211e5fa3b..ad0978709b 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -9,9 +10,10 @@ public class JsonApiQueryController : JsonApiQueryController where T : class, IIdentifiable { public JsonApiQueryController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } @@ -19,9 +21,10 @@ public class JsonApiQueryController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiQueryController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } [HttpGet] diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 59813d7c3d..9a1c94e4fc 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -159,12 +159,7 @@ public virtual async Task CreateAsync(TEntity entity) { AttachRelationships(entity); _dbSet.Add(entity); - - - await _context.SaveChangesAsync(); - - return entity; } diff --git a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs index 4b31a7f021..05ce8e6238 100644 --- a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs @@ -27,7 +27,7 @@ public static ErrorCollection ConvertToErrorCollection(this ModelStateDictionary return collection; } - public static ErrorCollection ConvertToErrorCollection(this ModelStateDictionary modelState, IResourceGraph resourceGraph) + public static ErrorCollection ConvertToErrorCollection(ModelStateDictionary modelState, IResourceGraph resourceGraph) { ErrorCollection collection = new ErrorCollection(); foreach (var entry in modelState) diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index 3397aa4eda..f0d7184cb1 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -25,7 +25,7 @@ public async Task GetAsync_Calls_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getAll: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getAll: serviceMock.Object); // act await controller.GetAsync(); @@ -40,7 +40,7 @@ public async Task GetAsync_Throws_405_If_No_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync()); @@ -55,7 +55,7 @@ public async Task GetAsyncById_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getById: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getById: serviceMock.Object); // act await controller.GetAsync(id); @@ -71,7 +71,7 @@ public async Task GetAsyncById_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getById: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getById: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync(id)); @@ -86,7 +86,7 @@ public async Task GetRelationshipsAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationships: serviceMock.Object); // act await controller.GetRelationshipsAsync(id, string.Empty); @@ -102,7 +102,7 @@ public async Task GetRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipsAsync(id, string.Empty)); @@ -117,7 +117,7 @@ public async Task GetRelationshipAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationship: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationship: serviceMock.Object); // act await controller.GetRelationshipAsync(id, string.Empty); @@ -133,7 +133,7 @@ public async Task GetRelationshipAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationship: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationship: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipAsync(id, string.Empty)); @@ -151,7 +151,7 @@ public async Task PatchAsync_Calls_Service() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); // act await controller.PatchAsync(id, resource); @@ -170,7 +170,7 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateDisbled() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); // act var response = await controller.PatchAsync(id, resource); @@ -192,7 +192,7 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateEnabled() _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions{ValidateModelState = true}); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -210,7 +210,7 @@ public async Task PatchAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchAsync(id, It.IsAny())); @@ -227,7 +227,7 @@ public async Task PostAsync_Calls_Service() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext {HttpContext = new DefaultHttpContext()}; @@ -247,7 +247,7 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; @@ -270,7 +270,7 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateEnabled() _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = true }); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -289,7 +289,7 @@ public async Task PatchRelationshipsAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, updateRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, updateRelationships: serviceMock.Object); // act await controller.PatchRelationshipsAsync(id, string.Empty, null); @@ -305,7 +305,7 @@ public async Task PatchRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, updateRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, updateRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchRelationshipsAsync(id, string.Empty, null)); @@ -317,16 +317,16 @@ public async Task PatchRelationshipsAsync_Throws_405_If_No_Service() [Fact] public async Task DeleteAsync_Calls_Service() { - // arrange + // Arrange const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, delete: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, delete: serviceMock.Object); - // act + // Act await controller.DeleteAsync(id); - // assert + // Assert serviceMock.Verify(m => m.DeleteAsync(id), Times.Once); VerifyApplyContext(); } @@ -337,7 +337,7 @@ public async Task DeleteAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, delete: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, delete: null); // act var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); From 494f6eb56f459640f0153a8fce9e9fe67ef39812 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Thu, 23 May 2019 13:21:33 +0200 Subject: [PATCH 06/10] feat: rename: QueryManager -> RequestManager, deeper seperation of concerns, 12 tests running... --- ...uilder_ GetNamespaceFromPath_Benchmarks.cs | 40 +++++++- benchmarks/Program.cs | 4 +- .../JsonApiSerializer_Benchmarks.cs | 98 +++++++++---------- .../Controllers/ArticlesController.cs | 10 +- .../Services/CustomArticleService.cs | 2 +- .../Builders/ContextGraphBuilder.cs | 1 + .../Builders/DocumentBuilder.cs | 70 +++++++------ .../Builders/ILinkBuilder.cs | 11 +++ src/JsonApiDotNetCore/Builders/LinkBuilder.cs | 58 +++-------- .../Configuration/IJsonApiOptions.cs | 1 + .../Configuration/JsonApiOptions.cs | 1 + .../Controllers/BaseJsonApiController.cs | 22 ++--- .../Controllers/JsonApiCmdController.cs | 3 +- .../Controllers/JsonApiController.cs | 17 ++-- .../Data/DefaultEntityRepository.cs | 8 +- .../IApplicationBuilderExtensions.cs | 1 + .../IServiceCollectionExtensions.cs | 3 +- .../Extensions/ModelStateExtensions.cs | 1 + .../Internal/ContextGraph.cs | 1 + .../Internal/Contracts/IResourceGraph.cs | 87 ++++++++++++++++ src/JsonApiDotNetCore/Internal/PageManager.cs | 16 ++- .../Internal/ResourceGraph.cs | 81 +++------------ .../Managers/Contracts/IPageManager.cs | 24 +++++ .../Managers/Contracts/IQueryManager.cs | 20 ---- .../Managers/Contracts/IRequestManager.cs | 41 ++++++++ .../Contracts/IResourceGraphManager.cs | 11 +++ .../Managers/QueryManager.cs | 28 ------ .../Managers/RequestManager.cs | 41 ++++++++ .../Middleware/RequestMiddleware.cs | 63 +++++++++++- .../Models/ResourceDefinition.cs | 1 + .../Serialization/JsonApiSerializer.cs | 6 +- .../Services/EntityResourceService.cs | 31 +++--- .../Services/IJsonApiContext.cs | 17 ++-- .../Services/JsonApiContext.cs | 45 +++++---- .../Processors/CreateOpProcessor.cs | 1 + .../Operations/Processors/GetOpProcessor.cs | 1 + .../Processors/RemoveOpProcessor.cs | 1 + .../Processors/UpdateOpProcessor.cs | 1 + .../Services/QueryAccessor.cs | 4 +- .../Services/QueryComposer.cs | 9 +- .../ServiceDiscoveryFacadeTests.cs | 2 +- .../CamelCasedModelsControllerTests.cs | 6 +- .../Extensibility/CustomErrorTests.cs | 2 +- .../Builders/ContextGraphBuilder_Tests.cs | 1 + .../Builders/DocumentBuilder_Tests.cs | 57 +++++++---- test/UnitTests/Builders/LinkBuilder_Tests.cs | 35 +++---- .../BaseJsonApiController_Tests.cs | 5 +- .../IServiceCollectionExtensionsTests.cs | 1 + test/UnitTests/JsonApiContext/BasicTest.cs | 4 +- .../Models/ResourceDefinitionTests.cs | 1 + .../Serialization/JsonApiSerializerTests.cs | 37 ++++--- test/UnitTests/Services/QueryAccessorTests.cs | 94 +++++++++--------- test/UnitTests/Services/QueryComposerTests.cs | 21 ++-- 53 files changed, 702 insertions(+), 445 deletions(-) create mode 100644 src/JsonApiDotNetCore/Builders/ILinkBuilder.cs create mode 100644 src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs delete mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs delete mode 100644 src/JsonApiDotNetCore/Managers/QueryManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/RequestManager.cs diff --git a/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs b/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs index 05728321c3..1432afecd8 100644 --- a/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs +++ b/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs @@ -1,10 +1,11 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes.Exporters; using BenchmarkDotNet.Attributes.Jobs; +using System; namespace Benchmarks.LinkBuilder { - [MarkdownExporter, SimpleJob(launchCount : 3, warmupCount : 10, targetCount : 20), MemoryDiagnoser] + [MarkdownExporter, SimpleJob(launchCount: 3, warmupCount: 10, targetCount: 20), MemoryDiagnoser] public class LinkBuilder_GetNamespaceFromPath_Benchmarks { private const string PATH = "/api/some-really-long-namespace-path/resources/current/articles"; @@ -14,7 +15,7 @@ public class LinkBuilder_GetNamespaceFromPath_Benchmarks public void UsingSplit() => GetNamespaceFromPath_BySplitting(PATH, ENTITY_NAME); [Benchmark] - public void Current() => GetNameSpaceFromPath_Current(PATH, ENTITY_NAME); + public void Current() => GetNameSpaceFromPathCurrent(PATH, ENTITY_NAME); public static string GetNamespaceFromPath_BySplitting(string path, string entityName) { @@ -32,7 +33,38 @@ public static string GetNamespaceFromPath_BySplitting(string path, string entity return nSpace; } - public static string GetNameSpaceFromPath_Current(string path, string entityName) - => JsonApiDotNetCore.Builders.LinkBuilder.GetNamespaceFromPath(path, entityName); + public static string GetNameSpaceFromPathCurrent(string path, string entityName) + { + + var entityNameSpan = entityName.AsSpan(); + var pathSpan = path.AsSpan(); + const char delimiter = '/'; + for (var i = 0; i < pathSpan.Length; i++) + { + if (pathSpan[i].Equals(delimiter)) + { + var nextPosition = i + 1; + if (pathSpan.Length > i + entityNameSpan.Length) + { + var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); + if (entityNameSpan.SequenceEqual(possiblePathSegment)) + { + // check to see if it's the last position in the string + // or if the next character is a / + var lastCharacterPosition = nextPosition + entityNameSpan.Length; + + if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) + { + return pathSpan.Slice(0, i).ToString(); + } + } + } + } + } + + return string.Empty; + + + } } } diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index 9a2c45dffb..0ec4c80e14 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -1,4 +1,4 @@ -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; using Benchmarks.JsonApiContext; using Benchmarks.LinkBuilder; using Benchmarks.Query; @@ -10,7 +10,7 @@ class Program { static void Main(string[] args) { var switcher = new BenchmarkSwitcher(new[] { typeof(JsonApiDeserializer_Benchmarks), - typeof(JsonApiSerializer_Benchmarks), + //typeof(JsonApiSerializer_Benchmarks), typeof(QueryParser_Benchmarks), typeof(LinkBuilder_GetNamespaceFromPath_Benchmarks), typeof(ContainsMediaTypeParameters_Benchmarks), diff --git a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs index d80540434b..1238cc082f 100644 --- a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs @@ -1,49 +1,49 @@ -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes.Exporters; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Internal.Generics; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using Moq; -using Newtonsoft.Json.Serialization; - -namespace Benchmarks.Serialization { - [MarkdownExporter] - public class JsonApiSerializer_Benchmarks { - private const string TYPE_NAME = "simple-types"; - private static readonly SimpleType Content = new SimpleType(); - - private readonly JsonApiSerializer _jsonApiSerializer; - - public JsonApiSerializer_Benchmarks() { - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource(TYPE_NAME); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var genericProcessorFactoryMock = new Mock(); - - var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); - _jsonApiSerializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - } - - [Benchmark] - public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content); - - private class SimpleType : Identifiable { - [Attr("name")] - public string Name { get; set; } - } - } -} +//using System.Collections.Generic; +//using BenchmarkDotNet.Attributes; +//using BenchmarkDotNet.Attributes.Exporters; +//using JsonApiDotNetCore.Builders; +//using JsonApiDotNetCore.Configuration; +//using JsonApiDotNetCore.Internal.Generics; +//using JsonApiDotNetCore.Models; +//using JsonApiDotNetCore.Serialization; +//using JsonApiDotNetCore.Services; +//using Moq; +//using Newtonsoft.Json.Serialization; + +//namespace Benchmarks.Serialization { +// [MarkdownExporter] +// public class JsonApiSerializer_Benchmarks { +// private const string TYPE_NAME = "simple-types"; +// private static readonly SimpleType Content = new SimpleType(); + +// private readonly JsonApiSerializer _jsonApiSerializer; + +// public JsonApiSerializer_Benchmarks() { +// var resourceGraphBuilder = new ResourceGraphBuilder(); +// resourceGraphBuilder.AddResource(TYPE_NAME); +// var resourceGraph = resourceGraphBuilder.Build(); + +// var jsonApiContextMock = new Mock(); +// jsonApiContextMock.SetupAllProperties(); +// jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); +// jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + +// var jsonApiOptions = new JsonApiOptions(); +// jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); +// jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + +// var genericProcessorFactoryMock = new Mock(); + +// var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); +// _jsonApiSerializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); +// } + +// [Benchmark] +// public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content); + +// private class SimpleType : Identifiable { +// [Attr("name")] +// public string Name { get; set; } +// } +// } +//} diff --git a/src/Examples/GettingStarted/Controllers/ArticlesController.cs b/src/Examples/GettingStarted/Controllers/ArticlesController.cs index 93d370897b..8077983e92 100644 --- a/src/Examples/GettingStarted/Controllers/ArticlesController.cs +++ b/src/Examples/GettingStarted/Controllers/ArticlesController.cs @@ -10,7 +10,15 @@ public class ArticlesController : JsonApiController
public ArticlesController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, - IResourceService
resourceService) : base(jsonApiOptions, jsonApiContext, resourceService) + IResourceService
resourceService) + + + + + + + + : base(jsonApiOptions, jsonApiContext, resourceService) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index 5244f3d46d..92c5a11447 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -18,7 +18,7 @@ public CustomArticleService( IJsonApiContext jsonApiContext, IEntityRepository
repository, IJsonApiOptions jsonApiOptions, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory ) : base(jsonApiContext, repository, jsonApiOptions, queryManager, pageManager, loggerFactory) diff --git a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs index f17d20f4a2..038796c17a 100644 --- a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs @@ -7,6 +7,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 48464923d2..04194d68c2 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -4,6 +4,8 @@ using System.Linq; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -12,6 +14,8 @@ namespace JsonApiDotNetCore.Builders /// public class DocumentBuilder : IDocumentBuilder { + private readonly IRequestManager _requestManager; + private readonly IPageManager _pageManager; private readonly IJsonApiContext _jsonApiContext; private readonly IResourceGraph _resourceGraph; private readonly IRequestMeta _requestMeta; @@ -20,10 +24,14 @@ public class DocumentBuilder : IDocumentBuilder public DocumentBuilder( IJsonApiContext jsonApiContext, + IPageManager pageManager, + IRequestManager requestManager, IRequestMeta requestMeta = null, IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null, IScopedServiceProvider scopedServiceProvider = null) { + _requestManager = requestManager; + _pageManager = pageManager; _jsonApiContext = jsonApiContext; _resourceGraph = jsonApiContext.ResourceGraph; _requestMeta = requestMeta; @@ -44,7 +52,9 @@ public Document Build(IIdentifiable entity) }; if (ShouldIncludePageLinks(contextEntity)) - document.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext)); + { + document.Links = _pageManager.GetPageLinks(); + } document.Included = AppendIncludedObject(document.Included, contextEntity, entity); @@ -66,7 +76,9 @@ public Documents Build(IEnumerable entities) }; if (ShouldIncludePageLinks(contextEntity)) - documents.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext)); + { + documents.Links = _pageManager.GetPageLinks(); + } foreach (var entity in enumeratedEntities) { @@ -80,7 +92,7 @@ public Documents Build(IEnumerable entities) private Dictionary GetMeta(IIdentifiable entity) { var builder = _jsonApiContext.MetaBuilder; - if (_jsonApiContext.Options.IncludeTotalRecordCount && _jsonApiContext.PageManager.TotalRecords != null) + if (_jsonApiContext.Options.IncludeTotalRecordCount && _pageManager.TotalRecords != null) builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords); if (_requestMeta != null) @@ -146,9 +158,9 @@ private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue, R { return OmitNullValuedAttribute(attr, attributeValue) == false && attr.InternalAttributeName != nameof(Identifiable.Id) - && ((_jsonApiContext.QuerySet == null - || _jsonApiContext.QuerySet.Fields.Count == 0) - || _jsonApiContext.QuerySet.Fields.Contains(relationship != null ? + && ((_requestManager.QuerySet == null + || _requestManager.QuerySet.Fields.Count == 0) + || _requestManager.QuerySet.Fields.Contains(relationship != null ? $"{relationship.InternalRelationshipName}.{attr.InternalAttributeName}" : attr.InternalAttributeName)); } @@ -171,39 +183,39 @@ private void AddRelationships(ResourceObject data, ContextEntity contextEntity, private RelationshipData GetRelationshipData(RelationshipAttribute attr, ContextEntity contextEntity, IIdentifiable entity) { - var linkBuilder = new LinkBuilder(_jsonApiContext); + //var linkBuilder = new LinkBuilder(_documentBuilderOptions,_requestManager); var relationshipData = new RelationshipData(); - if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) - { - relationshipData.Links = new Links(); - if (attr.DocumentLinks.HasFlag(Link.Self)) - relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - - if (attr.DocumentLinks.HasFlag(Link.Related)) - relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - } - - // this only includes the navigation property, we need to actually check the navigation property Id - var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); - if (navigationEntity == null) - relationshipData.SingleData = attr.IsHasOne - ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) - : null; - else if (navigationEntity is IEnumerable) - relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); - else - relationshipData.SingleData = GetRelationship(navigationEntity); + //if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) + //{ + // relationshipData.Links = new Links(); + // if (attr.DocumentLinks.HasFlag(Link.Self)) + // relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + + // if (attr.DocumentLinks.HasFlag(Link.Related)) + // relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + //} + + //// this only includes the navigation property, we need to actually check the navigation property Id + //var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); + //if (navigationEntity == null) + // relationshipData.SingleData = attr.IsHasOne + // ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) + // : null; + //else if (navigationEntity is IEnumerable) + // relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); + //else + // relationshipData.SingleData = GetRelationship(navigationEntity); return relationshipData; } private List GetIncludedEntities(List included, ContextEntity rootContextEntity, IIdentifiable rootResource) { - if (_jsonApiContext.IncludedRelationships != null) + if (_jsonApiContext.RequestManager.IncludedRelationships != null) { - foreach (var relationshipName in _jsonApiContext.IncludedRelationships) + foreach (var relationshipName in _jsonApiContext.RequestManager.IncludedRelationships) { var relationshipChain = relationshipName.Split('.'); diff --git a/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs b/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs new file mode 100644 index 0000000000..c8af9e7dac --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Http; + +namespace JsonApiDotNetCore.Builders +{ + public interface ILinkBuilder + { + string GetPageLink(int pageOffset, int pageSize); + string GetRelatedRelationLink(string parent, string parentId, string child); + string GetSelfRelationLink(string parent, string parentId, string child); + } +} diff --git a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs index 3de45558c4..f06134d397 100644 --- a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs @@ -1,72 +1,38 @@ using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Builders { - public class LinkBuilder + public class LinkBuilder : ILinkBuilder { - private readonly IJsonApiContext _context; + private IRequestManager _requestManager; + private IJsonApiOptions _options; - public LinkBuilder(IJsonApiContext context) + public LinkBuilder(IJsonApiOptions options, IRequestManager requestManager) { - _context = context; + _requestManager = requestManager; + _options = options; } - public string GetBasePath(HttpContext context, string entityName) - { - var r = context.Request; - return (_context.Options.RelativeLinks) - ? GetNamespaceFromPath(r.Path, entityName) - : $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; - } - - internal static string GetNamespaceFromPath(string path, string entityName) - { - var entityNameSpan = entityName.AsSpan(); - var pathSpan = path.AsSpan(); - const char delimiter = '/'; - for (var i = 0; i < pathSpan.Length; i++) - { - if(pathSpan[i].Equals(delimiter)) - { - var nextPosition = i + 1; - if(pathSpan.Length > i + entityNameSpan.Length) - { - var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); - if (entityNameSpan.SequenceEqual(possiblePathSegment)) - { - // check to see if it's the last position in the string - // or if the next character is a / - var lastCharacterPosition = nextPosition + entityNameSpan.Length; - - if(lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) - { - return pathSpan.Slice(0, i).ToString(); - } - } - } - } - } - - return string.Empty; - } public string GetSelfRelationLink(string parent, string parentId, string child) { - return $"{_context.BasePath}/{parent}/{parentId}/relationships/{child}"; + return $"{_requestManager.BasePath}/{parent}/{parentId}/relationships/{child}"; } public string GetRelatedRelationLink(string parent, string parentId, string child) { - return $"{_context.BasePath}/{parent}/{parentId}/{child}"; + return $"{_requestManager.BasePath}/{parent}/{parentId}/{child}"; } public string GetPageLink(int pageOffset, int pageSize) { var filterQueryComposer = new QueryComposer(); - var filters = filterQueryComposer.Compose(_context); - return $"{_context.BasePath}/{_context.RequestEntity.EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; + var filters = filterQueryComposer.Compose(_requestManager); + return $"{_requestManager.BasePath}/{_requestManager.GetContextEntity().EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; } } } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index bb338b2d19..191ea1b937 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using Newtonsoft.Json; diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 738b499210..f81c034663 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using Microsoft.EntityFrameworkCore; diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index f2206667ec..2c44a5f335 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -17,7 +18,7 @@ public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService - ) : base(jsonApiOptions, jsonApiContext, resourceService) { } + ) : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) { } public BaseJsonApiController( IJsonApiOptions jsonApiOptions, @@ -26,6 +27,7 @@ public BaseJsonApiController( IResourceCmdService cmdService = null ) : base(jsonApiOptions, jsonApiContext, queryService, cmdService) { } + public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, @@ -53,15 +55,15 @@ public class BaseJsonApiController private readonly IUpdateRelationshipService _updateRelationships; private readonly IDeleteService _delete; private readonly IJsonApiOptions _jsonApiOptions; - private readonly IJsonApiContext _jsonApiContext; + private readonly IResourceGraph _resourceGraph; public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraphManager, IResourceService resourceService) { _jsonApiOptions = jsonApiOptions; - _jsonApiContext = jsonApiContext.ApplyContext(this); + _resourceGraph = resourceGraphManager; _getAll = resourceService; _getById = resourceService; _getRelationship = resourceService; @@ -79,7 +81,6 @@ public BaseJsonApiController( IResourceCmdService cmdService = null) { _jsonApiOptions = jsonApiOptions; - _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = queryService; _getById = queryService; _getRelationship = queryService; @@ -103,7 +104,6 @@ public BaseJsonApiController( IDeleteService delete = null) { _jsonApiOptions = jsonApiOptions; - _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = getAll; _getById = getById; _getRelationship = getRelationship; @@ -165,7 +165,7 @@ public virtual async Task PostAsync([FromBody] T entity) if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) { - return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _resourceGraph)); } entity = await _create.CreateAsync(entity); @@ -176,13 +176,12 @@ public virtual async Task PostAsync([FromBody] T entity) public virtual async Task PatchAsync(TId id, [FromBody] T entity) { if (_update == null) throw Exceptions.UnSupportedRequestMethod; - if (entity == null) return UnprocessableEntity(); if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) { - return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _resourceGraph)); } var updatedEntity = await _update.UpdateAsync(id, entity); @@ -196,21 +195,16 @@ public virtual async Task PatchAsync(TId id, [FromBody] T entity) public virtual async Task PatchRelationshipsAsync(TId id, string relationshipName, [FromBody] List relationships) { if (_updateRelationships == null) throw Exceptions.UnSupportedRequestMethod; - await _updateRelationships.UpdateRelationshipsAsync(id, relationshipName, relationships); - return Ok(); } public virtual async Task DeleteAsync(TId id) { if (_delete == null) throw Exceptions.UnSupportedRequestMethod; - var wasDeleted = await _delete.DeleteAsync(id); - if (!wasDeleted) return NotFound(); - return NoContent(); } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index b3d69060a0..eed4f9bd1a 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -14,7 +14,8 @@ public JsonApiCmdController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, + jsonApiContext, resourceService) { } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 4569048d6d..0427f5a678 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -43,20 +43,23 @@ public JsonApiController( IDeleteService delete = null ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } - public JsonApiController(IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) + public JsonApiController(IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService resourceService, + ILoggerFactory loggerFactory) : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { - } + } } - public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable + public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService) + ILoggerFactory loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) { } public JsonApiController( @@ -99,7 +102,9 @@ public override async Task PostAsync([FromBody] T entity) [HttpPatch("{id}")] public override async Task PatchAsync(TId id, [FromBody] T entity) - => await base.PatchAsync(id, entity); + { + return await base.PatchAsync(id, entity); + } [HttpPatch("{id}/relationships/{relationshipName}")] public override async Task PatchRelationshipsAsync( diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 9a1c94e4fc..2a60acff21 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -82,8 +82,8 @@ public DefaultEntityRepository( public virtual IQueryable Get() { - if (_jsonApiContext.QuerySet?.Fields != null && _jsonApiContext.QuerySet.Fields.Count > 0) - return _dbSet.Select(_jsonApiContext.QuerySet?.Fields); + if (_jsonApiContext.RequestManager.QuerySet?.Fields != null && _jsonApiContext.RequestManager.QuerySet.Fields.Count > 0) + return _dbSet.Select(_jsonApiContext.RequestManager.QuerySet?.Fields); return _dbSet; } @@ -140,7 +140,7 @@ public virtual IQueryable Sort(IQueryable entities, List public virtual async Task GetAsync(TId id) { - return await Select(GetQueryable(), _jsonApiContext.QuerySet?.Fields).SingleOrDefaultAsync(e => e.Id.Equals(id)); + return await Select(GetQueryable(), _jsonApiContext.RequestManager.QuerySet?.Fields).SingleOrDefaultAsync(e => e.Id.Equals(id)); } /// @@ -148,7 +148,7 @@ public virtual async Task GetAndIncludeAsync(TId id, string relationshi { _logger?.LogDebug($"[JADN] GetAndIncludeAsync({id}, {relationshipName})"); - var includedSet = Include(Select(GetQueryable(), _jsonApiContext.QuerySet?.Fields), relationshipName); + var includedSet = Include(Select(GetQueryable(), _jsonApiContext.RequestManager.QuerySet?.Fields), relationshipName); var result = await includedSet.SingleOrDefaultAsync(e => e.Id.Equals(id)); return result; diff --git a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs index 20cee086d6..6fef3b44d1 100644 --- a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs @@ -1,6 +1,7 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Middleware; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 3268278d0e..57674cf0ca 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -161,7 +161,8 @@ public static void AddJsonApiInternals( services.AddScoped(); // services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } private static void AddOperationServices(IServiceCollection services) diff --git a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs index 05ce8e6238..c756570552 100644 --- a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs @@ -1,5 +1,6 @@ using System; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore.Internal; diff --git a/src/JsonApiDotNetCore/Internal/ContextGraph.cs b/src/JsonApiDotNetCore/Internal/ContextGraph.cs index 18aab6646e..e3656943b4 100644 --- a/src/JsonApiDotNetCore/Internal/ContextGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ContextGraph.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs new file mode 100644 index 0000000000..54febfa45b --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -0,0 +1,87 @@ +using JsonApiDotNetCore.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Internal.Contracts +{ + /// + /// A cache for the models in entity core + /// + public interface IResourceGraph + { + /// + /// 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); + + /// + /// Get the entity type based on a string + /// + /// + /// The context entity from the resource graph + ContextEntity GetEntityType(string entityName); + + /// + /// 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 resource metadata by the DbSet property name + /// + ContextEntity GetContextEntity(string dbSetName); + + /// + /// Get the resource metadata by the resource type + /// + ContextEntity GetContextEntity(Type entityType); + + /// + /// 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; } + } +} diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index e486731052..28c13fd885 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -7,6 +7,12 @@ namespace JsonApiDotNetCore.Internal { public class PageManager : IPageManager { + private ILinkBuilder _linkBuilder; + + public PageManager(ILinkBuilder linkBuilder) + { + _linkBuilder = linkBuilder; + } public int? TotalRecords { get; set; } public int PageSize { get; set; } public int DefaultPageSize { get; set; } @@ -14,7 +20,7 @@ public class PageManager : IPageManager public bool IsPaginated => PageSize > 0; public int TotalPages => (TotalRecords == null) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords.Value, PageSize)); - public RootLinks GetPageLinks(LinkBuilder linkBuilder) + public RootLinks GetPageLinks() { if (ShouldIncludeLinksObject()) return null; @@ -22,16 +28,16 @@ public RootLinks GetPageLinks(LinkBuilder linkBuilder) var rootLinks = new RootLinks(); if (CurrentPage > 1) - rootLinks.First = linkBuilder.GetPageLink(1, PageSize); + rootLinks.First = _linkBuilder.GetPageLink(1, PageSize); if (CurrentPage > 1) - rootLinks.Prev = linkBuilder.GetPageLink(CurrentPage - 1, PageSize); + rootLinks.Prev = _linkBuilder.GetPageLink(CurrentPage - 1, PageSize); if (CurrentPage < TotalPages) - rootLinks.Next = linkBuilder.GetPageLink(CurrentPage + 1, PageSize); + rootLinks.Next = _linkBuilder.GetPageLink(CurrentPage + 1, PageSize); if (TotalPages > 0) - rootLinks.Last = linkBuilder.GetPageLink(TotalPages, PageSize); + rootLinks.Last = _linkBuilder.GetPageLink(TotalPages, PageSize); return rootLinks; } diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index a7afe98398..e2db1b667b 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -2,80 +2,14 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal { - public interface IResourceGraph - { - /// - /// 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 resource metadata by the DbSet property name - /// - ContextEntity GetContextEntity(string dbSetName); - - /// - /// Get the resource metadata by the resource type - /// - ContextEntity GetContextEntity(Type entityType); - - /// - /// 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; } - } - + /// + /// keeps track of all the models/resources defined in JADNC + /// public class ResourceGraph : IResourceGraph { internal List Entities { get; } @@ -91,6 +25,11 @@ public ResourceGraph(List entities, bool usesDbContext) Instance = this; } + public ContextEntity GetEntityType(string entityName) + { + return Entities.Where(e => e.EntityName == entityName).FirstOrDefault(); + } + // 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 @@ -177,5 +116,7 @@ public string GetPublicAttributeName(string internalAttributeName) .SingleOrDefault(a => a.InternalAttributeName == internalAttributeName)? .PublicAttributeName; } + + } } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs index f7c7c9012c..814cdc35a5 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs @@ -1,4 +1,6 @@ +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models; using System; using System.Collections.Generic; using System.Text; @@ -7,5 +9,27 @@ namespace JsonApiDotNetCore.Managers.Contracts { public interface IPageManager { + /// + /// What the total records are for this output + /// + int? TotalRecords { get; set; } + /// + /// How many records per page should be shown + /// + int PageSize { get; set; } + /// + /// What is the default page size + /// + int DefaultPageSize { get; set; } + /// + /// What page are we currently on + /// + int CurrentPage { get; set; } + /// + /// Are we even paginating + /// + bool IsPaginated { get; } + + RootLinks GetPageLinks(); } } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs deleted file mode 100644 index d148127dcd..0000000000 --- a/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace JsonApiDotNetCore.Managers.Contracts -{ - public interface IQueryManager - { - /// - /// Gets the relationships as set in the query parameters - /// - /// - List GetRelationships(); - /// - /// Gets the sparse fields - /// - /// - List GetFields(); - } -} diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs new file mode 100644 index 0000000000..3f8f0b8e52 --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Services; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IRequestManager : IQueryRequest + { + /// + /// The request namespace. This may be an absolute or relative path + /// depending upon the configuration. + /// + /// + /// Absolute: https://example.com/api/v1 + /// + /// Relative: /api/v1 + /// + string BasePath { get; set; } + QuerySet QuerySet { get; set; } + /// + /// Gets the relationships as set in the query parameters + /// + /// + List GetRelationships(); + /// + /// Gets the sparse fields + /// + /// + List GetFields(); + /// + /// Sets the current context entity for this entire request + /// + /// + void SetContextEntity(ContextEntity contextEntityCurrent); + + ContextEntity GetContextEntity(); + } +} diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs new file mode 100644 index 0000000000..3a58f3e2b2 --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs @@ -0,0 +1,11 @@ +using JsonApiDotNetCore.Internal; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IResourceGraphManager + { + } +} diff --git a/src/JsonApiDotNetCore/Managers/QueryManager.cs b/src/JsonApiDotNetCore/Managers/QueryManager.cs deleted file mode 100644 index 3e4dbdc66a..0000000000 --- a/src/JsonApiDotNetCore/Managers/QueryManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Services; -using System; -using System.Collections.Generic; -using System.Text; - -namespace JsonApiDotNetCore.Managers -{ - class QueryManager : IQueryManager - { - private IJsonApiContext _jsonApiContext; - - public QueryManager(IJsonApiContext jsonApiContext) - { - _jsonApiContext = jsonApiContext; - } - - public List GetFields() - { - return _jsonApiContext.QuerySet?.Fields; - } - - public List GetRelationships() - { - return _jsonApiContext.QuerySet?.IncludedRelationships; - } - } -} diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs new file mode 100644 index 0000000000..857b97c64f --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -0,0 +1,41 @@ +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Services; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers +{ + class RequestManager : IRequestManager + { + + private ContextEntity _contextEntity; + + public string BasePath { get; set; } + public List IncludedRelationships { get; set; } + public QuerySet QuerySet { get; set; } + public PageManager PageManager { get; set; } + + + public List GetFields() + { + return QuerySet?.Fields; + } + + public List GetRelationships() + { + return QuerySet?.IncludedRelationships; + } + public ContextEntity GetContextEntity() + { + return _contextEntity; + } + + public void SetContextEntity(ContextEntity contextEntityCurrent) + { + _contextEntity = contextEntityCurrent; + } + } +} diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index ab472a8dba..b148692e41 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -1,6 +1,10 @@ using System; using System.Threading.Tasks; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -16,7 +20,7 @@ public RequestMiddleware(RequestDelegate next) _next = next; } - public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext) + public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options ) { if (IsValid(context)) { @@ -25,10 +29,67 @@ public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext) // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); + ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph); + requestManager.SetContextEntity(contextEntityCurrent); + requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); await _next(context); } } + private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) + { + var r = context.Request; + if (options.RelativeLinks) + { + return GetNamespaceFromPath(r.Path, entityName); + } + else + { + return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; + } + } + internal static string GetNamespaceFromPath(string path, string entityName) + { + var entityNameSpan = entityName.AsSpan(); + var pathSpan = path.AsSpan(); + const char delimiter = '/'; + for (var i = 0; i < pathSpan.Length; i++) + { + if (pathSpan[i].Equals(delimiter)) + { + var nextPosition = i + 1; + if (pathSpan.Length > i + entityNameSpan.Length) + { + var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); + if (entityNameSpan.SequenceEqual(possiblePathSegment)) + { + // check to see if it's the last position in the string + // or if the next character is a / + var lastCharacterPosition = nextPosition + entityNameSpan.Length; + + if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) + { + return pathSpan.Slice(0, i).ToString(); + } + } + } + } + } + + return string.Empty; + } + /// + /// Gets the current entity that we need for serialization and deserialization. + /// + /// + /// + /// + private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph) + { + var typeString = path.ToString().Split('/')[1]; + return resourceGraph.GetEntityType(typeString); + } + private static bool IsValid(HttpContext context) { return IsValidContentTypeHeader(context) && IsValidAcceptHeader(context); diff --git a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs index 77461aba42..15a417376f 100644 --- a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs @@ -1,4 +1,5 @@ using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Query; using System; using System.Collections.Generic; diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index a784554f58..2729e1ec94 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.Extensions.Logging; @@ -12,6 +13,7 @@ public class JsonApiSerializer : IJsonApiSerializer { private readonly IDocumentBuilder _documentBuilder; private readonly ILogger _logger; + private readonly IRequestManager _requestManager; private readonly IJsonApiContext _jsonApiContext; public JsonApiSerializer( @@ -24,9 +26,11 @@ public JsonApiSerializer( public JsonApiSerializer( IJsonApiContext jsonApiContext, + IRequestManager requestManager, IDocumentBuilder documentBuilder, ILoggerFactory loggerFactory) { + _requestManager = requestManager; _jsonApiContext = jsonApiContext; _documentBuilder = documentBuilder; _logger = loggerFactory?.CreateLogger(); @@ -37,7 +41,7 @@ public string Serialize(object entity) if (entity == null) return GetNullDataResponse(); - if (entity.GetType() == typeof(ErrorCollection) || (_jsonApiContext.RequestEntity == null && _jsonApiContext.IsBulkOperationRequest == false)) + if (entity.GetType() == typeof(ErrorCollection) || (_requestManager.GetContextEntity()== null && _jsonApiContext.IsBulkOperationRequest == false)) return GetErrorJson(entity, _logger); if (_jsonApiContext.IsBulkOperationRequest) diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 19a12370c9..78f1539520 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -20,7 +20,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions options, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory = null) : base(jsonApiContext, entityRepository, options, queryManager, pageManager, loggerFactory) @@ -35,7 +35,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory = null) : base(jsonApiContext, entityRepository, apiOptions, queryManager, pageManager, loggerFactory) @@ -48,7 +48,7 @@ public class EntityResourceService : where TEntity : class, IIdentifiable { private readonly IPageManager _pageManager; - private readonly IQueryManager _queryManager; + private readonly IRequestManager _queryManager; private readonly IJsonApiContext _jsonApiContext; private readonly IJsonApiOptions _options; private readonly IEntityRepository _repository; @@ -59,7 +59,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, null, queryManager, pageManager, loggerFactory ) { @@ -75,7 +75,7 @@ public EntityResourceService( IEntityRepository entityRepository, IJsonApiOptions options, IResourceMapper mapper, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory) { @@ -131,15 +131,15 @@ public virtual async Task> GetAsync() if (AreRelationshipsIncluded()) { - entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); + entities = IncludeRelationships(entities, _jsonApiContext.RequestManager.QuerySet.IncludedRelationships); } if (_options.IncludeTotalRecordCount) { - _jsonApiContext.PageManager.TotalRecords = await _repository.CountAsync(entities); + _pageManager.TotalRecords = await _repository.CountAsync(entities); } - entities = _repository.Select(entities, _jsonApiContext.QuerySet?.Fields); + entities = _repository.Select(entities, _jsonApiContext.RequestManager.QuerySet?.Fields); // pagination should be done last since it will execute the query var pagedEntities = await ApplyPageQueryAsync(entities); @@ -235,8 +235,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa protected virtual async Task> ApplyPageQueryAsync(IQueryable entities) { - var pageManager = _jsonApiContext.PageManager; - if (!pageManager.IsPaginated) + if (!_pageManager.IsPaginated) { var allEntities = await _repository.ToListAsync(entities); return (typeof(TResource) == typeof(TEntity)) ? allEntities as IEnumerable : @@ -245,20 +244,20 @@ protected virtual async Task> ApplyPageQueryAsync(IQuerya if (_logger?.IsEnabled(LogLevel.Information) == true) { - _logger?.LogInformation($"Applying paging query. Fetching page {pageManager.CurrentPage} " + - $"with {pageManager.PageSize} entities"); + _logger?.LogInformation($"Applying paging query. Fetching page {_pageManager.CurrentPage} " + + $"with {_pageManager.PageSize} entities"); } - var pagedEntities = await _repository.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage); + var pagedEntities = await _repository.PageAsync(entities, _pageManager.PageSize, _pageManager.CurrentPage); return MapOut(pagedEntities); } protected virtual IQueryable ApplySortAndFilterQuery(IQueryable entities) { - var query = _jsonApiContext.QuerySet; + var query = _jsonApiContext.RequestManager.QuerySet; - if (_jsonApiContext.QuerySet == null) + if (_jsonApiContext.RequestManager.QuerySet == null) return entities; if (query.Filters.Count > 0) @@ -278,7 +277,7 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable protected virtual IQueryable IncludeRelationships(IQueryable entities, List relationships) { - _jsonApiContext.IncludedRelationships = relationships; + _jsonApiContext.RequestManager.IncludedRelationships = relationships; foreach (var r in relationships) { diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 94e13cb243..7d1304e9b3 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -3,8 +3,10 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Request; @@ -38,18 +40,9 @@ public interface IUpdateRequest Dictionary RelationshipsToUpdate { get; set; } } - public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRequest + public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest { - /// - /// The request namespace. This may be an absolute or relative path - /// depending upon the configuration. - /// - /// - /// Absolute: https://example.com/api/v1 - /// - /// Relative: /api/v1 - /// - string BasePath { get; set; } + /// /// Stores information to set relationships for the request resource. @@ -146,6 +139,8 @@ public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRe public interface IJsonApiContext : IJsonApiRequest { + IRequestManager RequestManager { get; set; } + IPageManager PageManager { get; set; } IJsonApiContext ApplyContext(object controller); IMetaBuilder MetaBuilder { get; set; } IGenericProcessorFactory GenericProcessorFactory { get; set; } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 33e3be2093..faf65f9397 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -3,8 +3,10 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Request; using Microsoft.AspNetCore.Http; @@ -24,8 +26,12 @@ public JsonApiContext( IMetaBuilder metaBuilder, IGenericProcessorFactory genericProcessorFactory, IQueryParser queryParser, + IPageManager pageManager, + IRequestManager requestManager, IControllerContext controllerContext) { + RequestManager = requestManager; + PageManager = pageManager; ResourceGraph = resourceGraph; _httpContextAccessor = httpContextAccessor; Options = options; @@ -39,12 +45,12 @@ public JsonApiContext( public IResourceGraph ResourceGraph { get; set; } [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } - public string BasePath { get; set; } + public QuerySet QuerySet { get; set; } public bool IsRelationshipData { get; set; } public bool IsRelationshipPath { get; private set; } public List IncludedRelationships { get; set; } - public PageManager PageManager { get; set; } + public IPageManager PageManager { get; set; } public IMetaBuilder MetaBuilder { get; set; } public IGenericProcessorFactory GenericProcessorFactory { get; set; } public Type ControllerType { get; set; } @@ -55,6 +61,8 @@ public JsonApiContext( public Dictionary RelationshipsToUpdate { get; set; } = new Dictionary(); public HasManyRelationshipPointers HasManyRelationshipPointers { get; private set; } = new HasManyRelationshipPointers(); public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers(); + public IRequestManager RequestManager { get; set; } + IPageManager IJsonApiContext.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public IJsonApiContext ApplyContext(object controller) { @@ -74,8 +82,7 @@ public IJsonApiContext ApplyContext(object controller) IncludedRelationships = QuerySet.IncludedRelationships; } - BasePath = new LinkBuilder(this).GetBasePath(context, _controllerContext.RequestEntity.EntityName); - PageManager = GetPageManager(); + //PageManager = GetPageManager(); IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); return this; @@ -115,20 +122,22 @@ internal static bool PathIsRelationship(string requestPath) return false; } - private PageManager GetPageManager() - { - if (Options.DefaultPageSize == 0 && (QuerySet == null || QuerySet.PageQuery.PageSize == 0)) - return new PageManager(); - - var query = QuerySet?.PageQuery ?? new PageQuery(); - - return new PageManager - { - DefaultPageSize = Options.DefaultPageSize, - CurrentPage = query.PageOffset, - PageSize = query.PageSize > 0 ? query.PageSize : Options.DefaultPageSize - }; - } + //private PageManager GetPageManager() + //{ + // if (Options.DefaultPageSize == 0 && (QuerySet == null || QuerySet.PageQuery.PageSize == 0)) + // { + // return new PageManager(); + // } + + // var query = QuerySet?.PageQuery ?? new PageQuery(); + + // return new PageManager + // { + // DefaultPageSize = Options.DefaultPageSize, + // CurrentPage = query.PageOffset, + // PageSize = query.PageSize > 0 ? query.PageSize : Options.DefaultPageSize + // }; + //} [Obsolete("Use the proxied method IControllerContext.GetControllerAttribute instead.")] public TAttribute GetControllerAttribute() where TAttribute : Attribute diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs index ebc600a5fb..bd4e89eb84 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs index 0551b0a3cd..66f4306fa7 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs index f3efe7939c..70ddae2aea 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs index 9b136b30c0..7969b6f3ed 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/QueryAccessor.cs b/src/JsonApiDotNetCore/Services/QueryAccessor.cs index dc9ff7ef0a..260c379968 100644 --- a/src/JsonApiDotNetCore/Services/QueryAccessor.cs +++ b/src/JsonApiDotNetCore/Services/QueryAccessor.cs @@ -71,13 +71,13 @@ public bool TryGetValue(string key, out T value) } private string GetFilterValue(string key) { - var publicValue = _jsonApiContext.QuerySet.Filters + var publicValue = _jsonApiContext.RequestManager.QuerySet.Filters .FirstOrDefault(f => string.Equals(f.Attribute, key, StringComparison.OrdinalIgnoreCase))?.Value; if(publicValue != null) return publicValue; - var internalValue = _jsonApiContext.QuerySet.Filters + var internalValue = _jsonApiContext.RequestManager.QuerySet.Filters .FirstOrDefault(f => string.Equals(f.Attribute, key, StringComparison.OrdinalIgnoreCase))?.Value; if(internalValue != null) { diff --git a/src/JsonApiDotNetCore/Services/QueryComposer.cs b/src/JsonApiDotNetCore/Services/QueryComposer.cs index e365811704..b96b83718a 100644 --- a/src/JsonApiDotNetCore/Services/QueryComposer.cs +++ b/src/JsonApiDotNetCore/Services/QueryComposer.cs @@ -1,21 +1,22 @@ using System.Collections.Generic; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; namespace JsonApiDotNetCore.Services { public interface IQueryComposer { - string Compose(IJsonApiContext jsonApiContext); + string Compose(IRequestManager jsonApiContext); } public class QueryComposer : IQueryComposer { - public string Compose(IJsonApiContext jsonApiContext) + public string Compose(IRequestManager requestManager) { string result = ""; - if (jsonApiContext != null && jsonApiContext.QuerySet != null) + if (requestManager != null && requestManager.QuerySet != null) { - List filterQueries = jsonApiContext.QuerySet.Filters; + List filterQueries = requestManager.QuerySet.Filters; if (filterQueries.Count > 0) { foreach (FilterQuery filter in filterQueries) diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index dc51ef806e..1f8acbbdb3 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -77,7 +77,7 @@ public class TestModelService : EntityResourceService private static IEntityRepository _repo = new Mock>().Object; private static IJsonApiContext _jsonApiContext = new Mock().Object; private static IJsonApiOptions _jsonApiOptions = new Mock().Object; - private static IQueryManager _queryManager = new Mock().Object; + private static IRequestManager _queryManager = new Mock().Object; private static IPageManager _pageManager = new Mock().Object; public TestModelService() : base(_jsonApiContext, _repo, _jsonApiOptions, _queryManager, _pageManager) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs index 533c59839a..1bf2bfa204 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs @@ -52,8 +52,7 @@ public async Task Can_Get_CamelCasedModels() // Act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService() - .DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -151,8 +150,7 @@ public async Task Can_Patch_CamelCasedModels() }; var httpMethod = new HttpMethod("PATCH"); var route = $"/camelCasedModels/{model.Id}"; - var builder = new WebHostBuilder() - .UseStartup(); + var builder = new WebHostBuilder().UseStartup(); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(httpMethod, route); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs index fcc6e5ffde..39fc11178d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs @@ -27,7 +27,7 @@ public void Can_Return_Custom_Error_Types() }); // act - var result = new JsonApiSerializer(null, null, null) + var result = new JsonApiSerializer(null, null, null,null) .Serialize(errorCollection); // assert diff --git a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs index a88ecceb67..d0055a9530 100644 --- a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs +++ b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/test/UnitTests/Builders/DocumentBuilder_Tests.cs b/test/UnitTests/Builders/DocumentBuilder_Tests.cs index 459a8a758d..ddff141b8a 100644 --- a/test/UnitTests/Builders/DocumentBuilder_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilder_Tests.cs @@ -1,8 +1,10 @@ +using System; using System.Collections; using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +16,7 @@ namespace UnitTests public class DocumentBuilder_Tests { private readonly Mock _jsonApiContextMock; - private readonly PageManager _pageManager; + private readonly IPageManager _pageManager; private readonly JsonApiOptions _options; private readonly Mock _requestMetaMock; @@ -43,14 +45,12 @@ public DocumentBuilder_Tests() .Setup(m => m.MetaBuilder) .Returns(new MetaBuilder()); - _pageManager = new PageManager(); + _pageManager = new Mock().Object; _jsonApiContextMock .Setup(m => m.PageManager) .Returns(_pageManager); - _jsonApiContextMock - .Setup(m => m.BasePath) - .Returns("localhost"); + _jsonApiContextMock .Setup(m => m.RequestEntity) @@ -65,7 +65,8 @@ public void Includes_Paging_Links_By_Default() _pageManager.TotalRecords = 1; _pageManager.CurrentPage = 1; - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + + var documentBuilder = GetDocumentBuilder(); var entity = new Model(); // act @@ -76,6 +77,8 @@ public void Includes_Paging_Links_By_Default() Assert.NotNull(document.Links.Last); } + + [Fact] public void Page_Links_Can_Be_Disabled_Globally() { @@ -90,7 +93,7 @@ public void Page_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model(); // act @@ -112,7 +115,7 @@ public void Related_Links_Can_Be_Disabled() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model(); // act @@ -136,7 +139,7 @@ public void Related_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new RelatedModel(); // act @@ -157,7 +160,7 @@ public void Related_Data_Included_In_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model { RelatedModel = new RelatedModel @@ -189,7 +192,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model { RelatedModelId = relatedId @@ -211,7 +214,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() public void Build_Can_Build_Arrays() { var entities = new[] { new Model() }; - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var documents = documentBuilder.Build(entities); @@ -222,7 +225,7 @@ public void Build_Can_Build_Arrays() public void Build_Can_Build_CustomIEnumerables() { var entities = new Models(new[] { new Model() }); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var documents = documentBuilder.Build(entities); @@ -247,7 +250,8 @@ public void DocumentBuilderOptions( documentBuilderBehaviourMock.Setup(m => m.GetDocumentBuilderOptions()) .Returns(new DocumentBuilderOptions(omitNullValuedAttributes.Value)); } - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, null, omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); + var pageManagerMock = new Mock(); + var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, null, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); var document = documentBuilder.Build(new Model() { StringProperty = attributeValue }); Assert.Equal(resultContainsAttribute, document.Data.Attributes.ContainsKey("StringProperty")); @@ -302,7 +306,7 @@ public void Build_Will_Use_Resource_If_Defined_For_Multiple_Documents() .AddScoped, UserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entities); @@ -325,7 +329,7 @@ public void Build_Will_Use_Resource_If_Defined_For_Single_Document() .AddScoped, UserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entity); @@ -347,7 +351,7 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Multiple_Do .AddScoped, InstanceSpecificUserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entities); @@ -370,10 +374,10 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Single_Docu .AddScoped, InstanceSpecificUserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entity); - + Assert.False(documents.Data.Attributes.ContainsKey("password")); Assert.True(documents.Data.Attributes.ContainsKey("username")); } @@ -395,5 +399,20 @@ public class UserResource : ResourceDefinition protected override List OutputAttrs() => Remove(user => user.Password); } + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock = null, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + var rmMock = new Mock(); + rmMock.SetupGet(rm => rm.BasePath).Returns("Localhost"); + if (jaContextMock != null) + { + return new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + + } + else + { + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + } + } } } diff --git a/test/UnitTests/Builders/LinkBuilder_Tests.cs b/test/UnitTests/Builders/LinkBuilder_Tests.cs index 69b135de03..1e9fdddda3 100644 --- a/test/UnitTests/Builders/LinkBuilder_Tests.cs +++ b/test/UnitTests/Builders/LinkBuilder_Tests.cs @@ -21,28 +21,29 @@ public void GetBasePath_Returns_Path_Before_Resource(string scheme, string host, string path, bool isRelative, string expectedPath) { // arrange - const string resource = "articles"; - var jsonApiContextMock = new Mock(); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions - { - RelativeLinks = isRelative - }); + //const string resource = "articles"; + //var jsonApiContextMock = new Mock(); + //jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions + //{ + // RelativeLinks = isRelative + //}); - var requestMock = new Mock(); - requestMock.Setup(m => m.Scheme).Returns(scheme); - requestMock.Setup(m => m.Host).Returns(new HostString(host)); - requestMock.Setup(m => m.Path).Returns(new PathString(path)); + //var requestMock = new Mock(); + //requestMock.Setup(m => m.Scheme).Returns(scheme); + //requestMock.Setup(m => m.Host).Returns(new HostString(host)); + //requestMock.Setup(m => m.Path).Returns(new PathString(path)); - var contextMock = new Mock(); - contextMock.Setup(m => m.Request).Returns(requestMock.Object); + //var contextMock = new Mock(); + //contextMock.Setup(m => m.Request).Returns(requestMock.Object); - var linkBuilder = new LinkBuilder(jsonApiContextMock.Object); + //var linkBuilder = new LinkBuilder(jsonApiContextMock.Object); - // act - var basePath = linkBuilder.GetBasePath(contextMock.Object, resource); + //// act + //var basePath = linkBuilder.GetBasePath(contextMock.Object, resource); - // assert - Assert.Equal(expectedPath, basePath); + //// assert + //Assert.Equal(expectedPath, basePath); + Assert.False(true); } } } diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index f0d7184cb1..6fe8638eca 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using JsonApiDotNetCore.Internal.Contracts; namespace UnitTests { @@ -337,7 +338,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, _jsonApiContextMock.Object, delete: null); + var controller = new BaseJsonApiController(new Mock().Object, + + _jsonApiContextMock.Object, delete: null); // act var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index 3b49dda9bf..41d546faf6 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Internal.Contracts; namespace UnitTests.Extensions { diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index 08c41bd4da..3d587a3065 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -41,7 +41,7 @@ public async Task GetAsync_Throw404OnNoEntityFound() IncludeTotalRecordCount = false } as IJsonApiOptions; var repositoryMock = new Mock>(); - var queryManagerMock = new Mock(); + var queryManagerMock = new Mock(); var pageManagerMock = new Mock(); var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); @@ -69,7 +69,7 @@ public async Task GetAsync_ShouldThrow404OnNoEntityFoundWithRelationships() IncludeTotalRecordCount = false } as IJsonApiOptions; var repositoryMock = new Mock>(); - var queryManagerMock = new Mock(); + var queryManagerMock = new Mock(); var pageManagerMock = new Mock(); queryManagerMock.Setup(qm => qm.GetRelationships()).Returns(new List() { "cookies" }); var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); diff --git a/test/UnitTests/Models/ResourceDefinitionTests.cs b/test/UnitTests/Models/ResourceDefinitionTests.cs index f058dbc946..1ae632777f 100644 --- a/test/UnitTests/Models/ResourceDefinitionTests.cs +++ b/test/UnitTests/Models/ResourceDefinitionTests.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Models; using System.Collections.Generic; diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs index c781617d03..479a59e652 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Request; using JsonApiDotNetCore.Serialization; @@ -23,7 +24,7 @@ public void Can_Serialize_Complex_Types() // arrange var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource("test-resource"); - + var serializer = GetSerializer(resourceGraphBuilder); var resource = new TestResource @@ -40,7 +41,7 @@ public void Can_Serialize_Complex_Types() // assert Assert.NotNull(result); - var expectedFormatted = + var expectedFormatted = @"{ ""data"": { ""attributes"": { @@ -61,7 +62,7 @@ public void Can_Serialize_Complex_Types() } }"; var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - + Assert.Equal(expected, result); } @@ -73,7 +74,7 @@ public void Can_Serialize_Deeply_Nested_Relationships() resourceGraphBuilder.AddResource("test-resource"); resourceGraphBuilder.AddResource("children"); resourceGraphBuilder.AddResource("infections"); - + var serializer = GetSerializer( resourceGraphBuilder, included: new List { "children.infections" } @@ -101,8 +102,8 @@ public void Can_Serialize_Deeply_Nested_Relationships() // assert Assert.NotNull(result); - - var expectedFormatted = + + var expectedFormatted = @"{ ""data"": { ""attributes"": { @@ -206,7 +207,7 @@ public void Can_Serialize_Deeply_Nested_Relationships() } private JsonApiSerializer GetSerializer( - ResourceGraphBuilder resourceGraphBuilder, + ResourceGraphBuilder resourceGraphBuilder, List included = null) { var resourceGraph = resourceGraphBuilder.Build(); @@ -221,10 +222,11 @@ private JsonApiSerializer GetSerializer( // jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); // jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager()); + var pmMock = new Mock(); + jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); + - if (included != null) - jsonApiContextMock.Setup(m => m.IncludedRelationships).Returns(included); + Assert.True(false); var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); @@ -239,7 +241,7 @@ private JsonApiSerializer GetSerializer( var provider = services.BuildServiceProvider(); var scoped = new TestScopedServiceProvider(provider); - var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object, scopedServiceProvider: scoped); + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, scopedServiceProvider: scoped); var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); return serializer; @@ -260,7 +262,7 @@ private class ComplexType private class ChildResource : Identifiable { - [HasMany("infections")] public List Infections { get; set;} + [HasMany("infections")] public List Infections { get; set; } [HasOne("parent")] public TestResource Parent { get; set; } } @@ -269,5 +271,14 @@ private class InfectionResource : Identifiable { [HasOne("infected")] public ChildResource Infected { get; set; } } + + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + var rmMock = new Mock(); + + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + + } } } diff --git a/test/UnitTests/Services/QueryAccessorTests.cs b/test/UnitTests/Services/QueryAccessorTests.cs index aa8bc6ae7e..855efa2d16 100644 --- a/test/UnitTests/Services/QueryAccessorTests.cs +++ b/test/UnitTests/Services/QueryAccessorTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; @@ -26,76 +26,80 @@ public QueryAccessorTests() [Fact] public void Can_Get_Guid_QueryValue() { - // arrange - const string key = "SomeId"; - var value = Guid.NewGuid(); - var querySet = new QuerySet - { - Filters = new List { - new FilterQuery(key, value.ToString(), "eq") - } - }; + //// arrange + //const string key = "SomeId"; + //var value = Guid.NewGuid(); + //var querySet = new QuerySet + //{ + // Filters = new List { + // new FilterQuery(key, value.ToString(), "eq") + // } + //}; + - _contextMock.Setup(c => c.QuerySet).Returns(querySet); + //_contextMock.Setup(c => c.QuerySet).Returns(querySet); - var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + //var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); - // act - var success = service.TryGetValue("SomeId", out Guid result); + //// act + //var success = service.TryGetValue("SomeId", out Guid result); - // assert - Assert.True(success); - Assert.Equal(value, result); + //// assert + //Assert.True(success); + //Assert.Equal(value, result); + Assert.True(false); } [Fact] public void GetRequired_Throws_If_Not_Present() { // arrange - const string key = "SomeId"; - var value = Guid.NewGuid(); + //const string key = "SomeId"; + //var value = Guid.NewGuid(); - var querySet = new QuerySet - { - Filters = new List { - new FilterQuery(key, value.ToString(), "eq") - } - }; + //var querySet = new QuerySet + //{ + // Filters = new List { + // new FilterQuery(key, value.ToString(), "eq") + // } + //}; - _contextMock.Setup(c => c.QuerySet).Returns(querySet); + //_contextMock.Setup(c => c.QuerySet).Returns(querySet); - var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + //var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); - // act - var exception = Assert.Throws(() => service.GetRequired("Invalid")); + //// act + //var exception = Assert.Throws(() => service.GetRequired("Invalid")); - // assert - Assert.Equal(422, exception.GetStatusCode()); + //// assert + //Assert.Equal(422, exception.GetStatusCode()); + Assert.True(false); } [Fact] public void GetRequired_Does_Not_Throw_If_Present() { // arrange - const string key = "SomeId"; - var value = Guid.NewGuid(); + //const string key = "SomeId"; + //var value = Guid.NewGuid(); - var querySet = new QuerySet - { - Filters = new List { - new FilterQuery(key, value.ToString(), "eq") - } - }; + //var querySet = new QuerySet + //{ + // Filters = new List { + // new FilterQuery(key, value.ToString(), "eq") + // } + //}; - _contextMock.Setup(c => c.QuerySet).Returns(querySet); + //_contextMock.Setup(c => c.QuerySet).Returns(querySet); - var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + //var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); - // act - var result = service.GetRequired("SomeId"); + //// act + //var result = service.GetRequired("SomeId"); - // assert - Assert.Equal(value, result); + //// assert + //Assert.Equal(value, result); + Assert.True(false); } } } diff --git a/test/UnitTests/Services/QueryComposerTests.cs b/test/UnitTests/Services/QueryComposerTests.cs index efa600f2f3..607c321b6c 100644 --- a/test/UnitTests/Services/QueryComposerTests.cs +++ b/test/UnitTests/Services/QueryComposerTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Moq; using Xunit; @@ -25,13 +26,14 @@ public void Can_ComposeEqual_FilterStringForUrl() filters.Add(filter); querySet.Filters = filters; - _jsonApiContext + var rmMock = new Mock(); + rmMock .Setup(m => m.QuerySet) .Returns(querySet); var queryComposer = new QueryComposer(); // act - var filterString = queryComposer.Compose(_jsonApiContext.Object); + var filterString = queryComposer.Compose(rmMock.Object); // assert Assert.Equal("&filter[attribute]=eq:value", filterString); } @@ -47,14 +49,15 @@ public void Can_ComposeLessThan_FilterStringForUrl() filters.Add(filter); filters.Add(filter2); querySet.Filters = filters; - - _jsonApiContext + var rmMock = new Mock(); + rmMock .Setup(m => m.QuerySet) .Returns(querySet); + var queryComposer = new QueryComposer(); // act - var filterString = queryComposer.Compose(_jsonApiContext.Object); + var filterString = queryComposer.Compose(rmMock.Object); // assert Assert.Equal("&filter[attribute]=le:value&filter[attribute2]=value2", filterString); } @@ -65,13 +68,15 @@ public void NoFilter_Compose_EmptyStringReturned() // arrange var querySet = new QuerySet(); - _jsonApiContext + var rmMock = new Mock(); + rmMock .Setup(m => m.QuerySet) .Returns(querySet); var queryComposer = new QueryComposer(); - // act - var filterString = queryComposer.Compose(_jsonApiContext.Object); + // Act + + var filterString = queryComposer.Compose(rmMock.Object); // assert Assert.Equal("", filterString); } From 77a2ae9e0caf33eb258c6ceeb80204e0f3eda10b Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 24 May 2019 11:28:26 +0200 Subject: [PATCH 07/10] feat: removed JsonApiContext dependency from controller, fixed namespaced tests --- .../Controllers/ArticlesController.cs | 5 +- .../Controllers/CamelCasedModelsController.cs | 5 +- .../Controllers/PeopleController.cs | 5 +- .../Controllers/PersonRolesController.cs | 5 +- .../Controllers/TodoCollectionsController.cs | 5 +- .../Controllers/TodoItemsController.cs | 5 +- .../Controllers/TodoItemsTestController.cs | 9 ++-- .../Controllers/UsersController.cs | 5 +- .../Controllers/CustomTodoItemsController.cs | 5 +- .../Builders/DocumentBuilder.cs | 46 ++++++++++--------- .../Configuration/IJsonApiOptions.cs | 1 + .../Controllers/BaseJsonApiController.cs | 22 ++++++--- .../Controllers/JsonApiCmdController.cs | 2 +- .../Controllers/JsonApiController.cs | 42 +++++------------ .../Controllers/JsonApiQueryController.cs | 2 +- .../{RequestManager.cs => QueryManager.cs} | 0 .../Middleware/RequestMiddleware.cs | 10 ++-- .../Serialization/JsonApiDeSerializer.cs | 20 +++++--- 18 files changed, 102 insertions(+), 92 deletions(-) rename src/JsonApiDotNetCore/Managers/{RequestManager.cs => QueryManager.cs} (100%) diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs index 865f8454a2..abf6ce50ea 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -9,9 +10,9 @@ public class ArticlesController : JsonApiController
{ public ArticlesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService
resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs index cf9c8bdf2e..c46a9aa094 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Mvc; @@ -13,10 +14,10 @@ public class CamelCasedModelsController : JsonApiController { public CamelCasedModelsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs index 236c946f89..d29cafe508 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class PeopleController : JsonApiController { public PeopleController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs index e00c5b906e..0234eb6899 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class PersonRolesController : JsonApiController { public PersonRolesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index 04fe912e57..f2da06c9c2 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Mvc; @@ -18,11 +19,11 @@ public class TodoCollectionsController : JsonApiController resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { _dbResolver = contextResolver; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs index 040ad38e20..c7f3a6244e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class TodoItemsController : JsonApiController { public TodoItemsController( IJsonApiOptions jsonApiOPtions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOPtions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOPtions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs index caab1c1c85..18e566dc07 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -13,10 +14,10 @@ public abstract class AbstractTodoItemsController { protected AbstractTodoItemsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, resourceGraph, service, loggerFactory) { } } @@ -25,10 +26,10 @@ public class TodoItemsTestController : AbstractTodoItemsController { public TodoItemsTestController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, resourceGraph, service, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs index 931db12325..475b93b300 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class UsersController : JsonApiController { public UsersController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs index 4027582a9c..4e93d26bea 100644 --- a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs +++ b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class CustomTodoItemsController : JsonApiController { public CustomTodoItemsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 04194d68c2..5f97567339 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -183,30 +183,34 @@ private void AddRelationships(ResourceObject data, ContextEntity contextEntity, private RelationshipData GetRelationshipData(RelationshipAttribute attr, ContextEntity contextEntity, IIdentifiable entity) { - //var linkBuilder = new LinkBuilder(_documentBuilderOptions,_requestManager); + var linkBuilder = new LinkBuilder(_jsonApiContext.Options,_requestManager); var relationshipData = new RelationshipData(); - //if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) - //{ - // relationshipData.Links = new Links(); - // if (attr.DocumentLinks.HasFlag(Link.Self)) - // relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - - // if (attr.DocumentLinks.HasFlag(Link.Related)) - // relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - //} - - //// this only includes the navigation property, we need to actually check the navigation property Id - //var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); - //if (navigationEntity == null) - // relationshipData.SingleData = attr.IsHasOne - // ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) - // : null; - //else if (navigationEntity is IEnumerable) - // relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); - //else - // relationshipData.SingleData = GetRelationship(navigationEntity); + if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) + { + relationshipData.Links = new Links(); + if (attr.DocumentLinks.HasFlag(Link.Self)) + { + relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + } + + if (attr.DocumentLinks.HasFlag(Link.Related)) + { + relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + } + } + + // this only includes the navigation property, we need to actually check the navigation property Id + var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); + if (navigationEntity == null) + relationshipData.SingleData = attr.IsHasOne + ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) + : null; + else if (navigationEntity is IEnumerable) + relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); + else + relationshipData.SingleData = GetRelationship(navigationEntity); return relationshipData; } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 191ea1b937..952ff26582 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -29,5 +29,6 @@ public interface IJsonApiOptions bool RelativeLinks { get; set; } IResourceGraph ResourceGraph { get; set; } bool AllowCustomQueryParameters { get; set; } + string Namespace { get; set; } } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 2c44a5f335..a04d2b1c94 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -7,6 +7,7 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Controllers { @@ -18,14 +19,13 @@ public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService - ) : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) { } + ) : base(jsonApiOptions, resourceService, resourceService) { } public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null - ) : base(jsonApiOptions, jsonApiContext, queryService, cmdService) { } + ) : base(jsonApiOptions, queryService, cmdService) { } public BaseJsonApiController( @@ -39,7 +39,7 @@ public BaseJsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } public class BaseJsonApiController @@ -54,14 +54,24 @@ public class BaseJsonApiController private readonly IUpdateService _update; private readonly IUpdateRelationshipService _updateRelationships; private readonly IDeleteService _delete; + private readonly ILogger> _logger; private readonly IJsonApiOptions _jsonApiOptions; private readonly IResourceGraph _resourceGraph; public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IResourceGraph resourceGraphManager, - IResourceService resourceService) + IResourceService resourceService, + ILoggerFactory loggerFactory) { + if(loggerFactory != null) + { + _logger = loggerFactory.CreateLogger>(); + } + else + { + _logger = new Logger>(new LoggerFactory()); + } _jsonApiOptions = jsonApiOptions; _resourceGraph = resourceGraphManager; _getAll = resourceService; @@ -76,7 +86,6 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null) { @@ -93,7 +102,6 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index eed4f9bd1a..3ee26db3c6 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -26,7 +26,7 @@ public JsonApiCmdController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceService) { } [HttpPost] diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 0427f5a678..0d69034dd4 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -17,22 +18,15 @@ public class JsonApiController : JsonApiController where T : class, I public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + ILoggerFactory loggerFactory = null + ) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) - { } - - public JsonApiController( - IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -41,37 +35,23 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + - public JsonApiController(IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, - IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) - { - } } public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) - { } - - public JsonApiController( - IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + ILoggerFactory loggerFactory = null) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory = null) { } public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -80,7 +60,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, jsonApiContext, 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(); diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index ad0978709b..21ca22a578 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -24,7 +24,7 @@ public JsonApiQueryController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceService) { } [HttpGet] diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/QueryManager.cs similarity index 100% rename from src/JsonApiDotNetCore/Managers/RequestManager.cs rename to src/JsonApiDotNetCore/Managers/QueryManager.cs diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index b148692e41..68ba0de48c 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -20,7 +20,7 @@ public RequestMiddleware(RequestDelegate next) _next = next; } - public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options ) + public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options) { if (IsValid(context)) { @@ -29,7 +29,7 @@ public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IR // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); - ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph); + ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph, options); requestManager.SetContextEntity(contextEntityCurrent); requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); await _next(context); @@ -84,9 +84,11 @@ internal static string GetNamespaceFromPath(string path, string entityName) /// /// /// - private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph) + private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph, IJsonApiOptions options) { - var typeString = path.ToString().Split('/')[1]; + var pathSplit = path.ToString().Replace($"{options.Namespace}/", "").Split('/'); + + var typeString = pathSplit[1]; return resourceGraph.GetEntityType(typeString); } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index ac4b4f2438..defdfd3e92 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -184,9 +184,15 @@ private object SetRelationships( foreach (var attr in contextEntity.Relationships) { - entity = attr.IsHasOne - ? SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, contextEntity, relationships, included) - : SetHasManyRelationship(entity, entityProperties, (HasManyAttribute)attr, contextEntity, relationships, included); + if (attr.IsHasOne) + { + SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, contextEntity, relationships, included); + } + else + { + SetHasManyRelationship(entity, entityProperties, (HasManyAttribute)attr, contextEntity, relationships, included); + } + } return entity; @@ -215,14 +221,14 @@ private object SetHasOneRelationship(object entity, SetHasOneNavigationPropertyValue(entity, attr, rio, included); // recursive call ... - if(included != null) + if (included != null) { var navigationPropertyValue = attr.GetValue(entity); var resourceGraphEntity = _jsonApiContext.ResourceGraph.GetContextEntity(attr.Type); - if(navigationPropertyValue != null && resourceGraphEntity != null) + if (navigationPropertyValue != null && resourceGraphEntity != null) { var includedResource = included.SingleOrDefault(r => r.Type == rio.Type && r.Id == rio.Id); - if(includedResource != null) + if (includedResource != null) SetRelationships(navigationPropertyValue, resourceGraphEntity, includedResource.Relationships, included); } } @@ -283,7 +289,7 @@ private object SetHasManyRelationship(object entity, if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) { - if(relationshipData.IsHasMany == false || relationshipData.ManyData == null) + if (relationshipData.IsHasMany == false || relationshipData.ManyData == null) return entity; var relatedResources = relationshipData.ManyData.Select(r => From 94615f85df27522a0ea8b67e4e7b067dace2804b Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 24 May 2019 17:29:29 +0200 Subject: [PATCH 08/10] feat: decoupled controllers --- .../Controllers/ArticlesController.cs | 12 +-- .../Controllers/PeopleController.cs | 7 +- .../ModelsController.cs | 6 +- .../Controllers/ReportsController.cs | 5 +- .../Controllers/CoursesController.cs | 5 +- .../Controllers/DepartmentsController.cs | 5 +- .../Controllers/StudentsController.cs | 5 +- .../Builders/DocumentBuilder.cs | 2 +- .../Controllers/BaseJsonApiController.cs | 13 ++- .../Controllers/JsonApiController.cs | 6 +- .../Internal/ResourceGraph.cs | 8 ++ .../Managers/Contracts/IRequestManager.cs | 3 + .../{QueryManager.cs => RequestManager.cs} | 1 + .../Middleware/RequestMiddleware.cs | 30 ++++++- .../Services/EntityResourceService.cs | 22 ++--- .../Services/JsonApiContext.cs | 5 ++ .../BaseJsonApiController_Tests.cs | 83 +++++++++---------- 17 files changed, 132 insertions(+), 86 deletions(-) rename src/JsonApiDotNetCore/Managers/{QueryManager.cs => RequestManager.cs} (96%) diff --git a/src/Examples/GettingStarted/Controllers/ArticlesController.cs b/src/Examples/GettingStarted/Controllers/ArticlesController.cs index 8077983e92..2d1567659b 100644 --- a/src/Examples/GettingStarted/Controllers/ArticlesController.cs +++ b/src/Examples/GettingStarted/Controllers/ArticlesController.cs @@ -1,6 +1,7 @@ using GettingStarted.Models; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; namespace GettingStarted @@ -9,16 +10,9 @@ public class ArticlesController : JsonApiController
{ public ArticlesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService
resourceService) - - - - - - - - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs index b033ae172a..8d8ee58a2a 100644 --- a/src/Examples/GettingStarted/Controllers/PeopleController.cs +++ b/src/Examples/GettingStarted/Controllers/PeopleController.cs @@ -1,6 +1,7 @@ using GettingStarted.Models; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; namespace GettingStarted @@ -8,10 +9,10 @@ namespace GettingStarted public class PeopleController : JsonApiController { public PeopleController( - IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IResourceService resourceService) - : base(jsonApiOptions,jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs index 55138e7d71..1c066c28c9 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs @@ -1,6 +1,6 @@ -using GettingStarted.Models; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; namespace GettingStarted.ResourceDefinitionExample @@ -9,9 +9,9 @@ public class ModelsController : JsonApiController { public ModelsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index 95add7975c..e421477c20 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal.Contracts; namespace ReportsExample.Controllers { @@ -11,9 +12,9 @@ public class ReportsController : BaseJsonApiController { public ReportsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IGetAllService getAll) - : base(jsonApiOptions, jsonApiContext, getAll: getAll) + : base(jsonApiOptions, resourceGraph, getAll: getAll) { } [HttpGet] diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs index 908776ebc5..48d280f5cb 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class CoursesController : JsonApiController { public CoursesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs index 9edef6fa7c..63310743ac 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs @@ -1,6 +1,7 @@ using System; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; using Microsoft.Extensions.Logging; @@ -11,10 +12,10 @@ public class DepartmentsController : JsonApiController { public DepartmentsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs index 7ba8fde1af..5f3551849a 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class StudentsController : JsonApiController { public StudentsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 5f97567339..7814915134 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -217,7 +217,7 @@ private RelationshipData GetRelationshipData(RelationshipAttribute attr, Context private List GetIncludedEntities(List included, ContextEntity rootContextEntity, IIdentifiable rootResource) { - if (_jsonApiContext.RequestManager.IncludedRelationships != null) + if (_requestManager.IncludedRelationships != null) { foreach (var relationshipName in _jsonApiContext.RequestManager.IncludedRelationships) { diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index a04d2b1c94..50b06e7b31 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; @@ -30,7 +31,7 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -39,7 +40,7 @@ public BaseJsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } public class BaseJsonApiController @@ -82,6 +83,12 @@ public BaseJsonApiController( _update = resourceService; _updateRelationships = resourceService; _delete = resourceService; + ParseQueryParams(); + } + + private void ParseQueryParams() + { + } public BaseJsonApiController( @@ -102,6 +109,7 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -111,6 +119,7 @@ public BaseJsonApiController( IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null) { + _resourceGraph = resourceGraph; _jsonApiOptions = jsonApiOptions; _getAll = getAll; _getById = getById; diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 0d69034dd4..9c9155bb8c 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -27,6 +27,7 @@ public JsonApiController( public JsonApiController( IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -35,7 +36,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } @@ -52,6 +53,7 @@ public JsonApiController( public JsonApiController( IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -60,7 +62,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } [HttpGet] public override async Task GetAsync() => await base.GetAsync(); diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index e2db1b667b..a08c18eaca 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -7,6 +7,10 @@ namespace JsonApiDotNetCore.Internal { + internal class ControllerMapping + { + + } /// /// keeps track of all the models/resources defined in JADNC /// @@ -14,6 +18,10 @@ public class ResourceGraph : IResourceGraph { internal List Entities { get; } internal List ValidationResults { get; } + + + + [Obsolete("please instantiate properly")] internal static IResourceGraph Instance { get; set; } public ResourceGraph() { } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs index 3f8f0b8e52..41eef8f1b7 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Managers.Contracts { @@ -20,6 +21,8 @@ public interface IRequestManager : IQueryRequest /// string BasePath { get; set; } QuerySet QuerySet { get; set; } + IQueryCollection FullQuerySet { get; set; } + /// /// Gets the relationships as set in the query parameters /// diff --git a/src/JsonApiDotNetCore/Managers/QueryManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs similarity index 96% rename from src/JsonApiDotNetCore/Managers/QueryManager.cs rename to src/JsonApiDotNetCore/Managers/RequestManager.cs index 857b97c64f..da0e3f105e 100644 --- a/src/JsonApiDotNetCore/Managers/QueryManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -12,6 +12,7 @@ class RequestManager : IRequestManager { private ContextEntity _contextEntity; + private IQueryParser _queryParser; public string BasePath { get; set; } public List IncludedRelationships { get; set; } diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 68ba0de48c..4173863f9a 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -11,6 +11,11 @@ namespace JsonApiDotNetCore.Middleware { + /// + /// Can be overwritten to help you out during testing + /// + /// This sets all necessary paaramters relating to the HttpContext for JADNC + /// public class RequestMiddleware { private readonly RequestDelegate _next; @@ -20,8 +25,14 @@ public RequestMiddleware(RequestDelegate next) _next = next; } - public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options) + public async Task Invoke(HttpContext context, + IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, + IRequestManager requestManager, + IQueryParser queryParser, + IJsonApiOptions options) { + if (IsValid(context)) { // HACK: this currently results in allocation of @@ -32,9 +43,26 @@ public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IR ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph, options); requestManager.SetContextEntity(contextEntityCurrent); requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); + //Handle all querySet + HandleUriParameters(context, queryParser, requestManager); + await _next(context); } } + /// + /// Parses the uri, and helps you out + /// + /// + /// + protected void HandleUriParameters(HttpContext context, IQueryParser queryParser, IRequestManager requestManager) + { + if (context.Request.Query.Count > 0) + { + //requestManager.FullQuerySet = context.Request.Query; + requestManager.QuerySet = queryParser.Parse(context.Request.Query); + requestManager.IncludedRelationships = requestManager.QuerySet.IncludedRelationships; + } + } private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) { diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 78f1539520..6aad0e92fb 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -48,7 +48,7 @@ public class EntityResourceService : where TEntity : class, IIdentifiable { private readonly IPageManager _pageManager; - private readonly IRequestManager _queryManager; + private readonly IRequestManager _requestManager; private readonly IJsonApiContext _jsonApiContext; private readonly IJsonApiOptions _options; private readonly IEntityRepository _repository; @@ -80,7 +80,7 @@ public EntityResourceService( ILoggerFactory loggerFactory) { _pageManager = pageManager; - _queryManager = queryManager; + _requestManager = queryManager; _jsonApiContext = jsonApiContext; _options = options; _repository = entityRepository; @@ -108,7 +108,7 @@ public virtual async Task CreateAsync(TResource resource) // this ensures relationships get reloaded from the database if they have // been requested // https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343 - if (AreRelationshipsIncluded()) + if (ShouldRelationshipsBeIncluded()) { if (_repository is IEntityFrameworkRepository efRepository) efRepository.DetachRelationshipPointers(entity); @@ -129,7 +129,7 @@ public virtual async Task> GetAsync() entities = ApplySortAndFilterQuery(entities); - if (AreRelationshipsIncluded()) + if (ShouldRelationshipsBeIncluded()) { entities = IncludeRelationships(entities, _jsonApiContext.RequestManager.QuerySet.IncludedRelationships); } @@ -150,7 +150,7 @@ public virtual async Task GetAsync(TId id) { TResource resource; - if (AreRelationshipsIncluded()) + if (ShouldRelationshipsBeIncluded()) { resource = await GetWithRelationshipsAsync(id); } @@ -270,7 +270,7 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable - /// actually include the relationships + /// Actually include the relationships /// /// /// @@ -294,17 +294,17 @@ protected virtual IQueryable IncludeRelationships(IQueryable e /// private async Task GetWithRelationshipsAsync(TId id) { - var fields = _queryManager.GetFields(); + var fields = _requestManager.GetFields(); var query = _repository.Select(_repository.GetQueryable(), fields).Where(e => e.Id.Equals(id)); - _queryManager.GetRelationships().ForEach(r => + _requestManager.GetRelationships().ForEach(r => { query = _repository.Include(query, r); }); TEntity value; // https://github.com/aspnet/EntityFrameworkCore/issues/6573 - if(_queryManager.GetFields()?.Count() > 0) + if(_requestManager.GetFields()?.Count() > 0) { value = query.FirstOrDefault(); } @@ -319,9 +319,9 @@ private async Task GetWithRelationshipsAsync(TId id) /// Should the relationships be included? /// /// - private bool AreRelationshipsIncluded() + private bool ShouldRelationshipsBeIncluded() { - return _queryManager.GetRelationships()?.Count() > 0; + return _requestManager.GetRelationships()?.Count() > 0; } /// diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index faf65f9397..637080e98f 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -42,10 +42,12 @@ public JsonApiContext( } public IJsonApiOptions Options { get; set; } + [Obsolete("Please use the standalone `IResourceGraph`")] public IResourceGraph ResourceGraph { get; set; } [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } + [Obsolete("Please us the IRequestManager")] public QuerySet QuerySet { get; set; } public bool IsRelationshipData { get; set; } public bool IsRelationshipPath { get; private set; } @@ -61,9 +63,12 @@ public JsonApiContext( public Dictionary RelationshipsToUpdate { get; set; } = new Dictionary(); public HasManyRelationshipPointers HasManyRelationshipPointers { get; private set; } = new HasManyRelationshipPointers(); public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers(); + [Obsolete("Please use the standalone Requestmanager")] public IRequestManager RequestManager { get; set; } IPageManager IJsonApiContext.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + [Obsolete("This is no longer necessary")] public IJsonApiContext ApplyContext(object controller) { if (controller == null) diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index 6fe8638eca..d6c5e951b8 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -18,7 +18,7 @@ public class Resource : Identifiable { [Attr("test-attribute")] public string TestAttribute { get; set; } } - private Mock _jsonApiContextMock = new Mock(); + private Mock _resourceGraph = new Mock(); private Mock _resourceGraphMock = new Mock(); [Fact] @@ -26,14 +26,14 @@ public async Task GetAsync_Calls_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getAll: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getAll: serviceMock.Object); // act await controller.GetAsync(); // assert serviceMock.Verify(m => m.GetAsync(), Times.Once); - VerifyApplyContext(); + } [Fact] @@ -41,7 +41,7 @@ public async Task GetAsync_Throws_405_If_No_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync()); @@ -56,14 +56,14 @@ public async Task GetAsyncById_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getById: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getById: serviceMock.Object); // act await controller.GetAsync(id); // assert serviceMock.Verify(m => m.GetAsync(id), Times.Once); - VerifyApplyContext(); + } [Fact] @@ -72,7 +72,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, _jsonApiContextMock.Object, getById: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getById: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync(id)); @@ -87,14 +87,13 @@ public async Task GetRelationshipsAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationships: serviceMock.Object); // act await controller.GetRelationshipsAsync(id, string.Empty); // assert serviceMock.Verify(m => m.GetRelationshipsAsync(id, string.Empty), Times.Once); - VerifyApplyContext(); } [Fact] @@ -103,7 +102,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, _jsonApiContextMock.Object, getRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipsAsync(id, string.Empty)); @@ -118,14 +117,13 @@ public async Task GetRelationshipAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationship: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationship: serviceMock.Object); // act await controller.GetRelationshipAsync(id, string.Empty); // assert serviceMock.Verify(m => m.GetRelationshipAsync(id, string.Empty), Times.Once); - VerifyApplyContext(); } [Fact] @@ -134,7 +132,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, _jsonApiContextMock.Object, getRelationship: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationship: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipAsync(id, string.Empty)); @@ -150,16 +148,16 @@ public async Task PatchAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + + var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, update: serviceMock.Object); // act await controller.PatchAsync(id, resource); // assert serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny()), Times.Once); - VerifyApplyContext(); } [Fact] @@ -169,16 +167,15 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateDisbled() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, update: serviceMock.Object); // act var response = await controller.PatchAsync(id, resource); // assert serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny()), Times.Once); - VerifyApplyContext(); Assert.IsNotType(response); } @@ -189,11 +186,10 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateEnabled() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions{ValidateModelState = true}); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); +// _resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, _resourceGraph.Object, update: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -211,7 +207,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, _jsonApiContextMock.Object, update: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, update: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchAsync(id, It.IsAny())); @@ -226,9 +222,9 @@ public async Task PostAsync_Calls_Service() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); +// _resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext {HttpContext = new DefaultHttpContext()}; @@ -237,7 +233,6 @@ public async Task PostAsync_Calls_Service() // assert serviceMock.Verify(m => m.CreateAsync(It.IsAny()), Times.Once); - VerifyApplyContext(); } [Fact] @@ -246,9 +241,9 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + //_resourceGraph.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, _resourceGraph.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; @@ -257,7 +252,6 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() // assert serviceMock.Verify(m => m.CreateAsync(It.IsAny()), Times.Once); - VerifyApplyContext(); Assert.IsNotType(response); } @@ -267,11 +261,11 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateEnabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); + //_resourceGraph.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = true }); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + //_resourceGraph.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = true }); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, _resourceGraph.Object, create: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -290,14 +284,13 @@ 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, _jsonApiContextMock.Object, updateRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, updateRelationships: serviceMock.Object); // act await controller.PatchRelationshipsAsync(id, string.Empty, null); // assert serviceMock.Verify(m => m.UpdateRelationshipsAsync(id, string.Empty, null), Times.Once); - VerifyApplyContext(); } [Fact] @@ -306,7 +299,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, _jsonApiContextMock.Object, updateRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, updateRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchRelationshipsAsync(id, string.Empty, null)); @@ -322,14 +315,13 @@ 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, _jsonApiContextMock.Object, delete: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, delete: serviceMock.Object); // Act await controller.DeleteAsync(id); // Assert serviceMock.Verify(m => m.DeleteAsync(id), Times.Once); - VerifyApplyContext(); } [Fact] @@ -340,7 +332,7 @@ public async Task DeleteAsync_Throws_405_If_No_Service() var serviceMock = new Mock>(); var controller = new BaseJsonApiController(new Mock().Object, - _jsonApiContextMock.Object, delete: null); + _resourceGraph.Object, delete: null); // act var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); @@ -349,7 +341,6 @@ public async Task DeleteAsync_Throws_405_If_No_Service() Assert.Equal(405, exception.GetStatusCode()); } - private void VerifyApplyContext() - => _jsonApiContextMock.Verify(m => m.ApplyContext(It.IsAny>()), Times.Once); + } } From 4a53c0056bba74c8967547ec365cb10ebe2cc4b4 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Mon, 27 May 2019 16:14:50 +0200 Subject: [PATCH 09/10] chore: renamed resourcegraphbuilder, took out some extensions, made benchmarks work again --- benchmarks/Query/QueryParser_Benchmarks.cs | 11 +- .../Controllers/TodoItemsCustomController.cs | 3 +- .../Builders/IResourceGraphBuilder.cs | 81 ++++++++++++ ...raphBuilder.cs => ResourceGraphBuilder.cs} | 118 +++++++----------- .../Configuration/JsonApiOptions.cs | 1 + .../Data/DefaultEntityRepository.cs | 31 ++++- .../Extensions/IQueryableExtensions.cs | 10 -- .../IServiceCollectionExtensions.cs | 27 ++-- .../Formatters/JsonApiReader.cs | 16 ++- .../Graph/ServiceDiscoveryFacade.cs | 94 ++++++++++---- .../Internal/Contracts/IResourceGraph.cs | 3 + src/JsonApiDotNetCore/Internal/PageManager.cs | 15 ++- .../Internal/Query/AttrSortQuery.cs | 6 +- .../Internal/Query/BaseAttrQuery.cs | 36 +++--- .../Internal/Query/BaseFilterQuery.cs | 2 +- .../Internal/Query/RelatedAttrSortQuery.cs | 2 +- .../Internal/ResourceGraph.cs | 41 +++++- .../Managers/Contracts/IRequestManager.cs | 7 ++ .../Managers/RequestManager.cs | 5 + .../Middleware/RequestMiddleware.cs | 48 ++++++- .../Services/EntityResourceService.cs | 4 +- .../Services/IJsonApiContext.cs | 3 + .../Services/JsonApiContext.cs | 2 - src/JsonApiDotNetCore/Services/QueryParser.cs | 25 ++-- .../Extensibility/CustomControllerTests.cs | 7 +- .../Acceptance/TodoItemsControllerTests.cs | 23 ++-- test/UnitTests/Services/QueryParser_Tests.cs | 85 ++++++------- 27 files changed, 470 insertions(+), 236 deletions(-) create mode 100644 src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs rename src/JsonApiDotNetCore/Builders/{ContextGraphBuilder.cs => ResourceGraphBuilder.cs} (78%) diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs index de82baa60f..19819c3609 100644 --- a/benchmarks/Query/QueryParser_Benchmarks.cs +++ b/benchmarks/Query/QueryParser_Benchmarks.cs @@ -5,6 +5,7 @@ using BenchmarkDotNet.Attributes.Jobs; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http.Internal; @@ -21,14 +22,14 @@ public class QueryParser_Benchmarks { private const string DESCENDING_SORT = "-" + ATTRIBUTE; public QueryParser_Benchmarks() { - var controllerContextMock = new Mock(); - controllerContextMock.Setup(m => m.RequestEntity).Returns(new ContextEntity { + var requestMock = new Mock(); + requestMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { Attributes = new List { new AttrAttribute(ATTRIBUTE, ATTRIBUTE) } }); var options = new JsonApiOptions(); - _queryParser = new BenchmarkFacade(controllerContextMock.Object, options); + _queryParser = new BenchmarkFacade(requestMock.Object, options); } [Benchmark] @@ -58,8 +59,8 @@ private void Run(int iterations, Action action) { // this facade allows us to expose and micro-benchmark protected methods private class BenchmarkFacade : QueryParser { public BenchmarkFacade( - IControllerContext controllerContext, - JsonApiOptions options) : base(controllerContext, options) { } + IRequestManager requestManager, + JsonApiOptions options) : base(requestManager, options) { } public void _ParseSortParameters(string value) => base.ParseSortParameters(value); } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs index ca2e860fa9..17b11215c4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs @@ -9,8 +9,7 @@ namespace JsonApiDotNetCoreExample.Controllers { - [DisableRoutingConvention] - [Route("custom/route/todo-items")] + [DisableRoutingConvention, Route("custom/route/todo-items")] public class TodoItemsCustomController : CustomJsonApiController { public TodoItemsCustomController( diff --git a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs new file mode 100644 index 0000000000..7bcf6aa7ad --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCore.Builders +{ + public interface IResourceGraphBuilder + { + /// + /// Construct the + /// + IResourceGraph Build(); + + /// + /// Add a json:api resource + /// + /// The resource model type + /// + /// The pluralized name that should be exposed by the API. + /// If nothing is specified, the configured name formatter will be used. + /// See . + /// + IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; + + IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null); + + /// + /// Add a json:api resource + /// + /// The resource model type + /// The resource model identifier type + /// + /// The pluralized name that should be exposed by the API. + /// If nothing is specified, the configured name formatter will be used. + /// See . + /// + IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; + + /// + /// Add a Json:Api resource + /// + /// The resource model type + /// The resource model identifier type + /// + /// The pluralized name that should be exposed by the API. + /// If nothing is specified, the configured name formatter will be used. + /// See . + /// + IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null); + + /// + /// Add all the models that are part of the provided + /// 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); + + /// + /// Which links to include. Defaults to . + /// + Link DocumentLinks { get; set; } + + + } +} diff --git a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs similarity index 78% rename from src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs rename to src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs index 038796c17a..696d2fe176 100644 --- a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs @@ -9,76 +9,18 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Builders { - public interface IResourceGraphBuilder - { - /// - /// Construct the - /// - IResourceGraph Build(); - - /// - /// Add a json:api resource - /// - /// The resource model type - /// - /// The pluralized name that should be exposed by the API. - /// If nothing is specified, the configured name formatter will be used. - /// See . - /// - IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; - - /// - /// Add a json:api resource - /// - /// The resource model type - /// The resource model identifier type - /// - /// The pluralized name that should be exposed by the API. - /// If nothing is specified, the configured name formatter will be used. - /// See . - /// - IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; - - /// - /// Add a json:api resource - /// - /// The resource model type - /// The resource model identifier type - /// - /// The pluralized name that should be exposed by the API. - /// If nothing is specified, the configured name formatter will be used. - /// See . - /// - IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null); - - /// - /// Add all the models that are part of the provided - /// 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); - - /// - /// Which links to include. Defaults to . - /// - Link DocumentLinks { get; set; } - } - 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 = JsonApiOptions.ResourceNameFormatter; @@ -91,7 +33,23 @@ public IResourceGraph Build() // this must be done at build so that call order doesn't matter _entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType)); - var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults); + List controllerContexts = new List() { }; + foreach(var cm in _controllerMapper) + { + var model = cm.Key; + foreach(var controller in cm.Value) + { + var routeAttribute = controller.GetCustomAttribute(); + + controllerContexts.Add(new ControllerModelMap + { + Model = model, + Controller = controller, + Path = routeAttribute?.Template + }); + } + } + var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults, controllerContexts); return graph; } @@ -183,16 +141,17 @@ protected virtual List GetRelationships(Type entityType) attribute.Type = GetRelationshipType(attribute, prop); attributes.Add(attribute); - if (attribute is HasManyThroughAttribute hasManyThroughAttribute) { + if (attribute is HasManyThroughAttribute hasManyThroughAttribute) + { var throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.InternalThroughName); - if(throughProperty == null) + if (throughProperty == null) throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}'. Type does not contain a property named '{hasManyThroughAttribute.InternalThroughName}'."); - - if(throughProperty.PropertyType.Implements() == false) + + if (throughProperty.PropertyType.Implements() == false) throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}.{throughProperty.Name}'. Property type does not implement IList."); - + // assumption: the property should be a generic collection, e.g. List - if(throughProperty.PropertyType.IsGenericType == false) + if (throughProperty.PropertyType.IsGenericType == false) throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}'. Expected through entity to be a generic type, such as List<{prop.PropertyType}>."); // Article → List @@ -202,7 +161,7 @@ protected virtual List GetRelationships(Type entityType) hasManyThroughAttribute.ThroughType = throughProperty.PropertyType.GetGenericArguments()[0]; var throughProperties = hasManyThroughAttribute.ThroughType.GetProperties(); - + // ArticleTag.Article hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType == entityType) ?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {entityType}"); @@ -215,7 +174,7 @@ protected virtual List GetRelationships(Type entityType) // Article → ArticleTag.Tag hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.Type) ?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {hasManyThroughAttribute.Type}"); - + // ArticleTag.TagId var rightIdPropertyName = JsonApiOptions.RelatedIdMapper.GetRelatedIdPropertyName(hasManyThroughAttribute.RightProperty.Name); hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName) @@ -305,5 +264,24 @@ public IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNam _resourceNameFormatter = resourceNameFormatter; return this; } + + public IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null) + { + if (model == null) + { + _undefinedMapper.Add(controller); + return this; + + } + if (_controllerMapper.Keys.Contains(model)) + { + _controllerMapper[model].Add(controller); + } + else + { + _controllerMapper.Add(model, new List() { controller }); + } + return this; + } } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index f81c034663..c0499d472a 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -77,6 +77,7 @@ public class JsonApiOptions : IJsonApiOptions /// /// The graph of all resources exposed by this application. /// + [Obsolete("Use the standalone resourcegraph")] public IResourceGraph ResourceGraph { get; set; } /// diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 2a60acff21..4254935d78 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -7,6 +7,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.EntityFrameworkCore; @@ -45,6 +46,7 @@ public class DefaultEntityRepository IEntityFrameworkRepository where TEntity : class, IIdentifiable { + private readonly IRequestManager _requestManager; private readonly DbContext _context; private readonly DbSet _dbSet; private readonly ILogger _logger; @@ -57,6 +59,7 @@ public DefaultEntityRepository( IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) { + _requestManager = jsonApiContext.RequestManager; _context = contextResolver.GetContext(); _dbSet = contextResolver.GetDbSet(); _jsonApiContext = jsonApiContext; @@ -70,6 +73,8 @@ public DefaultEntityRepository( IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) { + _requestManager = jsonApiContext.RequestManager; + _context = contextResolver.GetContext(); _dbSet = contextResolver.GetDbSet(); _jsonApiContext = jsonApiContext; @@ -79,7 +84,7 @@ public DefaultEntityRepository( } - + public virtual IQueryable Get() { if (_jsonApiContext.RequestManager.QuerySet?.Fields != null && _jsonApiContext.RequestManager.QuerySet.Fields.Count > 0) @@ -89,7 +94,7 @@ public virtual IQueryable Get() } /// - public virtual IQueryable GetQueryable() + public virtual IQueryable GetQueryable() => _dbSet; public virtual IQueryable Select(IQueryable entities, List fields) @@ -112,7 +117,23 @@ public virtual IQueryable Filter(IQueryable entities, FilterQu } } - return entities.Filter(_jsonApiContext, filterQuery); + return FilterEntities(entities, filterQuery); + } + + public IQueryable FilterEntities(IQueryable entities, FilterQuery filterQuery) + { + if (filterQuery == null) + { + return entities; + } + + // Relationship.Attribute + if (filterQuery.IsAttributeOfRelationship) + { + return entities.Filter(new RelatedAttrFilterQuery(_jsonApiContext, filterQuery)); + } + + return entities.Filter(new AttrFilterQuery(_jsonApiContext, filterQuery)); } /// @@ -245,7 +266,7 @@ private void AttachHasMany(TEntity entity, HasManyAttribute relationship, IList foreach (var pointer in pointers) { if (_context.EntityIsTracked(pointer as IIdentifiable) == false) - _context.Entry(pointer).State = EntityState.Unchanged; + _context.Entry(pointer).State = EntityState.Unchanged; } } } @@ -407,7 +428,7 @@ public virtual IQueryable Include(IQueryable entities, string // variables mutated in recursive loop // TODO: make recursive method string internalRelationshipPath = null; - var entity = _jsonApiContext.RequestEntity; + var entity = _requestManager.GetContextEntity(); for (var i = 0; i < relationshipChain.Length; i++) { var requestedRelationship = relationshipChain[i]; diff --git a/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs b/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs index af33e883a6..4dfa83733c 100644 --- a/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs @@ -122,17 +122,7 @@ private static IOrderedQueryable CallGenericOrderMethod(IQuery return (IOrderedQueryable)result; } - public static IQueryable Filter(this IQueryable source, IJsonApiContext jsonApiContext, FilterQuery filterQuery) - { - if (filterQuery == null) - return source; - // Relationship.Attribute - if (filterQuery.IsAttributeOfRelationship) - return source.Filter(new RelatedAttrFilterQuery(jsonApiContext, filterQuery)); - - return source.Filter(new AttrFilterQuery(jsonApiContext, filterQuery)); - } public static IQueryable Filter(this IQueryable source, BaseFilterQuery filterQuery) { diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 57674cf0ca..066b8a65af 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -66,8 +66,8 @@ public static IServiceCollection AddJsonApi( { var config = new JsonApiOptions(); configureOptions(config); - - if(autoDiscover != null) + + if (autoDiscover != null) { var facade = new ServiceDiscoveryFacade(services, config.ResourceGraphBuilder); autoDiscover(facade); @@ -103,7 +103,9 @@ public static void AddJsonApiInternals( JsonApiOptions jsonApiOptions) { if (jsonApiOptions.ResourceGraph == null) + { jsonApiOptions.ResourceGraph = jsonApiOptions.ResourceGraphBuilder.Build(); + } if (jsonApiOptions.ResourceGraph.UsesDbContext == false) { @@ -141,7 +143,7 @@ public static void AddJsonApiInternals( services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); services.AddSingleton(jsonApiOptions); - services.AddTransient(); + services.AddScoped(); services.AddSingleton(jsonApiOptions.ResourceGraph); services.AddScoped(); services.AddSingleton(); @@ -197,7 +199,7 @@ public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions js /// Adds all required registrations for the service to the container /// /// - public static IServiceCollection AddResourceService(this IServiceCollection services) + public static IServiceCollection AddResourceService(this IServiceCollection services) { var typeImplementsAnExpectedInterface = false; @@ -206,28 +208,29 @@ public static IServiceCollection AddResourceService(this IServiceCollection s // it is _possible_ that a single concrete type could be used for multiple resources... var resourceDescriptors = GetResourceTypesFromServiceImplementation(serviceImplementationType); - foreach(var resourceDescriptor in resourceDescriptors) + foreach (var resourceDescriptor in resourceDescriptors) { - foreach(var openGenericType in ServiceDiscoveryFacade.ServiceInterfaces) + foreach (var openGenericType in ServiceDiscoveryFacade.ServiceInterfaces) { // A shorthand interface is one where the id type is ommitted // e.g. IResourceService is the shorthand for IResourceService var isShorthandInterface = (openGenericType.GetTypeInfo().GenericTypeParameters.Length == 1); - if(isShorthandInterface && resourceDescriptor.IdType != typeof(int)) + if (isShorthandInterface && resourceDescriptor.IdType != typeof(int)) continue; // we can't create a shorthand for id types other than int var concreteGenericType = isShorthandInterface ? openGenericType.MakeGenericType(resourceDescriptor.ResourceType) : openGenericType.MakeGenericType(resourceDescriptor.ResourceType, resourceDescriptor.IdType); - if(concreteGenericType.IsAssignableFrom(serviceImplementationType)) { + if (concreteGenericType.IsAssignableFrom(serviceImplementationType)) + { services.AddScoped(concreteGenericType, serviceImplementationType); typeImplementsAnExpectedInterface = true; } } } - if(typeImplementsAnExpectedInterface == false) + if (typeImplementsAnExpectedInterface == false) throw new JsonApiSetupException($"{serviceImplementationType} does not implement any of the expected JsonApiDotNetCore interfaces."); return services; @@ -237,12 +240,12 @@ private static HashSet GetResourceTypesFromServiceImplementa { var resourceDecriptors = new HashSet(); var interfaces = type.GetInterfaces(); - foreach(var i in interfaces) + foreach (var i in interfaces) { - if(i.IsGenericType) + if (i.IsGenericType) { var firstGenericArgument = i.GenericTypeArguments.FirstOrDefault(); - if(TypeLocator.TryGetResourceDescriptor(firstGenericArgument, out var resourceDescriptor) == true) + if (TypeLocator.TryGetResourceDescriptor(firstGenericArgument, out var resourceDescriptor) == true) { resourceDecriptors.Add(resourceDescriptor); } diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs index 3acba206fa..7c4ec4e68c 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -12,14 +12,14 @@ namespace JsonApiDotNetCore.Formatters { public class JsonApiReader : IJsonApiReader { - private readonly IJsonApiDeSerializer _deSerializer; + private readonly IJsonApiDeSerializer _deserializer; private readonly IJsonApiContext _jsonApiContext; private readonly ILogger _logger; public JsonApiReader(IJsonApiDeSerializer deSerializer, IJsonApiContext jsonApiContext, ILoggerFactory loggerFactory) { - _deSerializer = deSerializer; + _deserializer = deSerializer; _jsonApiContext = jsonApiContext; _logger = loggerFactory.CreateLogger(); } @@ -37,9 +37,15 @@ public Task ReadAsync(InputFormatterContext context) { var body = GetRequestBody(context.HttpContext.Request.Body); - var model = _jsonApiContext.IsRelationshipPath ? - _deSerializer.DeserializeRelationship(body) : - _deSerializer.Deserialize(body); + object model; + if (_jsonApiContext.RequestManager.IsRelationshipPath) + { + model = _deserializer.DeserializeRelationship(body); + } + else + { + model = _deserializer.Deserialize(body); + } if (model == null) _logger?.LogError("An error occurred while de-serializing the payload"); diff --git a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs index f2b1e1aebf..d75cf9818e 100644 --- a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs @@ -1,9 +1,12 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; @@ -17,7 +20,7 @@ public class ServiceDiscoveryFacade { internal static HashSet ServiceInterfaces = new HashSet { typeof(IResourceService<>), - typeof(IResourceService<,>), + typeof(IResourceService<,>), typeof(IResourceCmdService<>), typeof(IResourceCmdService<,>), typeof(IResourceQueryService<>), @@ -46,13 +49,12 @@ public class ServiceDiscoveryFacade typeof(IEntityReadRepository<>), typeof(IEntityReadRepository<,>) }; - private readonly IServiceCollection _services; private readonly IResourceGraphBuilder _graphBuilder; private readonly List _identifiables = new List(); public ServiceDiscoveryFacade( - IServiceCollection services, + IServiceCollection services, IResourceGraphBuilder graphBuilder) { _services = services; @@ -80,13 +82,56 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly) 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) + { + return baseType.Assembly.GetTypes().Where(t => + { + if (t.BaseType != null) + { + return baseType.IsSubclassOf(t); + } + return false; + + }); + } + + private void AddDbContextResolvers(Assembly assembly) { var dbContextTypes = TypeLocator.GetDerivedTypes(assembly, typeof(DbContext)); - foreach(var dbContextType in dbContextTypes) + foreach (var dbContextType in dbContextTypes) { var resolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); _services.AddScoped(typeof(IDbContextResolver), resolverType); @@ -125,7 +170,7 @@ private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor id catch (InvalidOperationException e) { throw new JsonApiSetupException($"Cannot define multiple ResourceDefinition<> implementations for '{identifiable.ResourceType}'", e); - } + } } private void AddResourceToGraph(ResourceDescriptor identifiable) @@ -134,7 +179,7 @@ private void AddResourceToGraph(ResourceDescriptor identifiable) _graphBuilder.AddResource(identifiable.ResourceType, identifiable.IdType, resourceName); } - private string FormatResourceName(Type resourceType) + private string FormatResourceName(Type resourceType) => JsonApiOptions.ResourceNameFormatter.FormatResourceName(resourceType); /// @@ -145,58 +190,55 @@ public ServiceDiscoveryFacade AddServices(Assembly assembly) { var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly); foreach (var resourceDescriptor in resourceDescriptors) + { AddServices(assembly, resourceDescriptor); - + } return this; } private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor) { - - foreach(var serviceInterface in ServiceInterfaces) + foreach (var serviceInterface in ServiceInterfaces) + { RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); - - + } } /// /// Add implementations to container. /// /// The assembly to search for resources in. - public ServiceDiscoveryFacade AddRepositories(Assembly assembly) + public ServiceDiscoveryFacade AddRepositories(Assembly assembly) { var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly); foreach (var resourceDescriptor in resourceDescriptors) + { AddRepositories(assembly, resourceDescriptor); + } return this; } private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor) { - foreach(var serviceInterface in RepositoryInterfaces) + foreach (var serviceInterface in RepositoryInterfaces) + { RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); + } } public int i = 0; private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) { - - - if (resourceDescriptor.IdType == typeof(Guid) && interfaceType.GetTypeInfo().GenericTypeParameters.Length == 1) { - return ; + return; } - var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 - ? new [] { resourceDescriptor.ResourceType, resourceDescriptor.IdType } - : new [] { resourceDescriptor.ResourceType }; - + var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 ? new[] { resourceDescriptor.ResourceType, resourceDescriptor.IdType } : new[] { resourceDescriptor.ResourceType }; var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); - if(service.implementation?.Name == "CustomArticleService" && genericArguments[0].Name != "Article") - { - - service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); - } + //if(service.implementation?.Name == "CustomArticleService" && genericArguments[0].Name != "Article") + //{ + // service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + //} if (service.implementation != null) { _services.AddScoped(service.registrationInterface, service.implementation); diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs index 54febfa45b..2bbb638d5a 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -83,5 +83,8 @@ public interface IResourceGraph /// Was built against an EntityFrameworkCore DbContext ? /// bool UsesDbContext { get; } + + + ContextEntity GetEntityBasedOnPath(string pathParsed); } } diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index 28c13fd885..6e9d3e6a49 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -1,5 +1,6 @@ using System; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; @@ -8,10 +9,22 @@ namespace JsonApiDotNetCore.Internal public class PageManager : IPageManager { private ILinkBuilder _linkBuilder; + private IJsonApiOptions _options; - public PageManager(ILinkBuilder linkBuilder) + public PageManager(ILinkBuilder linkBuilder, IJsonApiOptions options, IRequestManager requestManager) { _linkBuilder = linkBuilder; + _options = options; + if (requestManager.QuerySet != null) + { + PageSize = requestManager.QuerySet?.PageQuery.PageSize != null ? requestManager.QuerySet.PageQuery.PageSize : _options.DefaultPageSize; + } + else + { + PageSize = _options.DefaultPageSize; + } + + DefaultPageSize = _options.DefaultPageSize; } public int? TotalRecords { get; set; } public int PageSize { get; set; } diff --git a/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs b/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs index 341b7e15c0..9f78bd2d5d 100644 --- a/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs @@ -4,10 +4,8 @@ namespace JsonApiDotNetCore.Internal.Query { public class AttrSortQuery : BaseAttrQuery { - public AttrSortQuery( - IJsonApiContext jsonApiContext, - SortQuery sortQuery) - :base(jsonApiContext, sortQuery) + public AttrSortQuery(IJsonApiContext jsonApiContext,SortQuery sortQuery) + :base(jsonApiContext.RequestManager,jsonApiContext.ResourceGraph, sortQuery) { if (Attribute == null) throw new JsonApiException(400, $"'{sortQuery.Attribute}' is not a valid attribute."); diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs index d0cde0495f..fcde60cd27 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs @@ -1,3 +1,5 @@ +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using System; @@ -12,24 +14,19 @@ namespace JsonApiDotNetCore.Internal.Query /// public abstract class BaseAttrQuery { - private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; + private readonly IResourceGraph _resourceGraph; - public BaseAttrQuery(IJsonApiContext jsonApiContext, BaseQuery baseQuery) + public BaseAttrQuery(IRequestManager requestManager, IResourceGraph resourceGraph, BaseQuery baseQuery) { - _jsonApiContext = jsonApiContext ?? throw new ArgumentNullException(nameof(jsonApiContext)); - - if(_jsonApiContext.RequestEntity == null) - throw new ArgumentException($"{nameof(IJsonApiContext)}.{nameof(_jsonApiContext.RequestEntity)} cannot be null. " - + "This property contains the ResourceGraph node for the requested entity. " - + "If this is a unit test, you need to mock this member. " - + "See this issue to check the current status of improved test guidelines: " - + "https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/251", nameof(jsonApiContext)); + _requestManager = requestManager ?? throw new ArgumentNullException(nameof(requestManager)); + _resourceGraph = resourceGraph ?? throw new ArgumentNullException(nameof(resourceGraph)); - if(_jsonApiContext.ResourceGraph == null) - throw new ArgumentException($"{nameof(IJsonApiContext)}.{nameof(_jsonApiContext.ResourceGraph)} cannot be null. " + if(_resourceGraph == null) + throw new ArgumentException($"{nameof(IJsonApiContext)}.{nameof(_resourceGraph)} cannot be null. " + "If this is a unit test, you need to construct a graph containing the resources being tested. " + "See this issue to check the current status of improved test guidelines: " - + "https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/251", nameof(jsonApiContext)); + + "https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/251", nameof(requestManager)); if (baseQuery.IsAttributeOfRelationship) { @@ -56,16 +53,19 @@ public string GetPropertyPath() } private AttrAttribute GetAttribute(string attribute) - => _jsonApiContext.RequestEntity.Attributes.FirstOrDefault(attr => attr.Is(attribute)); + { + return _requestManager.GetContextEntity().Attributes.FirstOrDefault(attr => attr.Is(attribute)); + } private RelationshipAttribute GetRelationship(string propertyName) - => _jsonApiContext.RequestEntity.Relationships.FirstOrDefault(r => r.Is(propertyName)); + { + return _requestManager.GetContextEntity().Relationships.FirstOrDefault(r => r.Is(propertyName)); + } private AttrAttribute GetAttribute(RelationshipAttribute relationship, string attribute) { - var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type); - return relatedContextEntity.Attributes - .FirstOrDefault(a => a.Is(attribute)); + var relatedContextEntity = _resourceGraph.GetContextEntity(relationship.Type); + return relatedContextEntity.Attributes.FirstOrDefault(a => a.Is(attribute)); } } } diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs index f7e308369e..fbf6301bc7 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs @@ -8,7 +8,7 @@ public class BaseFilterQuery : BaseAttrQuery public BaseFilterQuery( IJsonApiContext jsonApiContext, FilterQuery filterQuery) - : base(jsonApiContext, filterQuery) + : base(jsonApiContext.RequestManager, jsonApiContext.ResourceGraph, filterQuery) { PropertyValue = filterQuery.Value; FilterOperation = GetFilterOperation(filterQuery.Operation); diff --git a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs index 4215382c80..8c54581693 100644 --- a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs @@ -7,7 +7,7 @@ public class RelatedAttrSortQuery : BaseAttrQuery public RelatedAttrSortQuery( IJsonApiContext jsonApiContext, SortQuery sortQuery) - :base(jsonApiContext, sortQuery) + :base(jsonApiContext.RequestManager, jsonApiContext.ResourceGraph, sortQuery) { if (Relationship == null) throw new JsonApiException(400, $"{sortQuery.Relationship} is not a valid relationship on {jsonApiContext.RequestEntity.EntityName}."); diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index a08c18eaca..f15138dc46 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -7,8 +7,13 @@ namespace JsonApiDotNetCore.Internal { - internal class ControllerMapping + public class ControllerModelMap { + public Type Controller; + public Type Model; + public string Path { get; set; } + + } /// @@ -19,12 +24,13 @@ public class ResourceGraph : IResourceGraph internal List Entities { get; } internal List ValidationResults { get; } - + internal List ControllerModelMap { get; set; } [Obsolete("please instantiate properly")] internal static IResourceGraph Instance { get; set; } public ResourceGraph() { } + [Obsolete("Use new one")] public ResourceGraph(List entities, bool usesDbContext) { Entities = entities; @@ -38,12 +44,15 @@ public ContextEntity GetEntityType(string entityName) return Entities.Where(e => e.EntityName == entityName).FirstOrDefault(); } + + // 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) + internal ResourceGraph(List entities, bool usesDbContext, List validationResults, List controllerContexts) { + ControllerModelMap = controllerContexts; Entities = entities; UsesDbContext = usesDbContext; ValidationResults = validationResults; @@ -125,6 +134,32 @@ public string GetPublicAttributeName(string internalAttributeName) .PublicAttributeName; } + public ControllerModelMap GetControllerMap(string path) + { + var limitedContexts = ControllerModelMap.Where(cc => cc.Path != null); + foreach(var cc in limitedContexts) + { + if (path.Contains(cc.Path)) + { + return cc; + } + } + return null; + } + + public ContextEntity GetEntityBasedOnPath(string pathParsed) + { + // Check if there is a custom controller registered + var controllerHelper = GetControllerMap(pathParsed); + if (controllerHelper != null) + { + return GetContextEntity(controllerHelper.Model); + } + + var pathSplit = pathParsed.Split('/').ToList(); + + return GetContextEntity(pathSplit[0]); + } } } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs index 41eef8f1b7..0bf235488c 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Services; @@ -23,6 +24,10 @@ public interface IRequestManager : IQueryRequest QuerySet QuerySet { get; set; } IQueryCollection FullQuerySet { get; set; } + /// + /// If the request is on the `{id}/relationships/{relationshipName}` route + /// + bool IsRelationshipPath { get; set; } /// /// Gets the relationships as set in the query parameters /// @@ -40,5 +45,7 @@ public interface IRequestManager : IQueryRequest void SetContextEntity(ContextEntity contextEntityCurrent); ContextEntity GetContextEntity(); + QueryParams DisabledQueryParams { get; set; } + } } diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs index da0e3f105e..f51b22a480 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -1,7 +1,9 @@ +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Text; @@ -18,6 +20,9 @@ class RequestManager : IRequestManager public List IncludedRelationships { get; set; } public QuerySet QuerySet { get; set; } public PageManager PageManager { get; set; } + public IQueryCollection FullQuerySet { get; set; } + public QueryParams DisabledQueryParams { get; set; } + public bool IsRelationshipPath { get; set; } public List GetFields() diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 4173863f9a..6d535b8106 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; @@ -32,7 +33,6 @@ public async Task Invoke(HttpContext context, IQueryParser queryParser, IJsonApiOptions options) { - if (IsValid(context)) { // HACK: this currently results in allocation of @@ -45,7 +45,10 @@ public async Task Invoke(HttpContext context, requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); //Handle all querySet HandleUriParameters(context, queryParser, requestManager); - + requestManager.IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); + // BACKWARD COMPATIBILITY for v4 will be removed in v5 + jsonApiContext.RequestManager = requestManager; + jsonApiContext.PageManager = new PageManager(new LinkBuilder(options, requestManager), options, requestManager); await _next(context); } } @@ -63,7 +66,39 @@ protected void HandleUriParameters(HttpContext context, IQueryParser queryParser requestManager.IncludedRelationships = requestManager.QuerySet.IncludedRelationships; } } + internal static bool PathIsRelationship(string requestPath) + { + // while(!Debugger.IsAttached) { Thread.Sleep(1000); } + const string relationships = "relationships"; + const char pathSegmentDelimiter = '/'; + + var span = requestPath.AsSpan(); + + // we need to iterate over the string, from the end, + // checking whether or not the 2nd to last path segment + // is "relationships" + // -2 is chosen in case the path ends with '/' + for (var i = requestPath.Length - 2; i >= 0; i--) + { + // if there are not enough characters left in the path to + // contain "relationships" + if (i < relationships.Length) + return false; + // we have found the first instance of '/' + if (span[i] == pathSegmentDelimiter) + { + // in the case of a "relationships" route, the next + // path segment will be "relationships" + return ( + span.Slice(i - relationships.Length, relationships.Length) + .SequenceEqual(relationships.AsSpan()) + ); + } + } + + return false; + } private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) { var r = context.Request; @@ -114,10 +149,13 @@ internal static string GetNamespaceFromPath(string path, string entityName) /// private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph, IJsonApiOptions options) { - var pathSplit = path.ToString().Replace($"{options.Namespace}/", "").Split('/'); + var pathParsed = path.ToString().Replace($"{options.Namespace}/", ""); + if(pathParsed[0] == '/') + { + pathParsed = pathParsed.Substring(1); + } + return resourceGraph.GetEntityBasedOnPath(pathParsed); - var typeString = pathSplit[1]; - return resourceGraph.GetEntityType(typeString); } private static bool IsValid(HttpContext context) diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 6aad0e92fb..624d53fdc6 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -75,12 +75,12 @@ public EntityResourceService( IEntityRepository entityRepository, IJsonApiOptions options, IResourceMapper mapper, - IRequestManager queryManager, + IRequestManager requestManager, IPageManager pageManager, ILoggerFactory loggerFactory) { _pageManager = pageManager; - _requestManager = queryManager; + _requestManager = requestManager; _jsonApiContext = jsonApiContext; _options = options; _repository = entityRepository; diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 7d1304e9b3..c39bce76f3 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -15,6 +15,7 @@ namespace JsonApiDotNetCore.Services public interface IJsonApiApplication { IJsonApiOptions Options { get; set; } + [Obsolete("Use standalone resourcegraph")] IResourceGraph ResourceGraph { get; set; } } @@ -139,7 +140,9 @@ public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest public interface IJsonApiContext : IJsonApiRequest { + [Obsolete("Use standalone IRequestManager")] IRequestManager RequestManager { get; set; } + [Obsolete("Use standalone IPageManager")] IPageManager PageManager { get; set; } IJsonApiContext ApplyContext(object controller); IMetaBuilder MetaBuilder { get; set; } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 637080e98f..165a2ee154 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -65,7 +65,6 @@ public JsonApiContext( public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers(); [Obsolete("Please use the standalone Requestmanager")] public IRequestManager RequestManager { get; set; } - IPageManager IJsonApiContext.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } [Obsolete("This is no longer necessary")] @@ -87,7 +86,6 @@ public IJsonApiContext ApplyContext(object controller) IncludedRelationships = QuerySet.IncludedRelationships; } - //PageManager = GetPageManager(); IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); return this; diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 21a66e651b..e067d6d5d9 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using Microsoft.AspNetCore.Http; @@ -17,22 +18,23 @@ public interface IQueryParser public class QueryParser : IQueryParser { - private readonly IControllerContext _controllerContext; + private readonly IRequestManager _requestManager; private readonly IJsonApiOptions _options; public QueryParser( - IControllerContext controllerContext, + IRequestManager requestManager, IJsonApiOptions options) { - _controllerContext = controllerContext; + _requestManager = requestManager; _options = options; } public virtual QuerySet Parse(IQueryCollection query) { var querySet = new QuerySet(); - var disabledQueries = _controllerContext.GetControllerAttribute()?.QueryParams ?? QueryParams.None; + // var disabledQueries = _controllerContext.GetControllerAttribute()?.QueryParams ?? QueryParams.None; + var disabledQueries = QueryParams.None; foreach (var pair in query) { if (pair.Key.StartsWith(QueryConstants.FILTER)) @@ -133,9 +135,11 @@ protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, stri const string NUMBER = "number"; if (propertyName == SIZE) + { pageQuery.PageSize = int.TryParse(value, out var pageSize) ? pageSize : throw new JsonApiException(400, $"Invalid page size '{value}'"); + } else if (propertyName == NUMBER) pageQuery.PageOffset = int.TryParse(value, out var pageOffset) ? @@ -184,8 +188,8 @@ protected virtual List ParseFieldsQuery(string key, string value) var typeName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1]; var includedFields = new List { nameof(Identifiable.Id) }; - var relationship = _controllerContext.RequestEntity.Relationships.SingleOrDefault(a => a.Is(typeName)); - if (relationship == default && string.Equals(typeName, _controllerContext.RequestEntity.EntityName, StringComparison.OrdinalIgnoreCase) == false) + var relationship = _requestManager.GetContextEntity().Relationships.SingleOrDefault(a => a.Is(typeName)); + if (relationship == default && string.Equals(typeName, _requestManager.GetContextEntity().EntityName, StringComparison.OrdinalIgnoreCase) == false) return includedFields; var fields = value.Split(QueryConstants.COMMA); @@ -203,9 +207,9 @@ protected virtual List ParseFieldsQuery(string key, string value) } else { - var attr = _controllerContext.RequestEntity.Attributes.SingleOrDefault(a => a.Is(field)); + var attr = _requestManager.GetContextEntity().Attributes.SingleOrDefault(a => a.Is(field)); if (attr == null) - throw new JsonApiException(400, $"'{_controllerContext.RequestEntity.EntityName}' does not contain '{field}'."); + throw new JsonApiException(400, $"'{_requestManager.GetContextEntity().EntityName}' does not contain '{field}'."); // e.g. "Name" includedFields.Add(attr.InternalAttributeName); @@ -219,14 +223,13 @@ protected virtual AttrAttribute GetAttribute(string propertyName) { try { - return _controllerContext - .RequestEntity + return _requestManager.GetContextEntity() .Attributes .Single(attr => attr.Is(propertyName)); } catch (InvalidOperationException e) { - throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_controllerContext.RequestEntity.EntityName}'", e); + throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_requestManager.GetContextEntity().EntityName}'", e); } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs index 478f40f14f..3b9fd699c7 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs @@ -110,8 +110,7 @@ public async Task CustomRouteControllers_Creates_Proper_Relationship_Links() context.TodoItems.Add(todoItem); await context.SaveChangesAsync(); - var builder = new WebHostBuilder() - .UseStartup(); + var builder = new WebHostBuilder().UseStartup(); var httpMethod = new HttpMethod("GET"); var route = $"/custom/route/todo-items/{todoItem.Id}"; @@ -119,8 +118,10 @@ public async Task CustomRouteControllers_Creates_Proper_Relationship_Links() var client = server.CreateClient(); var request = new HttpRequestMessage(httpMethod, route); - // act & assert + // Act var response = await client.SendAsync(request); + + // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs index 69bcccb2a2..555617ab32 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs @@ -44,15 +44,22 @@ public TodoItemControllerTests(TestFixture fixture) } [Fact] - public async Task Can_Get_TodoItems() + public async Task Can_Get_TodoItems_Paginate_Check() { // Arrange - const int expectedEntitiesPerPage = 5; - var person = new Person(); - var todoItem = _todoItemFaker.Generate(); - todoItem.Owner = person; - _context.TodoItems.Add(todoItem); + _context.TodoItems.RemoveRange(_context.TodoItems.ToList()); _context.SaveChanges(); + int expectedEntitiesPerPage = _jsonApiContext.Options.DefaultPageSize; + var person = new Person(); + var todoItems = _todoItemFaker.Generate(expectedEntitiesPerPage +1); + + foreach (var todoItem in todoItems) + { + todoItem.Owner = person; + _context.TodoItems.Add(todoItem); + _context.SaveChanges(); + + } var httpMethod = new HttpMethod("GET"); var route = "/api/v1/todo-items"; @@ -66,7 +73,7 @@ public async Task Can_Get_TodoItems() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotEmpty(deserializedBody); - Assert.True(deserializedBody.Count <= expectedEntitiesPerPage); + Assert.True(deserializedBody.Count <= expectedEntitiesPerPage, $"There are more items on the page than the default page size. {deserializedBody.Count} > {expectedEntitiesPerPage}"); } [Fact] @@ -96,7 +103,7 @@ public async Task Can_Filter_By_Resource_Id() public async Task Can_Filter_By_Relationship_Id() { // Arrange - var person = new Person(); + var person = new Person(); var todoItem = _todoItemFaker.Generate(); todoItem.Owner = person; _context.TodoItems.Add(todoItem); diff --git a/test/UnitTests/Services/QueryParser_Tests.cs b/test/UnitTests/Services/QueryParser_Tests.cs index c0e5752dad..276cc16d93 100644 --- a/test/UnitTests/Services/QueryParser_Tests.cs +++ b/test/UnitTests/Services/QueryParser_Tests.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -14,12 +15,12 @@ namespace UnitTests.Services { public class QueryParser_Tests { - private readonly Mock _controllerContextMock; + private readonly Mock _requestMock; private readonly Mock _queryCollectionMock; public QueryParser_Tests() { - _controllerContextMock = new Mock(); + _requestMock = new Mock(); _queryCollectionMock = new Mock(); } @@ -35,11 +36,11 @@ public void Can_Build_Filters() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.None)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.None); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -61,11 +62,11 @@ public void Filters_Properly_Parses_DateTime_With_Operation() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.None)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.None); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -88,11 +89,11 @@ public void Filters_Properly_Parses_DateTime_Without_Operation() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.None)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.None); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -114,11 +115,11 @@ public void Can_Disable_Filters() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Filter)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Filter); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -139,11 +140,11 @@ public void Can_Disable_Sort() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Sort)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Sort); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -164,11 +165,11 @@ public void Can_Disable_Include() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Include)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Include); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -189,11 +190,11 @@ public void Can_Disable_Page() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Page)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Page); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -214,11 +215,11 @@ public void Can_Disable_Fields() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Fields)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Fields); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -241,8 +242,8 @@ public void Can_Parse_Fields_Query() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.RequestEntity) + _requestMock + .Setup(m => m.GetContextEntity()) .Returns(new ContextEntity { EntityName = type, @@ -256,7 +257,7 @@ public void Can_Parse_Fields_Query() Relationships = new List() }); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -281,8 +282,8 @@ public void Throws_JsonApiException_If_Field_DoesNotExist() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.RequestEntity) + _requestMock + .Setup(m => m.GetContextEntity()) .Returns(new ContextEntity { EntityName = type, @@ -290,7 +291,7 @@ public void Throws_JsonApiException_If_Field_DoesNotExist() Relationships = new List() }); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act , assert var ex = Assert.Throws(() => queryParser.Parse(_queryCollectionMock.Object)); @@ -312,7 +313,7 @@ public void Can_Parse_Page_Size_Query(string value, int expectedValue, bool shou .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act if (shouldThrow) @@ -342,7 +343,7 @@ public void Can_Parse_Page_Number_Query(string value, int expectedValue, bool sh .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act if (shouldThrow) From afb02f2eb34d951430bf91b7fe76a80df7ef0c70 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Tue, 2 Jul 2019 16:06:49 +0200 Subject: [PATCH 10/10] feat: json api context decoupling mroe and more --- .../Data/DefaultEntityRepository.cs | 55 +++---- .../Hooks/ResourceHookExecutor.cs | 25 +--- .../Hooks/Traversal/ChildNode.cs | 14 +- .../Hooks/Traversal/IEntityNode.cs | 7 +- .../Hooks/Traversal/ITraversalHelper.cs | 26 +++- .../Hooks/Traversal/RootNode.cs | 4 +- .../Hooks/Traversal/TraversalHelper.cs | 33 ++--- .../Managers/Contracts/IRequestManager.cs | 3 + .../Managers/RequestManager.cs | 32 ++++- .../Services/IJsonApiContext.cs | 17 +-- .../Delete/AfterDeleteTests.cs | 8 +- .../ResourceHooks/ResourceHooksTestsSetup.cs | 135 +++++++++--------- 12 files changed, 201 insertions(+), 158 deletions(-) diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 3e5a7b0a89..00169edd0a 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -14,27 +14,7 @@ using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Data { - /// - public class DefaultEntityRepository - : DefaultEntityRepository, - IEntityRepository - where TEntity : class, IIdentifiable - { - public DefaultEntityRepository( - IJsonApiContext jsonApiContext, - IDbContextResolver contextResolver, - ResourceDefinition resourceDefinition = null) - : base(jsonApiContext, contextResolver, resourceDefinition) - { } - public DefaultEntityRepository( - ILoggerFactory loggerFactory, - IJsonApiContext jsonApiContext, - IDbContextResolver contextResolver, - ResourceDefinition resourceDefinition = null) - : base(loggerFactory, jsonApiContext, contextResolver, resourceDefinition) - { } - } /// /// Provides a default repository implementation and is responsible for @@ -52,6 +32,11 @@ public class DefaultEntityRepository private readonly IJsonApiContext _jsonApiContext; private readonly IGenericProcessorFactory _genericProcessorFactory; private readonly ResourceDefinition _resourceDefinition; + + + + + [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( IJsonApiContext jsonApiContext, IDbContextResolver contextResolver, @@ -65,6 +50,7 @@ public DefaultEntityRepository( _resourceDefinition = resourceDefinition; } + [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( ILoggerFactory loggerFactory, IJsonApiContext jsonApiContext, @@ -146,7 +132,7 @@ public virtual async Task GetAndIncludeAsync(TId id, string relationshi /// public virtual async Task CreateAsync(TEntity entity) { - foreach (var relationshipAttr in _jsonApiContext.RelationshipsToUpdate?.Keys) + foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships()?.Keys) { var trackedRelationshipValue = GetTrackedRelationshipValue(relationshipAttr, entity, out bool wasAlreadyTracked); LoadInverseRelationships(trackedRelationshipValue, relationshipAttr); @@ -228,7 +214,7 @@ private bool IsHasOneRelationship(string internalRelationshipName, Type type) public void DetachRelationshipPointers(TEntity entity) { - foreach (var relationshipAttr in _jsonApiContext.RelationshipsToUpdate.Keys) + foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships().Keys) { if (relationshipAttr is HasOneAttribute hasOneAttr) { @@ -271,10 +257,10 @@ public virtual async Task UpdateAsync(TEntity updatedEntity) if (databaseEntity == null) return null; - foreach (var attr in _jsonApiContext.AttributesToUpdate.Keys) + foreach (var attr in _requestManager.GetUpdatedAttributes().Keys) attr.SetValue(databaseEntity, attr.GetValue(updatedEntity)); - foreach (var relationshipAttr in _jsonApiContext.RelationshipsToUpdate?.Keys) + foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships()?.Keys) { /// loads databasePerson.todoItems LoadCurrentRelationships(databaseEntity, relationshipAttr); @@ -584,4 +570,25 @@ private IIdentifiable AttachOrGetTracked(IIdentifiable relationshipValue) return null; } } + /// + public class DefaultEntityRepository + : DefaultEntityRepository, + IEntityRepository + where TEntity : class, IIdentifiable + { + public DefaultEntityRepository( + IJsonApiContext jsonApiContext, + IDbContextResolver contextResolver, + ResourceDefinition resourceDefinition = null) + : base(jsonApiContext, contextResolver, resourceDefinition) + { } + + public DefaultEntityRepository( + ILoggerFactory loggerFactory, + IJsonApiContext jsonApiContext, + IDbContextResolver contextResolver, + ResourceDefinition resourceDefinition = null) + : base(loggerFactory, jsonApiContext, contextResolver, resourceDefinition) + { } + } } diff --git a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs index 9d7fa4e4be..808ed3bc90 100644 --- a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs @@ -15,7 +15,7 @@ namespace JsonApiDotNetCore.Hooks { /// - internal class ResourceHookExecutor : IResourceHookExecutor + internal class ResourceHookExecutor : IResourceHookExecutor { public static readonly IdentifiableComparer Comparer = new IdentifiableComparer(); internal readonly ITraversalHelper _traversalHelper; @@ -32,17 +32,6 @@ public ResourceHookExecutor(IHookExecutorHelper helper, ITraversalHelper travers _traversalHelper = traversalHelper; } - - [Obsolete("Dont use, use consturctor without jsonApiContext")] - public ResourceHookExecutor(IHookExecutorHelper helper, ITraversalHelper traversalHelper, IJsonApiContext context, IRequestManager requestManager) : this(helper, traversalHelper, context.ResourceGraph, context.RequestManager) - { - - } - - - - - /// public virtual void BeforeRead(ResourcePipeline pipeline, string stringId = null) where TEntity : class, IIdentifiable { @@ -201,12 +190,12 @@ bool GetHook(ResourceHook target, IEnumerable entities, } /// - /// Traverses the nodes in a . + /// Traverses the nodes in a . /// - void Traverse(EntityChildLayer currentLayer, ResourceHook target, Action action) + void Traverse(NodeLayer currentLayer, ResourceHook target, Action action) { if (!currentLayer.AnyEntities()) return; - foreach (IEntityNode node in currentLayer) + foreach (INode node in currentLayer) { var entityType = node.EntityType; var hookContainer = _executorHelper.GetResourceHookContainer(entityType, target); @@ -259,9 +248,9 @@ void RecursiveBeforeRead(ContextEntity contextEntity, List relationshipC /// First the BeforeUpdateRelationship should be for owner1, then the /// BeforeImplicitUpdateRelationship hook should be fired for /// owner2, and lastely the BeforeImplicitUpdateRelationship for article2. - void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, EntityChildLayer layer) + void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer layer) { - foreach (IEntityNode node in layer) + foreach (INode node in layer) { var nestedHookcontainer = _executorHelper.GetResourceHookContainer(node.EntityType, ResourceHook.BeforeUpdateRelationship); IEnumerable uniqueEntities = node.UniqueEntities; @@ -461,7 +450,7 @@ IEnumerable LoadDbValues(Type entityType, IEnumerable uniqueEntities, ResourceHo /// /// Fires the AfterUpdateRelationship hook /// - void FireAfterUpdateRelationship(IResourceHookContainer container, IEntityNode node, ResourcePipeline pipeline) + void FireAfterUpdateRelationship(IResourceHookContainer container, INode node, ResourcePipeline pipeline) { Dictionary currenEntitiesGrouped = node.RelationshipsFromPreviousLayer.GetDependentEntities(); diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs index 443ee7daf1..2fa1ae2aa2 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; using JsonApiDotNetCore.Extensions; @@ -7,8 +7,11 @@ namespace JsonApiDotNetCore.Hooks { - /// - internal class ChildNode : IEntityNode where TEntity : class, IIdentifiable + /// + /// Child node in the tree + /// + /// + internal class ChildNode : INode where TEntity : class, IIdentifiable { /// public DependentType EntityType { get; private set; } @@ -51,7 +54,10 @@ public void UpdateUnique(IEnumerable updated) } } - /// + /// + /// Reassignment is done according to provided relationships + /// + /// public void Reassign(IEnumerable updated = null) { var unique = (HashSet)UniqueEntities; diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs index 159b373ef5..00e8a0b8f3 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs @@ -1,9 +1,12 @@ -using System.Collections; +using System.Collections; using DependentType = System.Type; namespace JsonApiDotNetCore.Hooks { - internal interface IEntityNode + /// + /// This is the interface that nodes need to inherit from + /// + internal interface INode { /// /// Each node representes the entities of a given type throughout a particular layer. diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs index 7d20fdf26f..f0449b9756 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs @@ -1,12 +1,30 @@ -using System.Collections.Generic; +using System.Collections.Generic; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Hooks { internal interface ITraversalHelper { - EntityChildLayer CreateNextLayer(IEntityNode rootNode); - EntityChildLayer CreateNextLayer(IEnumerable nodes); + /// + /// Crates the next layer + /// + /// + /// + NodeLayer CreateNextLayer(INode node); + /// + /// Creates the next layer based on the nodes provided + /// + /// + /// + NodeLayer CreateNextLayer(IEnumerable nodes); + /// + /// Creates a root node for breadth-first-traversal (BFS). Note that typically, in + /// JADNC, the root layer will be homogeneous. Also, because it is the first layer, + /// there can be no relationships to previous layers, only to next layers. + /// + /// The root node. + /// Root entities. + /// The 1st type parameter. RootNode CreateRootNode(IEnumerable rootEntities) where TEntity : class, IIdentifiable; } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs index 7783d041e1..1aa3c0eb8b 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -11,7 +11,7 @@ namespace JsonApiDotNetCore.Hooks /// The root node class of the breadth-first-traversal of entity data structures /// as performed by the /// - internal class RootNode : IEntityNode where TEntity : class, IIdentifiable + internal class RootNode : INode where TEntity : class, IIdentifiable { private readonly RelationshipProxy[] _allRelationshipsToNextLayer; private HashSet _uniqueEntities; diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs index a7d6fdb8fb..00c1c4dca8 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs @@ -6,6 +6,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using DependentType = System.Type; @@ -25,7 +26,8 @@ namespace JsonApiDotNetCore.Hooks internal class TraversalHelper : ITraversalHelper { private readonly IResourceGraph _graph; - private readonly IJsonApiContext _context; + private readonly IRequestManager _requestManager; + /// /// Keeps track of which entities has already been traversed through, to prevent /// infinite loops in eg cyclic data structures. @@ -36,13 +38,11 @@ internal class TraversalHelper : ITraversalHelper /// See the latter for more details. /// private readonly Dictionary RelationshipProxies = new Dictionary(); - - public TraversalHelper( IResourceGraph graph, - IJsonApiContext context) + IRequestManager requestManager) { - _context = context; + _requestManager = requestManager; _graph = graph; } @@ -69,9 +69,9 @@ public RootNode CreateRootNode(IEnumerable rootEntiti /// /// The next layer. /// Root node. - public EntityChildLayer CreateNextLayer(IEntityNode rootNode) + public NodeLayer CreateNextLayer(INode rootNode) { - return CreateNextLayer(new IEntityNode[] { rootNode }); + return CreateNextLayer(new INode[] { rootNode }); } /// @@ -79,7 +79,7 @@ public EntityChildLayer CreateNextLayer(IEntityNode rootNode) /// /// The next layer. /// Nodes. - public EntityChildLayer CreateNextLayer(IEnumerable nodes) + public NodeLayer CreateNextLayer(IEnumerable nodes) { /// first extract entities by parsing populated relationships in the entities /// of previous layer @@ -108,7 +108,7 @@ public EntityChildLayer CreateNextLayer(IEnumerable nodes) }).ToList(); /// wrap the child nodes in a EntityChildLayer - return new EntityChildLayer(nextNodes); + return new NodeLayer(nextNodes); } /// @@ -124,7 +124,7 @@ Dictionary - (Dictionary>, Dictionary>) ExtractEntities(IEnumerable principalNodes) + (Dictionary>, Dictionary>) ExtractEntities(IEnumerable principalNodes) { var principalsEntitiesGrouped = new Dictionary>(); // RelationshipAttr_prevlayer->currentlayer => prevLayerEntities var dependentsEntitiesGrouped = new Dictionary>(); // RelationshipAttr_prevlayer->currentlayer => currentLayerEntities @@ -211,6 +211,7 @@ void RegisterRelationshipProxies(DependentType type) DependentType dependentType = GetDependentTypeFromRelationship(attr); bool isContextRelation = false; if (_context.RelationshipsToUpdate != null) isContextRelation = _context.RelationshipsToUpdate.ContainsKey(attr); + if (_context.RelationshipsToUpdate != null) isContextRelation = _context.RelationshipsToUpdate.ContainsKey(attr); var proxy = new RelationshipProxy(attr, dependentType, isContextRelation); RelationshipProxies[attr] = proxy; } @@ -287,10 +288,10 @@ void AddToRelationshipGroup(Dictionary> t /// /// Reflective helper method to create an instance of ; /// - IEntityNode CreateNodeInstance(DependentType nodeType, RelationshipProxy[] relationshipsToNext, IEnumerable relationshipsFromPrev) + INode CreateNodeInstance(DependentType nodeType, RelationshipProxy[] relationshipsToNext, IEnumerable relationshipsFromPrev) { IRelationshipsFromPreviousLayer prev = CreateRelationshipsFromInstance(nodeType, relationshipsFromPrev); - return (IEntityNode)TypeHelper.CreateInstanceOfOpenType(typeof(ChildNode<>), nodeType, new object[] { relationshipsToNext, prev }); + return (INode)TypeHelper.CreateInstanceOfOpenType(typeof(ChildNode<>), nodeType, new object[] { relationshipsToNext, prev }); } /// @@ -318,21 +319,21 @@ IRelationshipGroup CreateRelationshipGroupInstance(Type thisLayerType, Relations /// A helper class that represents all entities in the current layer that /// are being traversed for which hooks will be executed (see IResourceHookExecutor) /// - internal class EntityChildLayer : IEnumerable + internal class NodeLayer : IEnumerable { - readonly List _collection; + readonly List _collection; public bool AnyEntities() { return _collection.Any(n => n.UniqueEntities.Cast().Any()); } - public EntityChildLayer(List nodes) + public NodeLayer(List nodes) { _collection = nodes; } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return _collection.GetEnumerator(); } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs index ac9f05c0c5..6a24b652a6 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -11,6 +12,8 @@ namespace JsonApiDotNetCore.Managers.Contracts { public interface IRequestManager : IQueryRequest { + Dictionary GetUpdatedAttributes(); + Dictionary GetUpdatedRelationships(); /// /// The request namespace. This may be an absolute or relative path /// depending upon the configuration. diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs index f51b22a480..592a55e724 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -2,6 +2,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using System; @@ -10,9 +11,24 @@ namespace JsonApiDotNetCore.Managers { - class RequestManager : IRequestManager + public class UpdatesContainer { + /// + /// The attributes that were included in a PATCH request. + /// Only the attributes in this dictionary should be updated. + /// + public Dictionary Attributes { get; set; } + + /// + /// Any relationships that were included in a PATCH request. + /// Only the relationships in this dictionary should be updated. + /// + public Dictionary Relationships { get; } + } + + class RequestManager : IRequestManager + { private ContextEntity _contextEntity; private IQueryParser _queryParser; @@ -23,8 +39,22 @@ class RequestManager : IRequestManager public IQueryCollection FullQuerySet { get; set; } public QueryParams DisabledQueryParams { get; set; } public bool IsRelationshipPath { get; set; } + public Dictionary AttributesToUpdate { get; set; } + /// + /// Contains all the information you want about any update occuring + /// + private UpdatesContainer _updatesContainer { get; set; } + public Dictionary RelationshipsToUpdate { get; set; } + public Dictionary GetUpdatedAttributes() + { + return _updatesContainer.Attributes; + } + public Dictionary GetUpdatedRelationships() + { + return _updatesContainer.Relationships; + } public List GetFields() { return QuerySet?.Fields; diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 5120163095..d1a7878c3f 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -26,25 +26,10 @@ public interface IQueryRequest PageManager PageManager { get; set; } } - public interface IUpdateRequest - { - /// - /// The attributes that were included in a PATCH request. - /// Only the attributes in this dictionary should be updated. - /// - Dictionary AttributesToUpdate { get; set; } - /// - /// Any relationships that were included in a PATCH request. - /// Only the relationships in this dictionary should be updated. - /// - Dictionary RelationshipsToUpdate { get; } - } - public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRequest + public interface IJsonApiRequest : IJsonApiApplication, IQueryRequest { - - /// /// Stores information to set relationships for the request resource. /// These relationships must already exist and should not be re-created. diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs index 051fea3bba..edc0f6e4ae 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs @@ -13,15 +13,15 @@ public class AfterDeleteTests : HooksTestsSetup [Fact] public void AfterDelete() { - // arrange + // Arrange var discovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); + var (contextMock, hookExecutor, resourceDefinitionMock) = CreateTestObjects(discovery); var todoList = CreateTodoWithOwner(); - // act + // Act hookExecutor.AfterDelete(todoList, ResourcePipeline.Delete, It.IsAny()); - // assert + // Assert resourceDefinitionMock.Verify(rd => rd.AfterDelete(It.IsAny>(), ResourcePipeline.Delete, It.IsAny()), Times.Once()); VerifyNoOtherCalls(resourceDefinitionMock); } diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index d898d1e93b..846ecd659e 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -15,6 +15,8 @@ using System.Collections.Generic; using System.Linq; using Person = JsonApiDotNetCoreExample.Models.Person; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; namespace UnitTests.ResourceHooks { @@ -134,21 +136,30 @@ protected List CreateTodoWithOwner() public class HooksTestsSetup : HooksDummyData { - protected (Mock, IResourceHookExecutor, Mock>) - CreateTestObjects(IHooksDiscovery discovery = null) - where TMain : class, IIdentifiable + (Mock, Mock, Mock, Mock) CreateMocks() + { + var pfMock = new Mock(); + var rgMock = new Mock(); + var rqMock = new Mock(); + var optionsMock = new Mock(new JsonApiOptions { LoadDatabaseValues = false }); + return (rgMock, rqMock, pfMock, optionsMock); + } + + internal (ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery discovery = null) + where TMain : class, IIdentifiable { // creates the resource definition mock and corresponding ImplementedHooks discovery instance var mainResource = CreateResourceDefinition(discovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - (var context, var processorFactory) = CreateContextAndProcessorMocks(); - var traversalHelper = new TraversalHelper(ResourceGraph.Instance, context.Object); + var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); - var meta = new HookExecutorHelper(context.Object.GenericProcessorFactory, ResourceGraph.Instance, context.Object.Options); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, ResourceGraph.Instance, context.Object.RequestManager); + var traversalHelper = new TraversalHelper(rgMock.Object, rqMock.Object); + + var meta = new HookExecutorHelper(gpfMock.Object, rgMock.Object, optionsMock.Object); + var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, rgMock.Object, rqMock.Object); - return (context, hookExecutor, mainResource); + return (hookExecutor, mainResource); } protected (Mock context, IResourceHookExecutor, Mock>, Mock>) @@ -157,20 +168,20 @@ public class HooksTestsSetup : HooksDummyData IHooksDiscovery nestedDiscovery = null, DbContextOptions repoDbContextOptions = null ) - where TMain : class, IIdentifiable - where TNested : class, IIdentifiable + where TMain : class, IIdentifiable + where TNested : class, IIdentifiable { // creates the resource definition mock and corresponding for a given set of discoverable hooks var mainResource = CreateResourceDefinition(mainDiscovery); var nestedResource = CreateResourceDefinition(nestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - (var context, var processorFactory) = CreateContextAndProcessorMocks(); + var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; - var traversalHelper = new TraversalHelper(ResourceGraph.Instance, context.Object); + var traversalHelper = new TraversalHelper(rgMock.Object, rqMock.Object); - SetupProcessorFactoryForResourceDefinition(processorFactory, mainResource.Object, mainDiscovery, context.Object, dbContext); + SetupProcessorFactoryForResourceDefinition(gpfMock.Object, mainResource.Object, mainDiscovery, context.Object, dbContext); var meta = new HookExecutorHelper(context.Object.GenericProcessorFactory, ResourceGraph.Instance, context.Object.Options); var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, ResourceGraph.Instance, context.Object.RequestManager); @@ -181,10 +192,10 @@ public class HooksTestsSetup : HooksDummyData protected (Mock context, IResourceHookExecutor, Mock>, Mock>, Mock>) CreateTestObjects( - IHooksDiscovery mainDiscovery = null, - IHooksDiscovery firstNestedDiscovery = null, - IHooksDiscovery secondNestedDiscovery = null, - DbContextOptions repoDbContextOptions = null + IHooksDiscovery mainDiscovery = null, + IHooksDiscovery firstNestedDiscovery = null, + IHooksDiscovery secondNestedDiscovery = null, + DbContextOptions repoDbContextOptions = null ) where TMain : class, IIdentifiable where TFirstNested : class, IIdentifiable @@ -196,14 +207,14 @@ public class HooksTestsSetup : HooksDummyData var secondNestedResource = CreateResourceDefinition(secondNestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - (var context, var processorFactory) = CreateContextAndProcessorMocks(); + (var context, var processorFactory) = CreateMocks(); var traversalHelper = new TraversalHelper(ResourceGraph.Instance, context.Object); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; SetupProcessorFactoryForResourceDefinition(processorFactory, mainResource.Object, mainDiscovery, context.Object, dbContext); var hookExecutorHelper = new HookExecutorHelper(context.Object.GenericProcessorFactory, ResourceGraph.Instance, context.Object.Options); - var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, context.Object, context.Object.RequestManager); + var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, ResourceGraph.Instance, context.Object.RequestManager); SetupProcessorFactoryForResourceDefinition(processorFactory, firstNestedResource.Object, firstNestedDiscovery, context.Object, dbContext); SetupProcessorFactoryForResourceDefinition(processorFactory, secondNestedResource.Object, secondNestedDiscovery, context.Object, dbContext); @@ -240,8 +251,8 @@ protected void VerifyNoOtherCalls(params dynamic[] resourceMocks) protected DbContextOptions InitInMemoryDb(Action seeder) { var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: "repository_mock") - .Options; + .UseInMemoryDatabase(databaseName: "repository_mock") + .Options; using (var context = new AppDbContext(options)) { @@ -254,70 +265,59 @@ protected DbContextOptions InitInMemoryDb(Action seeder void MockHooks(Mock> resourceDefinition) where TModel : class, IIdentifiable { resourceDefinition - .Setup(rd => rd.BeforeCreate(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entities, context) => entities) - .Verifiable(); + .Setup(rd => rd.BeforeCreate(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entities, context) => entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeRead(It.IsAny(), It.IsAny(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.BeforeRead(It.IsAny(), It.IsAny(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeUpdate(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entityDiff, context) => entityDiff.Entities) - .Verifiable(); + .Setup(rd => rd.BeforeUpdate(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entityDiff, context) => entityDiff.Entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeDelete(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entities, context) => entities) - .Verifiable(); + .Setup(rd => rd.BeforeDelete(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entities, context) => entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeUpdateRelationship(It.IsAny>(), It.IsAny>(), It.IsAny())) - .Returns, IRelationshipsDictionary, ResourcePipeline>((ids, context, helper) => ids) - .Verifiable(); + .Setup(rd => rd.BeforeUpdateRelationship(It.IsAny>(), It.IsAny>(), It.IsAny())) + .Returns, IRelationshipsDictionary, ResourcePipeline>((ids, context, helper) => ids) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeImplicitUpdateRelationship(It.IsAny>(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.BeforeImplicitUpdateRelationship(It.IsAny>(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.OnReturn(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entities, context) => entities) - .Verifiable(); + .Setup(rd => rd.OnReturn(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entities, context) => entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterCreate(It.IsAny>(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterCreate(It.IsAny>(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterRead(It.IsAny>(), It.IsAny(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterRead(It.IsAny>(), It.IsAny(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterUpdate(It.IsAny>(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterUpdate(It.IsAny>(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterDelete(It.IsAny>(), It.IsAny(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterDelete(It.IsAny>(), It.IsAny(), It.IsAny())) + .Verifiable(); } - (Mock, Mock) CreateContextAndProcessorMocks() - { - var processorFactory = new Mock(); - var context = new Mock(); - context.Setup(c => c.GenericProcessorFactory).Returns(processorFactory.Object); - context.Setup(c => c.Options).Returns(new JsonApiOptions { LoadDatabaseValues = false }); - context.Setup(c => c.ResourceGraph).Returns(ResourceGraph.Instance); - return (context, processorFactory); - } void SetupProcessorFactoryForResourceDefinition( - Mock processorFactory, - IResourceHookContainer modelResource, - IHooksDiscovery discovery, - IJsonApiContext apiContext, - AppDbContext dbContext = null + Mock processorFactory, + IResourceHookContainer modelResource, + IHooksDiscovery discovery, + IJsonApiContext apiContext, + AppDbContext dbContext = null ) where TModel : class, IIdentifiable { - processorFactory.Setup(c => c.GetProcessor(typeof(ResourceDefinition<>), typeof(TModel))) - .Returns(modelResource); + processorFactory.Setup(c => c.GetProcessor(typeof(ResourceDefinition<>), typeof(TModel))).Returns(modelResource); - processorFactory.Setup(c => c.GetProcessor(typeof(IHooksDiscovery<>), typeof(TModel))) - .Returns(discovery); + processorFactory.Setup(c => c.GetProcessor(typeof(IHooksDiscovery<>), typeof(TModel))).Returns(discovery); if (dbContext != null) { @@ -326,7 +326,8 @@ void SetupProcessorFactoryForResourceDefinition( { IEntityReadRepository repo = CreateTestRepository(dbContext, apiContext); processorFactory.Setup(c => c.GetProcessor>(typeof(IEntityReadRepository<,>), typeof(TModel), typeof(int))).Returns(repo); - } else + } + else { throw new TypeLoadException("Test not set up properly"); }