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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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/19] 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"); } From 2be340be1edbeeb9e1e92cae5f2964500ea9abff Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 15 Aug 2019 10:50:32 +0200 Subject: [PATCH 11/19] chore: readded solutions --- JsonApiDotnetCore.sln | 376 +++++++++++++++++++++--------------------- 1 file changed, 188 insertions(+), 188 deletions(-) diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index a0330ce005..d28c19a7f4 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -2,16 +2,10 @@ 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 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreExample", "src\Examples\JsonApiDotNetCoreExample\JsonApiDotNetCoreExample.csproj", "{97EE048B-16C0-43F6-BDA9-4E762B2F579F}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExampleTests", "test\JsonApiDotNetCoreExampleTests\JsonApiDotNetCoreExampleTests.csproj", "{0B959765-40D2-43B5-87EE-FE2FEF9DBED5}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C5B4D998-CECB-454D-9F32-085A897577BE}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore @@ -23,31 +17,37 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoEntityFrameworkExample", "src\Examples\NoEntityFrameworkExample\NoEntityFrameworkExample.csproj", "{570165EC-62B5-4684-A139-8D2A30DD4475}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{026FBC6C-AF76-4568-9B87-EC73457899FD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{076E1AE4-FD25-4684-B826-CAAE37FEA0AA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoEntityFrameworkTests", "test\NoEntityFrameworkTests\NoEntityFrameworkTests.csproj", "{73DA578D-A63F-4956-83ED-6D7102E09140}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{9D36BE59-7C14-448B-984D-93A0E7816314}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{6D4BD85A-A262-44C6-8572-FE3A30410BF3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExample", "src\Examples\JsonApiDotNetCoreExample\JsonApiDotNetCoreExample.csproj", "{27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{026FBC6C-AF76-4568-9B87-EC73457899FD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoEntityFrameworkExample", "src\Examples\NoEntityFrameworkExample\NoEntityFrameworkExample.csproj", "{99BAF03C-362B-41FA-9FFF-67F697EFC28C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportsExample", "src\Examples\ReportsExample\ReportsExample.csproj", "{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReportsExample", "src\Examples\ReportsExample\ReportsExample.csproj", "{1CC0831C-ED1D-442E-8421-331D50BD41F1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{076E1AE4-FD25-4684-B826-CAAE37FEA0AA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OperationsExample", "src\Examples\OperationsExample\OperationsExample.csproj", "{3AB43764-C57A-4B75-8C03-C671D3925BF3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{623792C0-5B7D-4D7D-A276-73F908FD4C34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExampleTests", "test\JsonApiDotNetCoreExampleTests\JsonApiDotNetCoreExampleTests.csproj", "{CAF331F8-9255-4D72-A1A8-A54141E99F1E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{1F604666-BB0F-413E-922D-9D37C6073285}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoEntityFrameworkTests", "test\NoEntityFrameworkTests\NoEntityFrameworkTests.csproj", "{4F15A8F8-5BC6-45A1-BC51-03F921B726A4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExample", "src\Examples\OperationsExample\OperationsExample.csproj", "{CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{8788FF65-C2B6-40B2-A3A0-1E3D91C02664}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{65BF5960-3D9B-4230-99F4-A12CAA130792}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{778C4EB9-BD65-4C0F-9230-B5CB1D72186A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{03032A2F-664D-4DD8-A82F-AD8A482EDD85}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" +Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{92BFF50F-BF96-43AD-AB86-A8B861C32412}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -59,181 +59,181 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|x64.ActiveCfg = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|x86.ActiveCfg = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|Any CPU.Build.0 = Release|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|x64.ActiveCfg = Release|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|x86.ActiveCfg = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|x64.ActiveCfg = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|x86.ActiveCfg = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|Any CPU.Build.0 = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|x64.ActiveCfg = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|x86.ActiveCfg = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|x64.ActiveCfg = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|x86.ActiveCfg = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|Any CPU.Build.0 = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|x64.ActiveCfg = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|x86.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|Any CPU.Build.0 = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x64.ActiveCfg = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x64.Build.0 = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x86.ActiveCfg = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x86.Build.0 = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|Any CPU.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|Any CPU.Build.0 = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x64.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x64.Build.0 = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x86.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x86.Build.0 = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|Any CPU.Build.0 = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x64.ActiveCfg = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x64.Build.0 = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x86.ActiveCfg = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x86.Build.0 = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|Any CPU.ActiveCfg = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|Any CPU.Build.0 = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x64.ActiveCfg = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x64.Build.0 = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x86.ActiveCfg = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x86.Build.0 = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x64.ActiveCfg = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x64.Build.0 = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x86.ActiveCfg = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x86.Build.0 = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|Any CPU.Build.0 = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x64.ActiveCfg = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x64.Build.0 = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x86.ActiveCfg = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x86.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x64.ActiveCfg = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x64.Build.0 = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x86.ActiveCfg = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x86.Build.0 = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|Any CPU.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.Build.0 = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.Build.0 = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.ActiveCfg = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.Build.0 = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.Build.0 = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.ActiveCfg = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.Build.0 = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.ActiveCfg = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.Build.0 = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x64.ActiveCfg = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x64.Build.0 = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x86.ActiveCfg = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x86.Build.0 = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|Any CPU.Build.0 = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x64.ActiveCfg = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x64.Build.0 = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x86.ActiveCfg = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x86.Build.0 = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x64.ActiveCfg = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x64.Build.0 = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x86.ActiveCfg = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x86.Build.0 = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|Any CPU.Build.0 = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x64.ActiveCfg = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x64.Build.0 = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x86.ActiveCfg = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x86.Build.0 = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x64.ActiveCfg = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x64.Build.0 = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x86.ActiveCfg = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x86.Build.0 = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|Any CPU.Build.0 = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x64.ActiveCfg = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x64.Build.0 = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x86.ActiveCfg = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x86.Build.0 = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x64.ActiveCfg = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x64.Build.0 = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x86.ActiveCfg = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x86.Build.0 = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|Any CPU.Build.0 = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.ActiveCfg = Release|Any CPU - {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 - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.Build.0 = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x86.ActiveCfg = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x86.Build.0 = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|Any CPU.Build.0 = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.ActiveCfg = Release|Any CPU - {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 + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|x64.ActiveCfg = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|x86.ActiveCfg = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|Any CPU.Build.0 = Release|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|x64.ActiveCfg = Release|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|x86.ActiveCfg = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|x64.ActiveCfg = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|x86.ActiveCfg = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|Any CPU.Build.0 = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|x64.ActiveCfg = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|x86.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x64.ActiveCfg = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x64.Build.0 = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x86.ActiveCfg = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x86.Build.0 = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|Any CPU.Build.0 = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x64.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x64.Build.0 = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x86.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x86.Build.0 = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x64.ActiveCfg = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x64.Build.0 = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x86.ActiveCfg = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x86.Build.0 = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|Any CPU.Build.0 = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x64.ActiveCfg = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x64.Build.0 = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x86.ActiveCfg = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x86.Build.0 = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x64.ActiveCfg = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x64.Build.0 = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x86.ActiveCfg = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x86.Build.0 = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|Any CPU.Build.0 = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x64.ActiveCfg = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x64.Build.0 = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x86.ActiveCfg = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x86.Build.0 = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x64.ActiveCfg = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x64.Build.0 = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x86.ActiveCfg = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x86.Build.0 = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|Any CPU.Build.0 = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x64.ActiveCfg = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x64.Build.0 = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x86.ActiveCfg = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x86.Build.0 = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|x64.ActiveCfg = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|Any CPU.Build.0 = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|x64.ActiveCfg = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|x86.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x64.Build.0 = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x86.Build.0 = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|Any CPU.Build.0 = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x64.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x64.Build.0 = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x86.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x86.Build.0 = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x64.ActiveCfg = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x64.Build.0 = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x86.ActiveCfg = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x86.Build.0 = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|Any CPU.Build.0 = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x64.ActiveCfg = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x64.Build.0 = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x86.ActiveCfg = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x86.Build.0 = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x64.ActiveCfg = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x64.Build.0 = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x86.ActiveCfg = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x86.Build.0 = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|Any CPU.Build.0 = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x64.ActiveCfg = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x64.Build.0 = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x86.ActiveCfg = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x86.Build.0 = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x64.ActiveCfg = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x64.Build.0 = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x86.ActiveCfg = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x86.Build.0 = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|Any CPU.Build.0 = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x64.ActiveCfg = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x64.Build.0 = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x86.ActiveCfg = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x86.Build.0 = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x64.ActiveCfg = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x64.Build.0 = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x86.ActiveCfg = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x86.Build.0 = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|Any CPU.Build.0 = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x64.ActiveCfg = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x64.Build.0 = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.ActiveCfg = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.Build.0 = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.ActiveCfg = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.Build.0 = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.ActiveCfg = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.Build.0 = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.Build.0 = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.ActiveCfg = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.Build.0 = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.ActiveCfg = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.Build.0 = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x64.ActiveCfg = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x64.Build.0 = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x86.ActiveCfg = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x86.Build.0 = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|Any CPU.Build.0 = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x64.ActiveCfg = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x64.Build.0 = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.ActiveCfg = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {C0EC9E70-EB2E-436F-9D94-FA16FA774123} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} - {97EE048B-16C0-43F6-BDA9-4E762B2F579F} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {570165EC-62B5-4684-A139-8D2A30DD4475} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {73DA578D-A63F-4956-83ED-6D7102E09140} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {6D4BD85A-A262-44C6-8572-FE3A30410BF3} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {026FBC6C-AF76-4568-9B87-EC73457899FD} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {1F604666-BB0F-413E-922D-9D37C6073285} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {F4097194-9415-418A-AB4E-315C5D5466AF} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {6DFA30D7-1679-4333-9779-6FB678E48EF5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {09C0C8D8-B721-4955-8889-55CB149C3B5C} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {9D36BE59-7C14-448B-984D-93A0E7816314} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {99BAF03C-362B-41FA-9FFF-67F697EFC28C} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {1CC0831C-ED1D-442E-8421-331D50BD41F1} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {3AB43764-C57A-4B75-8C03-C671D3925BF3} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {623792C0-5B7D-4D7D-A276-73F908FD4C34} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {CAF331F8-9255-4D72-A1A8-A54141E99F1E} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {65BF5960-3D9B-4230-99F4-A12CAA130792} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {03032A2F-664D-4DD8-A82F-AD8A482EDD85} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4} From 6796295ab9c669d037fc87f79eba2853b47523d4 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 2 Sep 2019 11:58:44 +0200 Subject: [PATCH 12/19] feat: upgrading to 2.2, setting contextentity in middleware --- Directory.Build.props | 12 +-- JsonApiDotnetCore.sln | 30 +++---- .../JsonApiDeserializer_Benchmarks.cs | 6 +- .../GettingStarted/GettingStarted.csproj | 6 +- .../ModelDefinition.cs | 4 - .../JsonApiDotNetCoreExample.csproj | 4 +- .../Builders/DocumentBuilder.cs | 4 +- src/JsonApiDotNetCore/Builders/LinkBuilder.cs | 24 ++++-- .../Builders/ResourceGraphBuilder.cs | 17 ++-- .../IApplicationBuilderExtensions.cs | 6 ++ .../IServiceCollectionExtensions.cs | 3 + .../Internal/Contracts/IResourceGraph.cs | 2 +- .../Internal/ResourceGraph.cs | 23 +++-- .../Internal/RouteMatcher.cs | 10 +++ .../JsonApiDotNetCore.csproj | 5 +- .../Managers/RequestManager.cs | 6 +- .../Middleware/RequestMiddleware.cs | 85 ++++++++++++------- .../Serialization/JsonApiSerializer.cs | 1 + .../Services/EntityResourceService.cs | 24 +----- .../Services/JsonApiContext.cs | 6 +- .../ServiceDiscoveryFacadeTests.cs | 12 ++- .../JsonApiDotNetCoreExampleTests.csproj | 1 + .../Builders/DocumentBuilder_Tests.cs | 47 +++++----- test/UnitTests/Builders/LinkBuilderTests.cs | 49 +++++++++++ test/UnitTests/Builders/LinkBuilder_Tests.cs | 49 ----------- .../BaseJsonApiController_Tests.cs | 14 +-- .../IServiceCollectionExtensionsTests.cs | 3 +- .../ResourceHooks/ResourceHooksTestsSetup.cs | 61 +++++++------ .../Serialization/JsonApiDeSerializerTests.cs | 7 +- .../Serialization/JsonApiSerializerTests.cs | 20 ++--- test/UnitTests/Services/QueryParserTests.cs | 2 +- test/UnitTests/UnitTests.csproj | 3 +- 32 files changed, 295 insertions(+), 251 deletions(-) create mode 100644 src/JsonApiDotNetCore/Internal/RouteMatcher.cs create mode 100644 test/UnitTests/Builders/LinkBuilderTests.cs delete mode 100644 test/UnitTests/Builders/LinkBuilder_Tests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 0d034e0c5d..fcd668b5ba 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,14 +4,14 @@ netcoreapp2.0 netstandard2.0 - 2.1.0 + 2.* - 2.1.0 - 2.1.0 - 2.1.0 + 2.* + 2.* + 2.* - 2.1.0 - 2.1.0 + 2.* + 2.* 4.0.0 2.1.0 diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index d28c19a7f4..a0be085cb2 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -45,10 +45,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{03032A2F-664D-4DD8-A82F-AD8A482EDD85}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{92BFF50F-BF96-43AD-AB86-A8B861C32412}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -191,18 +191,6 @@ Global {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x64.Build.0 = Release|Any CPU {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.ActiveCfg = Release|Any CPU {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.Build.0 = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.ActiveCfg = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.Build.0 = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.ActiveCfg = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.Build.0 = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.Build.0 = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.ActiveCfg = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.Build.0 = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.ActiveCfg = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.Build.0 = Release|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.Build.0 = Debug|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -215,6 +203,18 @@ Global {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x64.Build.0 = Release|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.ActiveCfg = Release|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.Build.0 = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x64.Build.0 = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x86.Build.0 = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|Any CPU.Build.0 = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x64.ActiveCfg = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x64.Build.0 = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x86.ActiveCfg = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -233,7 +233,7 @@ Global {65BF5960-3D9B-4230-99F4-A12CAA130792} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {778C4EB9-BD65-4C0F-9230-B5CB1D72186A} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {03032A2F-664D-4DD8-A82F-AD8A482EDD85} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4} diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index 983bc07f90..03b4878aa6 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -4,7 +4,6 @@ using BenchmarkDotNet.Attributes.Exporters; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; @@ -12,7 +11,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace Benchmarks.Serialization { +namespace Benchmarks.Serialization +{ [MarkdownExporter] public class JsonApiDeserializer_Benchmarks { private const string TYPE_NAME = "simple-types"; @@ -39,7 +39,7 @@ public JsonApiDeserializer_Benchmarks() { var jsonApiContextMock = new Mock(); jsonApiContextMock.SetupAllProperties(); jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + jsonApiContextMock.Setup(m => m.RequestManager.GetUpdatedAttributes()).Returns(new Dictionary()); var jsonApiOptions = new JsonApiOptions(); jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj index ece976d5e5..e29e94ce6a 100644 --- a/src/Examples/GettingStarted/GettingStarted.csproj +++ b/src/Examples/GettingStarted/GettingStarted.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs index b700022eba..d31458250c 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs @@ -1,9 +1,5 @@ using System.Collections.Generic; -using JsonApiDotNetCore.Internal; -<<<<<<< HEAD using JsonApiDotNetCore.Internal.Contracts; -======= ->>>>>>> master using JsonApiDotNetCore.Models; namespace GettingStarted.ResourceDefinitionExample diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj index d56e91f21e..92f1bf4fa0 100644 --- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj +++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index d8c7118ae8..426b61c185 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -30,9 +30,9 @@ public DocumentBuilder( IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null, IScopedServiceProvider scopedServiceProvider = null) { - _requestManager = requestManager; _pageManager = pageManager; _jsonApiContext = jsonApiContext; + _requestManager = requestManager ?? jsonApiContext.RequestManager; _resourceGraph = jsonApiContext.ResourceGraph; _requestMeta = requestMeta; _documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions(); @@ -219,7 +219,7 @@ private List GetIncludedEntities(List included, { if (_requestManager.IncludedRelationships != null) { - foreach (var relationshipName in _jsonApiContext.RequestManager.IncludedRelationships) + foreach (var relationshipName in _requestManager.IncludedRelationships) { var relationshipChain = relationshipName.Split('.'); diff --git a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs index f06134d397..9738065ec3 100644 --- a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs @@ -1,38 +1,44 @@ -using System; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; -using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Builders { public class LinkBuilder : ILinkBuilder { - private IRequestManager _requestManager; - private IJsonApiOptions _options; + private readonly IRequestManager _requestManager; + private readonly IJsonApiOptions _options; public LinkBuilder(IJsonApiOptions options, IRequestManager requestManager) { - _requestManager = requestManager; _options = options; + _requestManager = requestManager; } - + /// public string GetSelfRelationLink(string parent, string parentId, string child) { - return $"{_requestManager.BasePath}/{parent}/{parentId}/relationships/{child}"; + return $"{GetBasePath()}/{parent}/{parentId}/relationships/{child}"; } + /// public string GetRelatedRelationLink(string parent, string parentId, string child) { - return $"{_requestManager.BasePath}/{parent}/{parentId}/{child}"; + return $"{GetBasePath()}/{parent}/{parentId}/{child}"; } + /// public string GetPageLink(int pageOffset, int pageSize) { var filterQueryComposer = new QueryComposer(); var filters = filterQueryComposer.Compose(_requestManager); - return $"{_requestManager.BasePath}/{_requestManager.GetContextEntity().EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; + return $"{GetBasePath()}/{_requestManager.GetContextEntity().EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; + } + + private string GetBasePath() + { + if (_options.RelativeLinks) return string.Empty; + return _requestManager.BasePath; } } } diff --git a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs index 500446a7c5..2c9d2677ea 100644 --- a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs @@ -9,7 +9,6 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; -using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -33,20 +32,20 @@ public IResourceGraph Build() // this must be done at build so that call order doesn't matter _entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType)); - List controllerContexts = new List() { }; + List controllerContexts = new List() { }; foreach(var cm in _controllerMapper) { var model = cm.Key; - foreach(var controller in cm.Value) + foreach (var controller in cm.Value) { - var routeAttribute = controller.GetCustomAttribute(); - - controllerContexts.Add(new ControllerModelMap + var controllerName = controller.Name.Replace("Controller", ""); + + controllerContexts.Add(new ControllerResourceMap { - Model = model, - Controller = controller, - Path = routeAttribute?.Template + Resource = model, + ControllerName = controllerName, }); + } } var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults, controllerContexts); diff --git a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs index 91f7baf180..44c741043c 100644 --- a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs @@ -5,8 +5,10 @@ using JsonApiDotNetCore.Middleware; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Routing; namespace JsonApiDotNetCore.Extensions { @@ -18,10 +20,14 @@ public static IApplicationBuilder UseJsonApi(this IApplicationBuilder app, bool DisableDetailedErrorsIfProduction(app); LogResourceGraphValidations(app); + app.UseEndpointRouting(); + app.UseMiddleware(); if (useMvc) + { app.UseMvc(); + } using (var scope = app.ApplicationServices.CreateScope()) { diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 0d20148e82..61b5b329b3 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace JsonApiDotNetCore.Extensions { @@ -79,6 +80,7 @@ private static void AddMvcOptions(MvcOptions options, JsonApiOptions config) options.Filters.Add(typeof(JsonApiExceptionFilter)); options.Filters.Add(typeof(TypeMatchFilter)); options.SerializeAsJsonApi(config); + } public static void AddJsonApiInternals( @@ -170,6 +172,7 @@ public static void AddJsonApiInternals( services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor)); services.AddTransient(); } + services.AddTransient(); services.AddScoped(); } diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs index 71819d2666..39710ce616 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -88,6 +88,6 @@ public interface IResourceGraph bool UsesDbContext { get; } List IncludedRelationships { get; set; } - ContextEntity GetEntityBasedOnPath(string pathParsed); + ContextEntity GetEntityFromControllerName(string pathParsed); } } diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index 5659687994..770661d3e4 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -7,11 +7,10 @@ namespace JsonApiDotNetCore.Internal { - public class ControllerModelMap + public class ControllerResourceMap { - public Type Controller { get; set; } - public Type Model { get; set; } - public string Path { get; set; } + public string ControllerName { get; set; } + public Type Resource { get; set; } } /// @@ -22,7 +21,7 @@ public class ResourceGraph : IResourceGraph internal List Entities { get; } internal List ValidationResults { get; } - public List ControllerModelMap { get; internal set; } + public List ControllerResourceMap { get; internal set; } [Obsolete("please instantiate properly, dont use the static constructor")] internal static IResourceGraph Instance { get; set; } @@ -46,9 +45,9 @@ public ContextEntity GetEntityType(string entityName) // to avoid breaking changes, we will be leaving the original constructor in place // until the context graph validation process is completed // you can track progress on this issue here: https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170 - internal ResourceGraph(List entities, bool usesDbContext, List validationResults, List controllerContexts) + internal ResourceGraph(List entities, bool usesDbContext, List validationResults, List controllerContexts) { - ControllerModelMap = controllerContexts; + ControllerResourceMap = controllerContexts; Entities = entities; UsesDbContext = usesDbContext; ValidationResults = validationResults; @@ -137,13 +136,11 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati return GetContextEntity(relationship.DependentType).Relationships.SingleOrDefault(r => r.InternalRelationshipName == relationship.InverseNavigation); } - public ContextEntity GetEntityBasedOnPath(string pathParsed) + public ContextEntity GetEntityFromControllerName(string controllerName) { - var controllerMatches = ControllerModelMap.Where(cm => cm.Controller.Name.ToLower().Contains(pathParsed.ToLower())); - - var model = controllerMatches.First().Model; - - return Entities.Where(e => e.EntityType == model).First(); + var resource = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource; + if (resource == null) return null; + return Entities.First(e => e.EntityType == resource); } } } diff --git a/src/JsonApiDotNetCore/Internal/RouteMatcher.cs b/src/JsonApiDotNetCore/Internal/RouteMatcher.cs new file mode 100644 index 0000000000..4c5771ade1 --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/RouteMatcher.cs @@ -0,0 +1,10 @@ +using System; +namespace JsonApiDotNetCore.Internal +{ + public class RouteMatcher + { + public RouteMatcher() + { + } + } +} diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index dcfe030039..1ae5427196 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -27,7 +27,7 @@ - + @@ -44,7 +44,4 @@ - - - diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs index 592a55e724..8aad3794de 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -17,13 +17,13 @@ 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; } + public Dictionary Attributes { get; set; } = new Dictionary(); /// /// Any relationships that were included in a PATCH request. /// Only the relationships in this dictionary should be updated. /// - public Dictionary Relationships { get; } + public Dictionary Relationships { get; } = new Dictionary(); } @@ -43,7 +43,7 @@ class RequestManager : IRequestManager /// /// Contains all the information you want about any update occuring /// - private UpdatesContainer _updatesContainer { get; set; } + private UpdatesContainer _updatesContainer { get; set; } = new UpdatesContainer(); public Dictionary RelationshipsToUpdate { get; set; } diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 9d42f7da0b..25147cb22c 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; namespace JsonApiDotNetCore.Middleware @@ -20,36 +21,56 @@ namespace JsonApiDotNetCore.Middleware public class RequestMiddleware { private readonly RequestDelegate _next; + private IResourceGraph _resourceGraph; + private HttpContext _httpContext; + private IJsonApiOptions _options; + private IJsonApiContext _jsonApiContext; + private IRequestManager _requestManager; + private IQueryParser _queryParser; public RequestMiddleware(RequestDelegate next) { _next = next; } - public async Task Invoke(HttpContext context, + public async Task Invoke(HttpContext httpContext, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IQueryParser queryParser, IJsonApiOptions options) { - if (IsValid(context)) + _httpContext = httpContext; + _jsonApiContext = jsonApiContext; + _resourceGraph = resourceGraph; + _requestManager = requestManager; + _queryParser = queryParser; + _options = options; + + if (IsValid()) { + // HACK: this currently results in allocation of // objects that may or may not be used and even double allocation // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); - 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); - 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); + ContextEntity contextEntityCurrent = GetCurrentEntity(); + // the contextEntity is null eg when we're using a non-JsonApiDotNetCore route. + if (contextEntityCurrent != null) + { + requestManager.SetContextEntity(contextEntityCurrent); + // TODO: this does not need to be reset every request: we shouldn't need to rely on an external request to figure out the basepath of current application + requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); + //Handle all querySet + HandleUriParameters(); + requestManager.IsRelationshipPath = PathIsRelationship(); + // BACKWARD COMPATIBILITY for v4 will be removed in v5 + jsonApiContext.RequestManager = requestManager; + jsonApiContext.PageManager = new PageManager(new LinkBuilder(options, requestManager), options, requestManager); + } + + await _next(httpContext); } } /// @@ -57,17 +78,19 @@ public async Task Invoke(HttpContext context, /// /// /// - protected void HandleUriParameters(HttpContext context, IQueryParser queryParser, IRequestManager requestManager) + protected void HandleUriParameters() { - if (context.Request.Query.Count > 0) + if (_httpContext.Request.Query.Count > 0) { //requestManager.FullQuerySet = context.Request.Query; - requestManager.QuerySet = queryParser.Parse(context.Request.Query); - requestManager.IncludedRelationships = requestManager.QuerySet.IncludedRelationships; + _requestManager.QuerySet = _queryParser.Parse(_httpContext.Request.Query); + _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; } } - internal static bool PathIsRelationship(string requestPath) + + protected bool PathIsRelationship() { + string requestPath = _httpContext.Request.Path.Value; // while(!Debugger.IsAttached) { Thread.Sleep(1000); } const string relationships = "relationships"; const char pathSegmentDelimiter = '/'; @@ -99,10 +122,10 @@ internal static bool PathIsRelationship(string requestPath) return false; } - private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) + private string GetBasePath(string entityName) { - var r = context.Request; - if (options.RelativeLinks) + var r = _httpContext.Request; + if (_options.RelativeLinks) { return GetNamespaceFromPath(r.Path, entityName); } @@ -147,22 +170,18 @@ internal static string GetNamespaceFromPath(string path, string entityName) /// /// /// - private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph, IJsonApiOptions options) + private ContextEntity GetCurrentEntity() { - var pathParsed = path.ToString().Replace($"{options.Namespace}/", ""); - if(pathParsed[0] == '/') - { - pathParsed = pathParsed.Substring(1); - } - return resourceGraph.GetEntityBasedOnPath(pathParsed); + var controllerName = (string)_httpContext.GetRouteData().Values["controller"]; + return _resourceGraph.GetEntityFromControllerName(controllerName); } - private static bool IsValid(HttpContext context) + private bool IsValid() { - return IsValidContentTypeHeader(context) && IsValidAcceptHeader(context); + return IsValidContentTypeHeader(_httpContext) && IsValidAcceptHeader(_httpContext); } - private static bool IsValidContentTypeHeader(HttpContext context) + private bool IsValidContentTypeHeader(HttpContext context) { var contentType = context.Request.ContentType; if (contentType != null && ContainsMediaTypeParameters(contentType)) @@ -173,7 +192,7 @@ private static bool IsValidContentTypeHeader(HttpContext context) return true; } - private static bool IsValidAcceptHeader(HttpContext context) + private bool IsValidAcceptHeader(HttpContext context) { if (context.Request.Headers.TryGetValue(Constants.AcceptHeader, out StringValues acceptHeaders) == false) return true; @@ -189,7 +208,7 @@ private static bool IsValidAcceptHeader(HttpContext context) return true; } - internal static bool ContainsMediaTypeParameters(string mediaType) + internal bool ContainsMediaTypeParameters(string mediaType) { var incomingMediaTypeSpan = mediaType.AsSpan(); @@ -208,7 +227,7 @@ internal static bool ContainsMediaTypeParameters(string mediaType) ); } - private static void FlushResponse(HttpContext context, int statusCode) + private void FlushResponse(HttpContext context, int statusCode) { context.Response.StatusCode = statusCode; context.Response.Body.Flush(); diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index 2729e1ec94..f3db8e866b 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -21,6 +21,7 @@ public JsonApiSerializer( IDocumentBuilder documentBuilder) { _jsonApiContext = jsonApiContext; + _requestManager = jsonApiContext.RequestManager; _documentBuilder = documentBuilder; } diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index b73ba16ec8..d783d2df47 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -33,16 +33,6 @@ public class EntityResourceService : private readonly IResourceMapper _mapper; private readonly IResourceHookExecutor _hookExecutor; - /// - /// Base constructor where we assign the variables - /// - /// - /// - /// - /// - /// - /// - /// public EntityResourceService( IEntityRepository repository, IJsonApiOptions options, @@ -402,14 +392,8 @@ public class EntityResourceService : EntityResourceService { /// - /// Constructor for no mapping with integer as dfeault + /// Constructor for no mapping with integer as default /// - /// - /// - /// - /// - /// - /// public EntityResourceService( IEntityRepository repository, IJsonApiOptions options, @@ -421,8 +405,8 @@ public EntityResourceService( base(repository: repository, apiOptions: options, requestManager, resourceGraph, pageManager, loggerFactory, hookExecutor) { } - [Obsolete("Dont use this constructor, use the one without JsonApiContext instead")] - public EntityResourceService( - IJsonApiContext context, IEntityRepository repository) : this(repository, context.Options, context.RequestManager, context.PageManager, context.ResourceGraph) { } + //[Obsolete("Dont use this constructor, use the one without JsonApiContext instead")] + //public EntityResourceService( + // IJsonApiContext context, IEntityRepository repository) : this(repository, context.Options, context.RequestManager, context.PageManager, context.ResourceGraph) { } } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 6d7778c3b2..b235ffc197 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -48,10 +48,13 @@ public JsonApiContext( [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } - [Obsolete("Please us the IRequestManager")] + [Obsolete("Use IRequestManager")] public QuerySet QuerySet { get; set; } + [Obsolete("Use IRequestManager")] public bool IsRelationshipData { get; set; } + [Obsolete("Use IRequestManager")] public bool IsRelationshipPath { get; private set; } + [Obsolete("Use IRequestManager")] public List IncludedRelationships { get; set; } public IPageManager PageManager { get; set; } public IMetaBuilder MetaBuilder { get; set; } @@ -136,6 +139,7 @@ internal static bool PathIsRelationship(string requestPath) public void BeginOperation() { + RequestManager.IncludedRelationships = new List(); IncludedRelationships = new List(); AttributesToUpdate = new Dictionary(); HasManyRelationshipPointers = new HasManyRelationshipPointers(); diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 440bf9bd83..872b9f693b 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -1,12 +1,17 @@ using GettingStarted.Models; using GettingStarted.ResourceDefinitionExample; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Hooks; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -83,8 +88,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; + + public TestModelService(IEntityRepository repository, IJsonApiOptions options, IRequestManager requestManager, IPageManager pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, IResourceHookExecutor hookExecutor = null) : base(repository, options, requestManager, pageManager, resourceGraph, loggerFactory, hookExecutor) + { + } } public class TestModelRepository : DefaultEntityRepository diff --git a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj index b4fcaf7ae0..fb3fac898d 100644 --- a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj +++ b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj @@ -24,6 +24,7 @@ + diff --git a/test/UnitTests/Builders/DocumentBuilder_Tests.cs b/test/UnitTests/Builders/DocumentBuilder_Tests.cs index 00b3b57b16..19018e1a62 100644 --- a/test/UnitTests/Builders/DocumentBuilder_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilder_Tests.cs @@ -62,12 +62,17 @@ public DocumentBuilder_Tests() public void Includes_Paging_Links_By_Default() { // arrange - _pageManager.PageSize = 1; - _pageManager.TotalRecords = 1; - _pageManager.CurrentPage = 1; - var documentBuilder = GetDocumentBuilder(); + var rmMock = new Mock(); + rmMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { EntityName = "resources" }); + var rm = rmMock.Object; + var options = new JsonApiOptions { RelativeLinks = false }; + var pg = new PageManager(new LinkBuilder(options, rm), options, rm); + pg.PageSize = 1; + pg.TotalRecords = 1; + pg.CurrentPage = 1; + var documentBuilder = GetDocumentBuilder(pageManager: pg); var entity = new Model(); // act @@ -94,7 +99,7 @@ public void Page_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model(); // act @@ -116,7 +121,7 @@ public void Related_Links_Can_Be_Disabled() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model(); // act @@ -140,7 +145,7 @@ public void Related_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new RelatedModel(); // act @@ -161,7 +166,7 @@ public void Related_Data_Included_In_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model { RelatedModel = new RelatedModel @@ -193,7 +198,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model { RelatedModelId = relatedId @@ -215,7 +220,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() public void Build_Can_Build_Arrays() { var entities = new[] { new Model() }; - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var documents = documentBuilder.Build(entities); @@ -226,7 +231,7 @@ public void Build_Can_Build_Arrays() public void Build_Can_Build_CustomIEnumerables() { var entities = new Models(new[] { new Model() }); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var documents = documentBuilder.Build(entities); @@ -252,7 +257,8 @@ public void DocumentBuilderOptions( .Returns(new DocumentBuilderOptions(omitNullValuedAttributes.Value)); } var pageManagerMock = new Mock(); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, null, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); + var requestManagerMock = new Mock(); + var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, requestManagerMock.Object, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); var document = documentBuilder.Build(new Model() { StringProperty = attributeValue }); Assert.Equal(resultContainsAttribute, document.Data.Attributes.ContainsKey("StringProperty")); @@ -332,7 +338,7 @@ public void Build_Will_Use_Resource_If_Defined_For_Single_Document() .AddSingleton(resourceGraph) .BuildServiceProvider()); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider); var documents = documentBuilder.Build(entity); @@ -355,7 +361,7 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Multiple_Do .AddSingleton(resourceGraph) .BuildServiceProvider()); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider); var documents = documentBuilder.Build(entities); @@ -379,7 +385,7 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Single_Docu .AddSingleton(resourceGraph) .BuildServiceProvider()); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider); var documents = documentBuilder.Build(entity); @@ -412,20 +418,17 @@ public UserResource(IResourceGraph graph) : base(graph) protected override List OutputAttrs() => Remove(user => user.Password); } - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock = null, TestScopedServiceProvider scopedServiceProvider = null) + private DocumentBuilder GetDocumentBuilder(TestScopedServiceProvider scopedServiceProvider = null, IPageManager pageManager = 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 + if (pageManager != null) { - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + return new DocumentBuilder(_jsonApiContextMock.Object, pageManager, rmMock.Object, scopedServiceProvider: scopedServiceProvider); } + return new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); } } } diff --git a/test/UnitTests/Builders/LinkBuilderTests.cs b/test/UnitTests/Builders/LinkBuilderTests.cs new file mode 100644 index 0000000000..ae8b3ef68e --- /dev/null +++ b/test/UnitTests/Builders/LinkBuilderTests.cs @@ -0,0 +1,49 @@ +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; +using Moq; +using Xunit; + +namespace UnitTests +{ + public class LinkBuilderTests + { + private readonly Mock _requestManagerMock = new Mock(); + private const string _host = "http://www.example.com"; + + + public LinkBuilderTests() + { + _requestManagerMock.Setup(m => m.BasePath).Returns(_host); + _requestManagerMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { EntityName = "articles" }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetPageLink_GivenRelativeConfiguration_ReturnsExpectedPath(bool isRelative) + { + //arrange + var options = new JsonApiOptions { RelativeLinks = isRelative }; + var linkBuilder = new LinkBuilder(options, _requestManagerMock.Object); + var pageSize = 10; + var pageOffset = 20; + var expectedLink = $"/articles?page[size]={pageSize}&page[number]={pageOffset}"; + + // act + var link = linkBuilder.GetPageLink(pageOffset, pageSize); + + // assert + if (isRelative) + { + Assert.Equal(expectedLink, link); + } else + { + Assert.Equal(_host + expectedLink, link); + } + } + + /// todo: write tests for remaining linkBuilder methods + } +} diff --git a/test/UnitTests/Builders/LinkBuilder_Tests.cs b/test/UnitTests/Builders/LinkBuilder_Tests.cs deleted file mode 100644 index 1e9fdddda3..0000000000 --- a/test/UnitTests/Builders/LinkBuilder_Tests.cs +++ /dev/null @@ -1,49 +0,0 @@ -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Services; -using Microsoft.AspNetCore.Http; -using Moq; -using Xunit; - -namespace UnitTests -{ - public class LinkBuilder_Tests - { - [Theory] - [InlineData("http", "localhost", "/api/v1/articles", false, "http://localhost/api/v1")] - [InlineData("https", "localhost", "/api/v1/articles", false, "https://localhost/api/v1")] - [InlineData("http", "example.com", "/api/v1/articles", false, "http://example.com/api/v1")] - [InlineData("https", "example.com", "/api/v1/articles", false, "https://example.com/api/v1")] - [InlineData("https", "example.com", "/articles", false, "https://example.com")] - [InlineData("https", "example.com", "/articles", true, "")] - [InlineData("https", "example.com", "/api/v1/articles", true, "/api/v1")] - 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 - //}); - - //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 linkBuilder = new LinkBuilder(jsonApiContextMock.Object); - - //// act - //var basePath = linkBuilder.GetBasePath(contextMock.Object, resource); - - //// 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 d6c5e951b8..14bf06e3d5 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using JsonApiDotNetCore.Internal.Contracts; +using System.IO; namespace UnitTests { @@ -241,11 +242,10 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - //_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() }; + serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); + // act var response = await controller.PostAsync(resource); @@ -261,12 +261,12 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateEnabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - //_resourceGraph.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); - //_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); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, _resourceGraph.Object, create: serviceMock.Object); + controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); + serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); + // act var response = await controller.PostAsync(resource); diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index 41d546faf6..3da8339437 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -19,6 +19,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Internal.Contracts; + namespace UnitTests.Extensions { public class IServiceCollectionExtensionsTests @@ -42,7 +43,7 @@ public void AddJsonApiInternals_Adds_All_Required_Services() // assert Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService(typeof(IEntityRepository))); - Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index 5eae120747..8b2f61b5a7 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -22,6 +22,7 @@ namespace UnitTests.ResourceHooks { public class HooksDummyData { + protected IResourceGraph _graph; protected ResourceHook[] NoHooks = new ResourceHook[0]; protected ResourceHook[] EnableDbValues = { ResourceHook.BeforeUpdate, ResourceHook.BeforeUpdateRelationship }; protected ResourceHook[] DisableDbValues = new ResourceHook[0]; @@ -34,15 +35,15 @@ public class HooksDummyData protected readonly Faker _passportFaker; public HooksDummyData() { - new ResourceGraphBuilder() - .AddResource() - .AddResource() - .AddResource() - .AddResource
() - .AddResource() - .AddResource() - .AddResource() - .Build(); + _graph = new ResourceGraphBuilder() + .AddResource() + .AddResource() + .AddResource() + .AddResource
() + .AddResource() + .AddResource() + .AddResource() + .Build(); _todoFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); _personFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); @@ -136,28 +137,32 @@ protected List CreateTodoWithOwner() public class HooksTestsSetup : HooksDummyData { - (Mock, Mock, Mock, Mock) CreateMocks() + (IResourceGraph, Mock, Mock, IJsonApiOptions) CreateMocks() { var pfMock = new Mock(); - var rgMock = new Mock(); + var graph = _graph; var rqMock = new Mock(); - var optionsMock = new Mock(new JsonApiOptions { LoadDatabaseValues = false }); - return (rgMock, rqMock, pfMock, optionsMock); + var optionsMock = new JsonApiOptions { LoadDatabaseValues = false }; + return (graph, rqMock, pfMock, optionsMock); } - internal (Mock requestManagerMock, ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery discovery = null) + internal (Mock requestManagerMock, ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery mainDiscovery = null) where TMain : class, IIdentifiable { // creates the resource definition mock and corresponding ImplementedHooks discovery instance - var mainResource = CreateResourceDefinition(discovery); + var mainResource = CreateResourceDefinition(mainDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); + var (graph, rqMock, gpfMock, options) = CreateMocks(); + + + var traversalHelper = new TraversalHelper(graph, rqMock.Object); + + SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, null); - 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); + var meta = new HookExecutorHelper(gpfMock.Object, graph, options); + var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource); } @@ -176,15 +181,15 @@ public class HooksTestsSetup : HooksDummyData var nestedResource = CreateResourceDefinition(nestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); + var (graph, rqMock, gpfMock, options) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; - var traversalHelper = new TraversalHelper(rgMock.Object, rqMock.Object); + var traversalHelper = new TraversalHelper(graph, rqMock.Object); SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, nestedResource.Object, nestedDiscovery, dbContext); - var meta = new HookExecutorHelper(gpfMock.Object, ResourceGraph.Instance, optionsMock.Object); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, ResourceGraph.Instance, rqMock.Object); + var meta = new HookExecutorHelper(gpfMock.Object, graph, options); + var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, nestedResource); } @@ -206,8 +211,8 @@ public class HooksTestsSetup : HooksDummyData var secondNestedResource = CreateResourceDefinition(secondNestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (_, rqMock, gpfMock, optionsMock) = CreateMocks(); - var traversalHelper = new TraversalHelper(ResourceGraph.Instance, rqMock.Object); + var (graph, rqMock, gpfMock, options) = CreateMocks(); + var traversalHelper = new TraversalHelper(graph, rqMock.Object); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; @@ -215,8 +220,8 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, firstNestedResource.Object, firstNestedDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, secondNestedResource.Object, secondNestedDiscovery, dbContext); - var hookExecutorHelper = new HookExecutorHelper(gpfMock.Object, ResourceGraph.Instance, optionsMock.Object); - var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, ResourceGraph.Instance, rqMock.Object); + var hookExecutorHelper = new HookExecutorHelper(gpfMock.Object, graph, options); + var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, firstNestedResource, secondNestedResource); } @@ -333,7 +338,7 @@ void SetupProcessorFactoryForResourceDefinition( var idType = TypeHelper.GetIdentifierType(); if (idType == typeof(int)) { - IEntityReadRepository repo = CreateTestRepository(dbContext); + IEntityReadRepository repo = CreateTestRepository(dbContext, new Mock().Object); processorFactory.Setup(c => c.GetProcessor>(typeof(IEntityReadRepository<,>), typeof(TModel), typeof(int))).Returns(repo); } else diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index 5f746508af..57e0d98497 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -29,6 +29,7 @@ public JsonApiDeSerializerTests() _jsonApiContextMock.Setup(m => m.RequestManager).Returns(_requestManagerMock.Object); var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("test-resource-with-list"); resourceGraphBuilder.AddResource("independents"); resourceGraphBuilder.AddResource("dependents"); var resourceGraph = resourceGraphBuilder.Build(); @@ -79,7 +80,7 @@ public void Can_Deserialize_Complex_List_Types() { Data = new ResourceObject { - Type = "test-resource", + Type = "test-resource-with-list", Id = "1", Attributes = new Dictionary { @@ -103,6 +104,8 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() // arrange var jsonApiOptions = new JsonApiOptions(); jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- + _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); var content = new Document @@ -133,7 +136,7 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() { // arrange var attributesToUpdate = new Dictionary(); - _requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); + _requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(attributesToUpdate); var jsonApiOptions = new JsonApiOptions(); jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs index fc3b716bf3..8a1afdebe4 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs @@ -77,7 +77,7 @@ public void Can_Serialize_Deeply_Nested_Relationships() var serializer = GetSerializer( resourceGraphBuilder, - included: new List { "children.infections" } + new List { "children.infections" } ); var resource = new TestResource @@ -211,16 +211,17 @@ private JsonApiSerializer GetSerializer( List included = null) { var resourceGraph = resourceGraphBuilder.Build(); - + var requestManagerMock = new Mock(); + requestManagerMock.Setup(m => m.GetContextEntity()).Returns(resourceGraph.GetContextEntity("test-resource")); + requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); var jsonApiContextMock = new Mock(); jsonApiContextMock.SetupAllProperties(); jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); - // jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - // jsonApiContextMock.Setup(m => m.RelationshipsToUpdate).Returns(new Dictionary()); - // jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); - // jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); + jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); + + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); var pmMock = new Mock(); jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); @@ -240,7 +241,7 @@ private JsonApiSerializer GetSerializer( var provider = services.BuildServiceProvider(); var scoped = new TestScopedServiceProvider(provider); - var documentBuilder = GetDocumentBuilder(jsonApiContextMock, scopedServiceProvider: scoped); + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); return serializer; @@ -271,12 +272,11 @@ private class InfectionResource : Identifiable [HasOne("infected")] public ChildResource Infected { get; set; } } - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, TestScopedServiceProvider scopedServiceProvider = null) + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) { var pageManagerMock = new Mock(); - var rmMock = new Mock(); - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); } } diff --git a/test/UnitTests/Services/QueryParserTests.cs b/test/UnitTests/Services/QueryParserTests.cs index ba45c0719d..f8f091f6ae 100644 --- a/test/UnitTests/Services/QueryParserTests.cs +++ b/test/UnitTests/Services/QueryParserTests.cs @@ -142,7 +142,7 @@ public void Parse_EmptySortSegment_ReceivesJsonApiException(string stringSortQue .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // Act / Assert var exception = Assert.Throws(() => diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 8494f476a9..120249c9ea 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -8,12 +8,13 @@ + - + From 0d212549354dc0e4596ec90b0bf6832bad42cf66 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 3 Sep 2019 12:19:15 +0200 Subject: [PATCH 13/19] fix: removed outdated authorization test --- .../Controllers/BaseJsonApiController.cs | 2 +- .../Middleware/RequestMiddleware.cs | 35 +----- .../Extensibility/RepositoryOverrideTests.cs | 102 ------------------ .../Helpers/Startups/AuthorizedStartup.cs | 56 ---------- .../JsonApiDotNetCoreExampleTests.csproj | 7 ++ 5 files changed, 11 insertions(+), 191 deletions(-) delete mode 100644 test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 3a076430d7..1194bca652 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,4 +1,4 @@ -using System; + using System; using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 25147cb22c..3cd3f52094 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -90,37 +90,8 @@ protected void HandleUriParameters() protected bool PathIsRelationship() { - string requestPath = _httpContext.Request.Path.Value; - // 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; + var actionName = (string)_httpContext.GetRouteData().Values["action"]; + return actionName.ToLower().Contains("relationships"); } private string GetBasePath(string entityName) { @@ -208,7 +179,7 @@ private bool IsValidAcceptHeader(HttpContext context) return true; } - internal bool ContainsMediaTypeParameters(string mediaType) + internal static bool ContainsMediaTypeParameters(string mediaType) { var incomingMediaTypeSpan = mediaType.AsSpan(); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs deleted file mode 100644 index d63575e263..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Data; -using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCoreExampleTests.Services; -using JsonApiDotNetCoreExampleTests.Startups; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Xunit; - -namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility -{ - [Collection("WebHostCollection")] - public class RepositoryOverrideTests - { - private TestFixture _fixture; - - public RepositoryOverrideTests(TestFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public async Task Total_Record_Count_Included() - { - // arrange - var builder = new WebHostBuilder() - .UseStartup(); - var server = new TestServer(builder); - var client = server.CreateClient(); - var context = (AppDbContext)server.Host.Services.GetService(typeof(AppDbContext)); - var jsonApiContext = (IJsonApiContext)server.Host.Services.GetService(typeof(IJsonApiContext)); - - var person = new Person(); - context.People.Add(person); - var ownedTodoItem = new TodoItem(); - var unOwnedTodoItem = new TodoItem(); - ownedTodoItem.Owner = person; - context.TodoItems.Add(ownedTodoItem); - context.TodoItems.Add(unOwnedTodoItem); - context.SaveChanges(); - - var authService = (IAuthorizationService)server.Host.Services.GetService(typeof(IAuthorizationService)); - authService.CurrentUserId = person.Id; - - var httpMethod = new HttpMethod("GET"); - var route = $"/api/v1/todo-items?include=owner"; - - var request = new HttpRequestMessage(httpMethod, route); - - // act - var response = await client.SendAsync(request); - var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(responseBody); - - // assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - foreach(var item in deserializedBody) - Assert.Equal(person.Id, item.Owner.Id); - } - - [Fact] - public async Task Sparse_Fields_Works_With_Get_Override() - { - // arrange - var builder = new WebHostBuilder() - .UseStartup(); - var server = new TestServer(builder); - var client = server.CreateClient(); - var context = (AppDbContext)server.Host.Services.GetService(typeof(AppDbContext)); - var jsonApiContext = (IJsonApiContext)server.Host.Services.GetService(typeof(IJsonApiContext)); - - var person = new Person(); - context.People.Add(person); - var todoItem = new TodoItem(); - todoItem.Owner = person; - context.TodoItems.Add(todoItem); - context.SaveChanges(); - - var authService = (IAuthorizationService)server.Host.Services.GetService(typeof(IAuthorizationService)); - authService.CurrentUserId = person.Id; - - var httpMethod = new HttpMethod("GET"); - var route = $"/api/v1/todo-items/{todoItem.Id}?fields[todo-items]=description"; - - var request = new HttpRequestMessage(httpMethod, route); - - // act - var response = await client.SendAsync(request); - var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().Deserialize(responseBody); - - // assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(todoItem.Description, deserializedBody.Description); - - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs deleted file mode 100644 index 12a207fea8..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using JsonApiDotNetCoreExample.Data; -using Microsoft.EntityFrameworkCore; -using JsonApiDotNetCore.Extensions; -using System; -using JsonApiDotNetCoreExample; -using Moq; -using JsonApiDotNetCoreExampleTests.Services; -using JsonApiDotNetCore.Data; -using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCoreExampleTests.Repositories; -using UnitTests; -using JsonApiDotNetCore.Services; - -namespace JsonApiDotNetCoreExampleTests.Startups -{ - public class AuthorizedStartup : Startup - { - public AuthorizedStartup(IHostingEnvironment env) - : base(env) - { } - - public override IServiceProvider ConfigureServices(IServiceCollection services) - { - var loggerFactory = new LoggerFactory(); - - loggerFactory.AddConsole(); - - services.AddSingleton(loggerFactory); - - services.AddDbContext(options => - { - options.UseNpgsql(GetDbConnectionString()); - }, ServiceLifetime.Transient); - - services.AddJsonApi(opt => - { - opt.Namespace = "api/v1"; - opt.DefaultPageSize = 5; - opt.IncludeTotalRecordCount = true; - }); - - // custom authorization implementation - var authServicMock = new Mock(); - authServicMock.SetupAllProperties(); - services.AddSingleton(authServicMock.Object); - services.AddScoped, AuthorizedTodoItemsRepository>(); - - services.AddScoped(); - - return services.BuildServiceProvider(); - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj index fb3fac898d..91471ee7c0 100644 --- a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj +++ b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj @@ -30,4 +30,11 @@ + + + + + + + From ab027662954c3e3b0e4e717d423e10fe34e9ae56 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 3 Sep 2019 12:26:26 +0200 Subject: [PATCH 14/19] fix: requestmeta tests --- .../Extensibility/RequestMetaTests.cs | 3 ++- .../Helpers/Startups/MetaStartup.cs | 21 ++----------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs index 4f9198619a..d69280e7e7 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs @@ -40,7 +40,8 @@ public async Task Injecting_IRequestMeta_Adds_Meta_Data() // act var response = await client.SendAsync(request); - var documents = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + var body = await response.Content.ReadAsStringAsync(); + var documents = JsonConvert.DeserializeObject(body); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs index 6bc5a08016..1aa7614a7e 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs @@ -1,9 +1,5 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using JsonApiDotNetCoreExample.Data; -using Microsoft.EntityFrameworkCore; -using JsonApiDotNetCore.Extensions; using System; using JsonApiDotNetCoreExample; using JsonApiDotNetCore.Services; @@ -19,21 +15,8 @@ public MetaStartup(IHostingEnvironment env) public override IServiceProvider ConfigureServices(IServiceCollection services) { - var loggerFactory = new LoggerFactory(); - loggerFactory.AddConsole(LogLevel.Warning); - - services - .AddSingleton(loggerFactory) - .AddDbContext(options => - options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) - .AddJsonApi(options => { - options.Namespace = "api/v1"; - options.DefaultPageSize = 5; - options.IncludeTotalRecordCount = true; - }) - .AddScoped(); - - return services.BuildServiceProvider(); + services.AddScoped(); + return base.ConfigureServices(services); } } } From 7246ca992928fa6eb5a04252266a7f2efba6ff5f Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 5 Sep 2019 10:55:27 +0200 Subject: [PATCH 15/19] fix: some acceptance tests --- .../Data/DefaultEntityRepository.cs | 4 +-- .../Internal/Contracts/IResourceGraph.cs | 1 - .../Internal/ResourceGraph.cs | 1 - .../Serialization/JsonApiDeSerializer.cs | 13 +++++-- .../Services/EntityResourceService.cs | 1 - .../Acceptance/Spec/AttributeFilterTests.cs | 2 ++ .../Acceptance/Spec/CreatingDataTests.cs | 8 +++-- .../Startups/ClientGeneratedIdsStartup.cs | 34 +++++++++---------- 8 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 54f9749629..112db1c352 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -75,7 +75,7 @@ public virtual IQueryable Select(IQueryable entities, List public virtual IQueryable Filter(IQueryable entities, FilterQuery filterQuery) { @@ -87,7 +87,7 @@ public virtual IQueryable Filter(IQueryable entities, FilterQu return defaultQueryFilter(entities, filterQuery); } } - return entities; + return entities.Filter(new AttrFilterQuery(_requestManager, _jsonApiContext.ResourceGraph, filterQuery)); } /// diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs index 39710ce616..5d2780d607 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -86,7 +86,6 @@ public interface IResourceGraph /// Was built against an EntityFrameworkCore DbContext ? ///
bool UsesDbContext { get; } - List IncludedRelationships { get; set; } ContextEntity GetEntityFromControllerName(string pathParsed); } diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index 770661d3e4..8e7499db39 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -56,7 +56,6 @@ internal ResourceGraph(List entities, bool usesDbContext, List public bool UsesDbContext { get; } - public List IncludedRelationships { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } /// public ContextEntity GetContextEntity(string entityName) diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 7dbc59dc04..ba0f33f768 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -259,7 +259,12 @@ private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - if (convertedValue == null) _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); + if (convertedValue == null) + { + _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + //_jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); + } + } } @@ -284,7 +289,8 @@ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute has /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); + _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + } } @@ -316,7 +322,8 @@ private object SetHasManyRelationship(object entity, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.HasManyRelationshipPointers.Add(attr, null); + _jsonApiContext.RequestManager.GetUpdatedRelationships()[attr] = null; + } return entity; diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index d783d2df47..e2d1f8c856 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -269,7 +269,6 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable protected virtual IQueryable IncludeRelationships(IQueryable entities, List relationships) { - _resourceGraph.IncludedRelationships = relationships; foreach (var r in relationships) { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs index 591dfa4c7f..370960ee29 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs @@ -215,6 +215,8 @@ public async Task Can_Filter_On_Not_In_Array_Values() { // arrange var context = _fixture.GetService(); + context.TodoItems.RemoveRange(context.TodoItems); + context.SaveChanges(); var todoItems = _todoItemFaker.Generate(5); var guids = new List(); var notInGuids = new List(); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index 559a8562d3..0811699b9c 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -235,9 +235,11 @@ public async Task Can_Create_And_Set_HasMany_Relationships() var context = _fixture.GetService(); - var owner = new JsonApiDotNetCoreExample.Models.Person(); - var todoItem = new TodoItem(); - todoItem.Owner = owner; + var owner = new Person(); + var todoItem = new TodoItem + { + Owner = owner + }; context.People.Add(owner); context.TodoItems.Add(todoItem); await context.SaveChangesAsync(); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs index 32d8186802..1f765e1b1e 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs @@ -6,6 +6,7 @@ using JsonApiDotNetCore.Extensions; using System; using JsonApiDotNetCoreExample; +using System.Reflection; namespace JsonApiDotNetCoreExampleTests.Startups { @@ -18,25 +19,24 @@ public ClientGeneratedIdsStartup(IHostingEnvironment env) public override IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); - - loggerFactory.AddConsole(); - - services.AddSingleton(loggerFactory); - - services.AddDbContext(options => - { - options.UseNpgsql(GetDbConnectionString()); - }, ServiceLifetime.Transient); - - services.AddJsonApi(opt => - { - opt.Namespace = "api/v1"; - opt.DefaultPageSize = 5; - opt.IncludeTotalRecordCount = true; - opt.AllowClientGeneratedIds = true; - }); + loggerFactory.AddConsole(LogLevel.Warning); + var mvcBuilder = services.AddMvcCore(); + services + .AddSingleton(loggerFactory) + .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) + .AddJsonApi(options => { + options.Namespace = "api/v1"; + options.DefaultPageSize = 5; + options.IncludeTotalRecordCount = true; + options.EnableResourceHooks = true; + options.LoadDatabaseValues = true; + options.AllowClientGeneratedIds = true; + }, + mvcBuilder, + discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample)))); return services.BuildServiceProvider(); + } } } From c177659aca07837cfcc3cda2947bed56975d7407 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 5 Sep 2019 11:58:32 +0200 Subject: [PATCH 16/19] fix: pagination --- src/JsonApiDotNetCore/Internal/PageManager.cs | 12 ++---------- src/JsonApiDotNetCore/Internal/Query/PageQuery.cs | 4 ++-- .../Middleware/RequestMiddleware.cs | 12 +++++++++--- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index 6e9d3e6a49..9efd2fdd6c 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -15,20 +15,12 @@ public PageManager(ILinkBuilder linkBuilder, IJsonApiOptions options, IRequestMa { _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; + PageSize = _options.DefaultPageSize; } public int? TotalRecords { get; set; } public int PageSize { get; set; } - public int DefaultPageSize { get; set; } + public int DefaultPageSize { get; set; } // I think we shouldnt expose this public int CurrentPage { get; set; } public bool IsPaginated => PageSize > 0; public int TotalPages => (TotalRecords == null) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords.Value, PageSize)); diff --git a/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs b/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs index 7c09d4c386..eb44cae170 100644 --- a/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs @@ -2,7 +2,7 @@ namespace JsonApiDotNetCore.Internal.Query { public class PageQuery { - public int PageSize { get; set; } - public int PageOffset { get; set; } = 1; + public int? PageSize { get; set; } + public int? PageOffset { get; set; } = 1; } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 3cd3f52094..7b30540d3a 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -23,10 +23,11 @@ public class RequestMiddleware private readonly RequestDelegate _next; private IResourceGraph _resourceGraph; private HttpContext _httpContext; - private IJsonApiOptions _options; private IJsonApiContext _jsonApiContext; private IRequestManager _requestManager; + private IPageManager _pageManager; private IQueryParser _queryParser; + private IJsonApiOptions _options; public RequestMiddleware(RequestDelegate next) { @@ -37,6 +38,7 @@ public async Task Invoke(HttpContext httpContext, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, + IPageManager pageManager, IQueryParser queryParser, IJsonApiOptions options) { @@ -44,6 +46,7 @@ public async Task Invoke(HttpContext httpContext, _jsonApiContext = jsonApiContext; _resourceGraph = resourceGraph; _requestManager = requestManager; + _pageManager = pageManager; _queryParser = queryParser; _options = options; @@ -74,7 +77,7 @@ public async Task Invoke(HttpContext httpContext, } } /// - /// Parses the uri, and helps you out + /// Parses the uri /// /// /// @@ -83,7 +86,10 @@ protected void HandleUriParameters() if (_httpContext.Request.Query.Count > 0) { //requestManager.FullQuerySet = context.Request.Query; - _requestManager.QuerySet = _queryParser.Parse(_httpContext.Request.Query); + var querySet = _queryParser.Parse(_httpContext.Request.Query); + _requestManager.QuerySet = querySet; //this shouldn't be exposed + _pageManager.PageSize = querySet.PageQuery.PageSize ?? _pageManager.PageSize; + _pageManager.CurrentPage = querySet.PageQuery.PageOffset ?? _pageManager.CurrentPage; _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; } } From 857b8a9db6675430b33b72419abe29ab95d0ffab Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Fri, 6 Sep 2019 11:50:21 +0200 Subject: [PATCH 17/19] fix: total records in meta --- src/JsonApiDotNetCore/Builders/DocumentBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 426b61c185..b2e9475db8 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -93,7 +93,7 @@ private Dictionary GetMeta(IIdentifiable entity) { var builder = _jsonApiContext.MetaBuilder; if (_jsonApiContext.Options.IncludeTotalRecordCount && _pageManager.TotalRecords != null) - builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords); + builder.Add("total-records", _pageManager.TotalRecords); if (_requestMeta != null) builder.Add(_requestMeta.GetMeta()); From 4eec35a383331928c326fb25f72d130ca7d01fb3 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Fri, 6 Sep 2019 14:43:40 +0200 Subject: [PATCH 18/19] feat: introduced JsonApiActionFilter --- .../IServiceCollectionExtensions.cs | 1 + .../Middleware/JsonApiActionFilter.cs | 137 ++++++++++++++++++ .../Middleware/RequestMiddleware.cs | 98 +------------ .../Acceptance/Spec/QueryParameters.cs | 7 +- 4 files changed, 146 insertions(+), 97 deletions(-) create mode 100644 src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 61b5b329b3..4181216f2e 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -79,6 +79,7 @@ private static void AddMvcOptions(MvcOptions options, JsonApiOptions config) { options.Filters.Add(typeof(JsonApiExceptionFilter)); options.Filters.Add(typeof(TypeMatchFilter)); + options.Filters.Add(typeof(JsonApiActionFilter)); options.SerializeAsJsonApi(config); } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs new file mode 100644 index 0000000000..2c42a8f82b --- /dev/null +++ b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs @@ -0,0 +1,137 @@ +using System; +using System.Linq; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; + +namespace JsonApiDotNetCore.Middleware +{ + public class JsonApiActionFilter : IActionFilter + { + private readonly IJsonApiContext _jsonApiContext; + private readonly IResourceGraph _resourceGraph; + private readonly IRequestManager _requestManager; + private readonly IPageManager _pageManager; + private readonly IQueryParser _queryParser; + private readonly IJsonApiOptions _options; + private HttpContext _httpContext; + public JsonApiActionFilter(IResourceGraph resourceGraph, + IRequestManager requestManager, + IPageManager pageManager, + IQueryParser queryParser, + IJsonApiOptions options) + { + _resourceGraph = resourceGraph; + _requestManager = requestManager; + _pageManager = pageManager; + _queryParser = queryParser; + _options = options; + } + + /// + /// + public void OnActionExecuting(ActionExecutingContext context) + { + _httpContext = context.HttpContext; + ContextEntity contextEntityCurrent = GetCurrentEntity(); + + // the contextEntity is null eg when we're using a non-JsonApiDotNetCore route. + if (contextEntityCurrent != null) + { + _requestManager.SetContextEntity(contextEntityCurrent); + _requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); + HandleUriParameters(); + _requestManager.IsRelationshipPath = PathIsRelationship(); + } + + } + + + /// + /// Parses the uri + /// + protected void HandleUriParameters() + { + if (_httpContext.Request.Query.Count > 0) + { + var querySet = _queryParser.Parse(_httpContext.Request.Query); + _requestManager.QuerySet = querySet; //this shouldn't be exposed? + _pageManager.PageSize = querySet.PageQuery.PageSize ?? _pageManager.PageSize; + _pageManager.CurrentPage = querySet.PageQuery.PageOffset ?? _pageManager.CurrentPage; + _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; + } + } + + protected bool PathIsRelationship() + { + var actionName = (string)_httpContext.GetRouteData().Values["action"]; + return actionName.ToLower().Contains("relationships"); + } + private string GetBasePath(string entityName) + { + var r = _httpContext.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() + { + var controllerName = (string)_httpContext.GetRouteData().Values["controller"]; + return _resourceGraph.GetEntityFromControllerName(controllerName); + } + + + private bool IsJsonApiRequest(HttpRequest request) + { + return (request.ContentType?.Equals(Constants.ContentType, StringComparison.OrdinalIgnoreCase) == true); + } + + public void OnActionExecuted(ActionExecutedContext context) { /* noop */ } + } +} diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 7b30540d3a..ecdd62d7d7 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -16,14 +17,13 @@ namespace JsonApiDotNetCore.Middleware /// /// Can be overwritten to help you out during testing /// - /// This sets all necessary paaramters relating to the HttpContext for JADNC + /// This sets all necessary parameters relating to the HttpContext for JADNC /// public class RequestMiddleware { private readonly RequestDelegate _next; private IResourceGraph _resourceGraph; private HttpContext _httpContext; - private IJsonApiContext _jsonApiContext; private IRequestManager _requestManager; private IPageManager _pageManager; private IQueryParser _queryParser; @@ -40,10 +40,10 @@ public async Task Invoke(HttpContext httpContext, IRequestManager requestManager, IPageManager pageManager, IQueryParser queryParser, - IJsonApiOptions options) + IJsonApiOptions options + ) { _httpContext = httpContext; - _jsonApiContext = jsonApiContext; _resourceGraph = resourceGraph; _requestManager = requestManager; _pageManager = pageManager; @@ -58,100 +58,10 @@ public async Task Invoke(HttpContext httpContext, // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); - ContextEntity contextEntityCurrent = GetCurrentEntity(); - // the contextEntity is null eg when we're using a non-JsonApiDotNetCore route. - if (contextEntityCurrent != null) - { - requestManager.SetContextEntity(contextEntityCurrent); - // TODO: this does not need to be reset every request: we shouldn't need to rely on an external request to figure out the basepath of current application - requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); - //Handle all querySet - HandleUriParameters(); - requestManager.IsRelationshipPath = PathIsRelationship(); - // BACKWARD COMPATIBILITY for v4 will be removed in v5 - jsonApiContext.RequestManager = requestManager; - jsonApiContext.PageManager = new PageManager(new LinkBuilder(options, requestManager), options, requestManager); - } await _next(httpContext); } } - /// - /// Parses the uri - /// - /// - /// - protected void HandleUriParameters() - { - if (_httpContext.Request.Query.Count > 0) - { - //requestManager.FullQuerySet = context.Request.Query; - var querySet = _queryParser.Parse(_httpContext.Request.Query); - _requestManager.QuerySet = querySet; //this shouldn't be exposed - _pageManager.PageSize = querySet.PageQuery.PageSize ?? _pageManager.PageSize; - _pageManager.CurrentPage = querySet.PageQuery.PageOffset ?? _pageManager.CurrentPage; - _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; - } - } - - protected bool PathIsRelationship() - { - var actionName = (string)_httpContext.GetRouteData().Values["action"]; - return actionName.ToLower().Contains("relationships"); - } - private string GetBasePath(string entityName) - { - var r = _httpContext.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() - { - var controllerName = (string)_httpContext.GetRouteData().Values["controller"]; - return _resourceGraph.GetEntityFromControllerName(controllerName); - } private bool IsValid() { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs index 58b2323da5..d530f70d40 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs @@ -35,12 +35,13 @@ public async Task Server_Returns_400_ForUnknownQueryParam() // act var response = await client.SendAsync(request); - var body = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + var body = await response.Content.ReadAsStringAsync(); + var errorCollection = JsonConvert.DeserializeObject(body); // assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - Assert.Single(body.Errors); - Assert.Equal($"[{queryKey}, {queryValue}] is not a valid query.", body.Errors[0].Title); + Assert.Single(errorCollection.Errors); + Assert.Equal($"[{queryKey}, {queryValue}] is not a valid query.", errorCollection.Errors[0].Title); } } } From d672e8e431f909500ef6a2475a9539cbcb2b62dd Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 9 Sep 2019 11:36:56 +0200 Subject: [PATCH 19/19] fix: more tests --- .../JsonApiDeserializer_Benchmarks.cs | 6 +- src/Examples/GettingStarted/Startup.cs | 5 +- .../Data/AppDbContext.cs | 4 + .../Resources/ArticleResource.cs | 2 - .../Services/CustomArticleService.cs | 4 +- .../JsonApiDotNetCoreExample/Startup.cs | 5 - src/Examples/ReportsExample/Startup.cs | 3 +- .../Startup.cs | 7 +- .../Configuration/JsonApiOptions.cs | 4 +- .../IServiceCollectionExtensions.cs | 108 +++++++++++++----- .../Formatters/JsonApiReader.cs | 9 +- .../Hooks/ResourceHookExecutor.cs | 5 +- .../Internal/ResourceGraph.cs | 16 ++- .../Middleware/JsonApiActionFilter.cs | 7 +- .../Middleware/RequestMiddleware.cs | 8 +- .../Middleware/TypeMatchFilter.cs | 11 +- .../Serialization/JsonApiDeSerializer.cs | 15 ++- .../Services/EntityResourceService.cs | 4 - .../Operations/OperationsProcessor.cs | 21 +++- .../ServiceDiscoveryFacadeTests.cs | 23 +++- .../ResourceDefinitionTests.cs | 4 - .../Startups/ClientGeneratedIdsStartup.cs | 4 +- .../ResourceHooks/ResourceHooksTestsSetup.cs | 11 +- .../Serialization/JsonApiDeSerializerTests.cs | 26 +++-- .../Operations/OperationsProcessorTests.cs | 10 +- test/UnitTests/Services/QueryParserTests.cs | 2 +- 26 files changed, 207 insertions(+), 117 deletions(-) diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index 03b4878aa6..15478a5c52 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -4,6 +4,7 @@ using BenchmarkDotNet.Attributes.Exporters; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; @@ -35,6 +36,9 @@ public JsonApiDeserializer_Benchmarks() { var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource(TYPE_NAME); var resourceGraph = resourceGraphBuilder.Build(); + var requestManagerMock = new Mock(); + + requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); var jsonApiContextMock = new Mock(); jsonApiContextMock.SetupAllProperties(); @@ -46,7 +50,7 @@ public JsonApiDeserializer_Benchmarks() { jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - _jsonApiDeSerializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + _jsonApiDeSerializer = new JsonApiDeSerializer(jsonApiContextMock.Object, requestManagerMock.Object); } [Benchmark] diff --git a/src/Examples/GettingStarted/Startup.cs b/src/Examples/GettingStarted/Startup.cs index 5d0fa8dc91..d5c805282b 100644 --- a/src/Examples/GettingStarted/Startup.cs +++ b/src/Examples/GettingStarted/Startup.cs @@ -20,11 +20,10 @@ public void ConfigureServices(IServiceCollection services) options.UseSqlite("Data Source=sample.db"); }); - var mvcCoreBuilder = services.AddMvcCore(); + var mvcBuilder = services.AddMvcCore(); services.AddJsonApi( options => options.Namespace = "api", - mvcCoreBuilder, - discover => discover.AddCurrentAssembly()); + discover => discover.AddCurrentAssembly(), mvcBuilder); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, SampleDbContext context) diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs index d7147123f6..6a6f7994fb 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -92,5 +92,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public DbSet ArticleTags { get; set; } public DbSet IdentifiableArticleTags { get; set; } public DbSet Tags { get; set; } + + } + + } diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs index 17dfca12f2..1e3230c759 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs @@ -5,8 +5,6 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Hooks; using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; -using System.Security.Principal; using JsonApiDotNetCore.Internal.Contracts; namespace JsonApiDotNetCoreExample.Resources diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index 4d486c58e5..eb05a54888 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Hooks; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; @@ -18,8 +19,9 @@ public CustomArticleService( IRequestManager queryManager, IPageManager pageManager, IResourceGraph resourceGraph, + IResourceHookExecutor resourceHookExecutor = null, ILoggerFactory loggerFactory = null - ) : base(repository: repository, jsonApiOptions, queryManager, pageManager, resourceGraph:resourceGraph, loggerFactory) + ) : base(repository: repository, jsonApiOptions, queryManager, pageManager, resourceGraph:resourceGraph, loggerFactory, resourceHookExecutor) { } public override async Task
GetAsync(int id) diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 1b3af9a1db..047161e324 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -7,9 +7,6 @@ using Microsoft.EntityFrameworkCore; using JsonApiDotNetCore.Extensions; using System; -using System.ComponentModel.Design; -using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCore.Services; namespace JsonApiDotNetCoreExample { @@ -32,7 +29,6 @@ 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) @@ -43,7 +39,6 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) options.EnableResourceHooks = true; options.LoadDatabaseValues = true; }, - mvcBuilder, discovery => discovery.AddCurrentAssembly()); return services.BuildServiceProvider(); diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs index b71b7fa74a..4f49e87db6 100644 --- a/src/Examples/ReportsExample/Startup.cs +++ b/src/Examples/ReportsExample/Startup.cs @@ -27,8 +27,7 @@ public virtual void ConfigureServices(IServiceCollection services) var mvcBuilder = services.AddMvcCore(); services.AddJsonApi( opt => opt.Namespace = "api", - mvcBuilder, - discovery => discovery.AddCurrentAssembly()); + discovery => discovery.AddCurrentAssembly(), mvcBuilder); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) diff --git a/src/Examples/ResourceEntitySeparationExample/Startup.cs b/src/Examples/ResourceEntitySeparationExample/Startup.cs index a99febfee8..f87dea6935 100644 --- a/src/Examples/ResourceEntitySeparationExample/Startup.cs +++ b/src/Examples/ResourceEntitySeparationExample/Startup.cs @@ -43,18 +43,19 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) ServiceLifetime.Transient); services.AddScoped>(); - var mvcBuilder = services.AddMvcCore(); services.AddJsonApi(options => { options.Namespace = "api/v1"; options.DefaultPageSize = 10; options.IncludeTotalRecordCount = true; - options.BuildResourceGraph((builder) => { + options.EnableResourceHooks = false; // not supported with ResourceEntitySeparation + options.BuildResourceGraph((builder) => + { builder.AddResource("courses"); builder.AddResource("departments"); builder.AddResource("students"); }); - }, mvcBuilder); + }); services.AddAutoMapper(); services.AddScoped(); diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 456ff26263..57a4f98396 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -2,13 +2,11 @@ using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Graph; -using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; namespace JsonApiDotNetCore.Configuration { @@ -32,7 +30,7 @@ public class JsonApiOptions : IJsonApiOptions /// Whether or not stack traces should be serialized in Error objects ///
public static bool DisableErrorStackTraces { get; set; } - + /// /// Whether or not source URLs should be serialized in Error objects /// diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 4181216f2e..57e5788b2c 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -29,52 +29,99 @@ namespace JsonApiDotNetCore.Extensions // ReSharper disable once InconsistentNaming public static class IServiceCollectionExtensions { - public static IServiceCollection AddJsonApi(this IServiceCollection services) + static private readonly Action _noopConfig = opt => { }; + static private JsonApiOptions _options { get { return new JsonApiOptions(); } } + public static IServiceCollection AddJsonApi(this IServiceCollection services, + IMvcCoreBuilder mvcBuilder = null) where TContext : DbContext { - var mvcBuilder = services.AddMvcCore(); - return AddJsonApi(services, opt => { }, mvcBuilder); + return AddJsonApi(services, _noopConfig, mvcBuilder); } - public static IServiceCollection AddJsonApi(this IServiceCollection services, Action options) + /// + /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. + /// + /// + /// + /// + /// + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action configureAction, + IMvcCoreBuilder mvcBuilder = null) where TContext : DbContext { - var mvcBuilder = services.AddMvcCore(); - return AddJsonApi(services, options, mvcBuilder); + var options = _options; + // add basic Mvc functionality + mvcBuilder = mvcBuilder ?? services.AddMvcCore(); + // set standard options + configureAction(options); + + // ResourceGraphBuilder should not be exposed on JsonApiOptions. + // Instead, ResourceGraphBuilder should consume JsonApiOptions + + // build the resource graph using ef core DbContext + options.BuildResourceGraph(builder => builder.AddDbContext()); + + // add JsonApi fitlers and serializer + mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options)); + + // register services + AddJsonApiInternals(services, options); + return services; } - public static IServiceCollection AddJsonApi( - this IServiceCollection services, - Action options, - IMvcCoreBuilder mvcBuilder) where TContext : DbContext + + /// + /// Enabling JsonApiDotNetCore using manual declaration to build the ResourceGraph. + /// + /// + /// + /// + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action configureOptions, + IMvcCoreBuilder mvcBuilder = null) { - var config = new JsonApiOptions(); - options(config); - config.BuildResourceGraph(builder => builder.AddDbContext()); - mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, config)); - AddJsonApiInternals(services, config); + var options = _options; + mvcBuilder = mvcBuilder ?? services.AddMvcCore(); + configureOptions(options); + + // add JsonApi fitlers and serializer + mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options)); + + // register services + AddJsonApiInternals(services, options); return services; } - public static IServiceCollection AddJsonApi( - this IServiceCollection services, - Action configureOptions, - IMvcCoreBuilder mvcBuilder, - Action autoDiscover = null) + /// + /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. + /// + /// + /// + /// + /// + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action configureOptions, + Action autoDiscover, + IMvcCoreBuilder mvcBuilder = null) { - var config = new JsonApiOptions(); - configureOptions(config); + var options = _options; + mvcBuilder = mvcBuilder ?? services.AddMvcCore(); + configureOptions(options); - if (autoDiscover != null) - { - var facade = new ServiceDiscoveryFacade(services, config.ResourceGraphBuilder); - autoDiscover(facade); - } - mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, config)); - AddJsonApiInternals(services, config); + // build the resource graph using auto discovery. + var facade = new ServiceDiscoveryFacade(services, options.ResourceGraphBuilder); + autoDiscover(facade); + + // add JsonApi fitlers and serializer + mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options)); + + // register services + AddJsonApiInternals(services, options); return services; } + private static void AddMvcOptions(MvcOptions options, JsonApiOptions config) { options.Filters.Add(typeof(JsonApiExceptionFilter)); @@ -144,7 +191,6 @@ public static void AddJsonApiInternals( services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddSingleton(jsonApiOptions); services.AddScoped(); @@ -173,7 +219,7 @@ public static void AddJsonApiInternals( services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor)); services.AddTransient(); } - services.AddTransient(); + //services.AddTransient(); services.AddScoped(); } diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs index 20d646d939..a8cf56a789 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; @@ -16,13 +17,13 @@ namespace JsonApiDotNetCore.Formatters public class JsonApiReader : IJsonApiReader { private readonly IJsonApiDeSerializer _deserializer; - private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; private readonly ILogger _logger; - public JsonApiReader(IJsonApiDeSerializer deSerializer, IJsonApiContext jsonApiContext, ILoggerFactory loggerFactory) + public JsonApiReader(IJsonApiDeSerializer deSerializer, IRequestManager requestManager, ILoggerFactory loggerFactory) { _deserializer = deSerializer; - _jsonApiContext = jsonApiContext; + _requestManager = requestManager; _logger = loggerFactory.CreateLogger(); } @@ -40,7 +41,7 @@ public Task ReadAsync(InputFormatterContext context) var body = GetRequestBody(context.HttpContext.Request.Body); object model = null; - if (_jsonApiContext.IsRelationshipPath) + if (_requestManager.IsRelationshipPath) { model = _deserializer.DeserializeRelationship(body); } diff --git a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs index 9207cc66a1..3b85e071ae 100644 --- a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs @@ -18,22 +18,21 @@ namespace JsonApiDotNetCore.Hooks internal class ResourceHookExecutor : IResourceHookExecutor { public static readonly IdentifiableComparer Comparer = new IdentifiableComparer(); - internal readonly ITraversalHelper _traversalHelper; private readonly IRequestManager _requestManager; internal readonly IHookExecutorHelper _executorHelper; protected readonly IJsonApiContext _context; private readonly IResourceGraph _graph; + private readonly TraversalHelper _traversalHelper; public ResourceHookExecutor( IHookExecutorHelper helper, - ITraversalHelper traversalHelper, IResourceGraph resourceGraph, IRequestManager requestManager) { _requestManager = requestManager; _executorHelper = helper; _graph = resourceGraph; - _traversalHelper = traversalHelper; + _traversalHelper = new TraversalHelper(resourceGraph, requestManager); } /// diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index 8e7499db39..8cb22f1030 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -137,9 +137,19 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati public ContextEntity GetEntityFromControllerName(string controllerName) { - var resource = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource; - if (resource == null) return null; - return Entities.First(e => e.EntityType == resource); + + if (ControllerResourceMap.Any()) + { + // Autodiscovery was used, so there is a well defined mapping between exposed resources and their associated controllers + var resourceType = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource; + if (resourceType == null) return null; + return Entities.First(e => e.EntityType == resourceType); + + } else + { + // No autodiscovery: try to guess contextentity from controller name. + return Entities.FirstOrDefault(e => e.EntityName.ToLower().Replace("-", "") == controllerName.ToLower()); + } } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs index 2c42a8f82b..9e6becbcde 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs @@ -46,7 +46,6 @@ public void OnActionExecuting(ActionExecutingContext context) _requestManager.SetContextEntity(contextEntityCurrent); _requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); HandleUriParameters(); - _requestManager.IsRelationshipPath = PathIsRelationship(); } } @@ -67,11 +66,7 @@ protected void HandleUriParameters() } } - protected bool PathIsRelationship() - { - var actionName = (string)_httpContext.GetRouteData().Values["action"]; - return actionName.ToLower().Contains("relationships"); - } + private string GetBasePath(string entityName) { var r = _httpContext.Request; diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index ecdd62d7d7..a540811dfa 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -58,12 +58,18 @@ IJsonApiOptions options // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); + _requestManager.IsRelationshipPath = PathIsRelationship(); await _next(httpContext); } } - private bool IsValid() + protected bool PathIsRelationship() + { + var actionName = (string)_httpContext.GetRouteData().Values["action"]; + return actionName.ToLower().Contains("relationships"); + } + private bool IsValid() { return IsValidContentTypeHeader(_httpContext) && IsValidAcceptHeader(_httpContext); } diff --git a/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs b/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs index 64624dfa70..e3e474740e 100644 --- a/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; @@ -9,11 +10,11 @@ namespace JsonApiDotNetCore.Middleware { public class TypeMatchFilter : IActionFilter { - private readonly IJsonApiContext _jsonApiContext; + private readonly IResourceGraph _resourceGraph; - public TypeMatchFilter(IJsonApiContext jsonApiContext) + public TypeMatchFilter(IResourceGraph resourceGraph) { - _jsonApiContext = jsonApiContext; + _resourceGraph = resourceGraph; } /// @@ -29,10 +30,10 @@ public void OnActionExecuting(ActionExecutingContext context) if (deserializedType != null && targetType != null && deserializedType != targetType) { - var expectedJsonApiResource = _jsonApiContext.ResourceGraph.GetContextEntity(targetType); + var expectedJsonApiResource = _resourceGraph.GetContextEntity(targetType); throw new JsonApiException(409, - $"Cannot '{context.HttpContext.Request.Method}' type '{_jsonApiContext.RequestEntity.EntityName}' " + $"Cannot '{context.HttpContext.Request.Method}' type '{deserializedType.Name}' " + $"to '{expectedJsonApiResource?.EntityName}' endpoint.", detail: "Check that the request payload type matches the type expected by this endpoint."); } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index ba0f33f768..86bd3f3016 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -6,6 +6,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Generics; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Services; @@ -17,10 +18,12 @@ namespace JsonApiDotNetCore.Serialization public class JsonApiDeSerializer : IJsonApiDeSerializer { private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; - public JsonApiDeSerializer(IJsonApiContext jsonApiContext) + public JsonApiDeSerializer(IJsonApiContext jsonApiContext, IRequestManager requestManager) { _jsonApiContext = jsonApiContext; + _requestManager = requestManager; } public object Deserialize(string requestBody) @@ -112,7 +115,7 @@ public object DocumentToObject(ResourceObject data, List include throw new JsonApiException(422, "Failed to deserialize document as json:api."); var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(data.Type?.ToString()); - _jsonApiContext.RequestEntity = contextEntity ?? throw new JsonApiException(400, + if(contextEntity == null) throw new JsonApiException(400, message: $"This API does not contain a json:api resource named '{data.Type}'.", detail: "This resource is not registered on the ResourceGraph. " + "If you are using Entity Framework, make sure the DbSet matches the expected resource name. " @@ -150,7 +153,7 @@ private object SetEntityAttributes( /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.RequestManager.GetUpdatedAttributes()[attr] = null; + _requestManager.GetUpdatedAttributes()[attr] = null; } } @@ -261,7 +264,7 @@ private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, /// see #512 if (convertedValue == null) { - _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + _requestManager.GetUpdatedRelationships()[hasOneAttr] = null; //_jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); } @@ -289,7 +292,7 @@ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute has /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + _requestManager.GetUpdatedRelationships()[hasOneAttr] = null; } } @@ -322,7 +325,7 @@ private object SetHasManyRelationship(object entity, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.RequestManager.GetUpdatedRelationships()[attr] = null; + _requestManager.GetUpdatedRelationships()[attr] = null; } diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index e2d1f8c856..aa64ba6b24 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -403,9 +403,5 @@ public EntityResourceService( IResourceHookExecutor hookExecutor = null) : base(repository: repository, apiOptions: options, requestManager, resourceGraph, pageManager, loggerFactory, hookExecutor) { } - - //[Obsolete("Dont use this constructor, use the one without JsonApiContext instead")] - //public EntityResourceService( - // IJsonApiContext context, IEntityRepository repository) : this(repository, context.Options, context.RequestManager, context.PageManager, context.ResourceGraph) { } } } diff --git a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs index 275b4b85b0..048959f9eb 100644 --- a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs @@ -4,6 +4,8 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using Microsoft.EntityFrameworkCore; @@ -20,15 +22,21 @@ public class OperationsProcessor : IOperationsProcessor private readonly IOperationProcessorResolver _processorResolver; private readonly DbContext _dbContext; private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; + private readonly IResourceGraph _resourceGraph; public OperationsProcessor( IOperationProcessorResolver processorResolver, IDbContextResolver dbContextResolver, - IJsonApiContext jsonApiContext) + IJsonApiContext jsonApiContext, + IRequestManager requestManager, + IResourceGraph resourceGraph) { _processorResolver = processorResolver; _dbContext = dbContextResolver.GetContext(); _jsonApiContext = jsonApiContext; + _requestManager = requestManager; + _resourceGraph = resourceGraph; } public async Task> ProcessAsync(List inputOps) @@ -44,6 +52,7 @@ public async Task> ProcessAsync(List inputOps) foreach (var op in inputOps) { _jsonApiContext.BeginOperation(); + lastAttemptedOperation = op.Op; await ProcessOperation(op, outputOps); opIndex++; @@ -70,6 +79,16 @@ private async Task ProcessOperation(Operation op, List outputOps) ReplaceLocalIdsInResourceObject(op.DataObject, outputOps); ReplaceLocalIdsInRef(op.Ref, outputOps); + string type = null; + if (op.Op == OperationCode.add || op.Op == OperationCode.update) + { + type = op.DataObject.Type; + } else if (op.Op == OperationCode.get || op.Op == OperationCode.remove) + { + type = op.Ref.Type; + } + _requestManager.SetContextEntity(_resourceGraph.GetEntityFromControllerName(type)); + var processor = GetOperationsProcessor(op); var resultOp = await processor.ProcessAsync(op); diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 872b9f693b..3a64dc3326 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Hooks; +using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; @@ -63,13 +64,20 @@ public void AddCurrentAssembly_Adds_Resources_To_Graph() [Fact] public void AddCurrentAssembly_Adds_Services_To_Container() - { - // arrange, act + { + // arrange, act + _services.AddSingleton(new JsonApiOptions()); + + _services.AddScoped( (_) => new Mock().Object); + _services.AddScoped( (_) => new Mock().Object); + _services.AddScoped( (_) => new Mock().Object); + _services.AddScoped( (_) => new Mock().Object); _facade.AddCurrentAssembly(); // assert var services = _services.BuildServiceProvider(); - Assert.IsType(services.GetService>()); + var service = services.GetService>(); + Assert.IsType(service); } [Fact] @@ -90,7 +98,14 @@ public class TestModelService : EntityResourceService private static IEntityRepository _repo = new Mock>().Object; private static IJsonApiContext _jsonApiContext = new Mock().Object; - public TestModelService(IEntityRepository repository, IJsonApiOptions options, IRequestManager requestManager, IPageManager pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, IResourceHookExecutor hookExecutor = null) : base(repository, options, requestManager, pageManager, resourceGraph, loggerFactory, hookExecutor) + public TestModelService( + IEntityRepository repository, + IJsonApiOptions options, + IRequestManager requestManager, + IPageManager pageManager, + IResourceGraph resourceGraph, + ILoggerFactory loggerFactory = null, + IResourceHookExecutor hookExecutor = null) : base(repository, options, requestManager, pageManager, resourceGraph, loggerFactory, hookExecutor) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs index 88b98f982a..616a1cf9da 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs @@ -206,10 +206,6 @@ public async Task Unauthorized_Article() var route = $"/api/v1/articles/{article.Id}"; - var httpMethod = new HttpMethod("GET"); - var request = new HttpRequestMessage(httpMethod, route); - - // Act var response = await _fixture.Client.GetAsync(route); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs index 1f765e1b1e..c3c3d5a3ec 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs @@ -32,8 +32,8 @@ public override IServiceProvider ConfigureServices(IServiceCollection services) options.LoadDatabaseValues = true; options.AllowClientGeneratedIds = true; }, - mvcBuilder, - discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample)))); + discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample))), + mvcBuilder); return services.BuildServiceProvider(); diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index 8b2f61b5a7..fbe909938f 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -155,14 +155,10 @@ public class HooksTestsSetup : HooksDummyData // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. var (graph, rqMock, gpfMock, options) = CreateMocks(); - - var traversalHelper = new TraversalHelper(graph, rqMock.Object); - SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, null); - var meta = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); + var hookExecutor = new ResourceHookExecutor(meta, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource); } @@ -189,7 +185,7 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, nestedResource.Object, nestedDiscovery, dbContext); var meta = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); + var hookExecutor = new ResourceHookExecutor(meta, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, nestedResource); } @@ -212,7 +208,6 @@ public class HooksTestsSetup : HooksDummyData // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. var (graph, rqMock, gpfMock, options) = CreateMocks(); - var traversalHelper = new TraversalHelper(graph, rqMock.Object); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; @@ -221,7 +216,7 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, secondNestedResource.Object, secondNestedDiscovery, dbContext); var hookExecutorHelper = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, graph, rqMock.Object); + var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, firstNestedResource, secondNestedResource); } diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index 57e0d98497..b2a7ad8e57 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -47,7 +47,7 @@ private void CreateMocks() public void Can_Deserialize_Complex_Types() { // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var content = new Document { @@ -74,7 +74,7 @@ public void Can_Deserialize_Complex_Types() public void Can_Deserialize_Complex_List_Types() { // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var content = new Document { @@ -106,7 +106,7 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var content = new Document { @@ -141,7 +141,8 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); + var content = new Document { @@ -176,7 +177,7 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship() { // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var property = Guid.NewGuid().ToString(); var content = new Document @@ -219,7 +220,8 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Str var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); + var property = Guid.NewGuid().ToString(); var content = new Document @@ -266,7 +268,7 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Rel var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var property = Guid.NewGuid().ToString(); var content = new Document @@ -320,7 +322,7 @@ public void Sets_The_DocumentMeta_Property_In_JsonApiContext() jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var property = Guid.NewGuid().ToString(); @@ -412,7 +414,7 @@ public void Can_Deserialize_Object_With_HasManyRelationship() var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var contentString = @"{ @@ -468,7 +470,7 @@ public void Sets_Attribute_Values_On_Included_HasMany_Relationships() var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var expectedName = "John Doe"; var contentString = @@ -536,7 +538,7 @@ public void Sets_Attribute_Values_On_Included_HasOne_Relationships() var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var expectedName = "John Doe"; var contentString = @@ -729,7 +731,7 @@ private JsonApiDeSerializer GetDeserializer(ResourceGraphBuilder resourceGraphBu var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); return deserializer; } diff --git a/test/UnitTests/Services/Operations/OperationsProcessorTests.cs b/test/UnitTests/Services/Operations/OperationsProcessorTests.cs index 08211c5a67..0bdcfa92e9 100644 --- a/test/UnitTests/Services/Operations/OperationsProcessorTests.cs +++ b/test/UnitTests/Services/Operations/OperationsProcessorTests.cs @@ -2,6 +2,8 @@ using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Services; using JsonApiDotNetCore.Services.Operations; @@ -93,7 +95,9 @@ public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_Relationship .Returns(opProcessorMock.Object); _dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object); - var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object); + var requestManagerMock = new Mock(); + var resourceGraphMock = new Mock(); + var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object, requestManagerMock.Object, resourceGraphMock.Object); // act var results = await operationsProcessor.ProcessAsync(operations); @@ -176,7 +180,9 @@ public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_References() .Returns(updateOpProcessorMock.Object); _dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object); - var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object); + var requestManagerMock = new Mock(); + var resourceGraphMock = new Mock(); + var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object, requestManagerMock.Object, resourceGraphMock.Object); // act var results = await operationsProcessor.ProcessAsync(operations); diff --git a/test/UnitTests/Services/QueryParserTests.cs b/test/UnitTests/Services/QueryParserTests.cs index f8f091f6ae..63e04004d0 100644 --- a/test/UnitTests/Services/QueryParserTests.cs +++ b/test/UnitTests/Services/QueryParserTests.cs @@ -223,7 +223,7 @@ public void Can_Disable_Page() var querySet = queryParser.Parse(_queryCollectionMock.Object); // assert - Assert.Equal(0, querySet.PageQuery.PageSize); + Assert.Equal(null, querySet.PageQuery.PageSize); } [Fact]