From 79970428e657e2fac3e9345a60a5b268c09293c2 Mon Sep 17 00:00:00 2001 From: maurei Date: Tue, 1 Dec 2020 12:23:19 +0000 Subject: [PATCH 1/7] feat: dynamic controller draft --- .../JsonApiApplicationBuilder.cs | 3 +++ .../JsonApiControllerFeatureProvider.cs | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index 74bc6cf4a5..0f9b9a079c 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -92,6 +92,7 @@ public void AddResourceGraph(ICollection dbContextTypes, Action public void ConfigureMvc() { + _mvcBuilder.AddMvcOptions(options => { options.EnableEndpointRouting = true; @@ -101,6 +102,8 @@ public void ConfigureMvc() ConfigureMvcOptions?.Invoke(options); }); + _mvcBuilder.ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(new JsonApiControllerFeatureProvider())); + if (_options.ValidateModelState) { _mvcBuilder.AddDataAnnotations(); diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs b/src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs new file mode 100644 index 0000000000..7ad8798c74 --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JsonApiDotNetCore.Controllers; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace JsonApiDotNetCore.Configuration +{ + public class JsonApiControllerFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) + { + var currentAssembly = Assembly.GetCallingAssembly(); + + var resourceDescriptors = currentAssembly + .GetExportedTypes() + .Select(TypeLocator.TryGetResourceDescriptor) + .Where(descriptor => descriptor != null); + + foreach (var descriptor in resourceDescriptors) + { + feature.Controllers.Add(typeof(BaseJsonApiController<,>).MakeGenericType(descriptor.ResourceType, descriptor.IdType).GetTypeInfo()); + } + } + } +} From 8aa4d75b0771e0530ff52a7c67e649f5ed78f0d6 Mon Sep 17 00:00:00 2001 From: maurei Date: Wed, 2 Dec 2020 21:17:56 +0000 Subject: [PATCH 2/7] feat: dynamic controller generation with routing refactor and removal of now unneeded controller files --- .../Controllers/AuthorsController.cs | 18 --- .../Controllers/BlogsController.cs | 18 --- .../Controllers/PassportsController.cs | 16 --- .../Controllers/PeopleController.cs | 18 --- .../Controllers/PersonRolesController.cs | 18 --- .../ThrowingResourcesController.cs | 18 --- .../Controllers/UsersController.cs | 28 ----- ...icleHooksDefinition.cs => ArticleHooks.cs} | 4 +- ...ortHooksDefinition.cs => PassportHooks.cs} | 4 +- ...ersonHooksDefinition.cs => PersonHooks.cs} | 0 .../{TagHooksDefinition.cs => TagHooks.cs} | 0 ...odoHooksDefinition.cs => TodoItemHooks.cs} | 0 .../IJsonApiControllerGenerator.cs | 8 ++ .../Configuration/IJsonApiOptions.cs | 6 + .../JsonApiApplicationBuilder.cs | 34 +++++- .../JsonApiControllerFeatureProvider.cs | 27 ----- .../JsonApiControllerGenerator.cs | 53 +++++++++ .../Configuration/JsonApiOptions.cs | 3 + .../Middleware/IControllerResourceMapping.cs | 2 +- .../Middleware/JsonApiMiddleware.cs | 17 +-- .../Middleware/JsonApiRoutingConvention.cs | 109 +++++++++++------- .../ResourceHooksTests.cs} | 0 .../ResourceHooksApplicationFactory.cs | 33 ------ .../CompositeKeys/CarsController.cs | 16 --- .../CompositeKeys/DealershipsController.cs | 16 --- .../CompositeKeys/EnginesController.cs | 16 --- .../ContentNegotiation/PoliciesController.cs | 16 --- .../EagerLoading/BuildingsController.cs | 16 --- .../EagerLoading/StatesController.cs | 16 --- .../EagerLoading/StreetsController.cs | 16 --- .../FilterableResourcesController.cs | 16 --- .../IdObfuscation/BankAccountsController.cs | 15 --- .../IdObfuscation/DebitCardsController.cs | 15 --- .../SystemDirectoriesController.cs | 16 --- .../SystemFilesController.cs | 16 --- .../ReadWrite/RgbColorsController.cs | 16 --- .../ReadWrite/UserAccountsController.cs | 16 --- .../ReadWrite/WorkItemGroupsController.cs | 17 --- .../ReadWrite/WorkItemsController.cs | 16 --- .../CustomersController.cs | 16 --- .../RequiredRelationships/OrdersController.cs | 16 --- .../ShipmentsController.cs | 16 --- .../CallableResourcesController.cs | 16 --- .../ResourceInheritance/{Models => }/Book.cs | 0 .../{Models => }/CompanyHealthInsurance.cs | 0 .../{Models => }/ContentItem.cs | 0 .../{Models => }/FamilyHealthInsurance.cs | 0 .../{Models => }/HealthInsurance.cs | 0 .../ResourceInheritance/{Models => }/Human.cs | 0 .../{Models => }/HumanFavoriteContentItem.cs | 0 .../ResourceInheritance/{Models => }/Man.cs | 0 .../ResourceInheritance/MenController.cs | 15 --- .../ResourceInheritance/{Models => }/Video.cs | 0 .../ResourceInheritance/{Models => }/Woman.cs | 0 .../SoftDeletion/CompaniesController.cs | 16 --- .../SoftDeletion/DepartmentsController.cs | 16 --- .../ZeroKeys/GamesController.cs | 16 --- .../ZeroKeys/MapsController.cs | 17 --- .../ZeroKeys/PlayersController.cs | 16 --- 59 files changed, 179 insertions(+), 670 deletions(-) delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/BlogsController.cs delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/ThrowingResourcesController.cs delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs rename src/Examples/JsonApiDotNetCoreExample/Definitions/{ArticleHooksDefinition.cs => ArticleHooks.cs} (84%) rename src/Examples/JsonApiDotNetCoreExample/Definitions/{PassportHooksDefinition.cs => PassportHooks.cs} (90%) rename src/Examples/JsonApiDotNetCoreExample/Definitions/{PersonHooksDefinition.cs => PersonHooks.cs} (100%) rename src/Examples/JsonApiDotNetCoreExample/Definitions/{TagHooksDefinition.cs => TagHooks.cs} (100%) rename src/Examples/JsonApiDotNetCoreExample/Definitions/{TodoHooksDefinition.cs => TodoItemHooks.cs} (100%) create mode 100644 src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs delete mode 100644 src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs create mode 100644 src/JsonApiDotNetCore/Configuration/JsonApiControllerGenerator.cs rename test/JsonApiDotNetCoreExampleTests/Acceptance/{ResourceDefinitions/ResourceDefinitionTests.cs => ResourceHooks/ResourceHooksTests.cs} (100%) delete mode 100644 test/JsonApiDotNetCoreExampleTests/Factories/ResourceHooksApplicationFactory.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Filtering/FilterableResourcesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/RgbColorsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/UserAccountsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemGroupsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/CustomersController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/OrdersController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/ShipmentsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/CallableResourcesController.cs rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/Book.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/CompanyHealthInsurance.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/ContentItem.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/FamilyHealthInsurance.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/HealthInsurance.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/Human.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/HumanFavoriteContentItem.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/Man.cs (100%) delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/MenController.cs rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/Video.cs (100%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models => }/Woman.cs (100%) delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/CompaniesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/DepartmentsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/GamesController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/MapsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/PlayersController.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs deleted file mode 100644 index 789c31cb95..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/AuthorsController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class AuthorsController : JsonApiController - { - public AuthorsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/BlogsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/BlogsController.cs deleted file mode 100644 index 824b7a30a6..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/BlogsController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class BlogsController : JsonApiController - { - public BlogsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs deleted file mode 100644 index 62fa1e96c3..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PassportsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class PassportsController : JsonApiController - { - public PassportsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs deleted file mode 100644 index 4b0116ece6..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class PeopleController : JsonApiController - { - public PeopleController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs deleted file mode 100644 index 75c930126f..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class PersonRolesController : JsonApiController - { - public PersonRolesController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/ThrowingResourcesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/ThrowingResourcesController.cs deleted file mode 100644 index 8b662cde09..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/ThrowingResourcesController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class ThrowingResourcesController : JsonApiController - { - public ThrowingResourcesController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs deleted file mode 100644 index 2411879bb7..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ /dev/null @@ -1,28 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class UsersController : JsonApiController - { - public UsersController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } - - public sealed class SuperUsersController : JsonApiController - { - public SuperUsersController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooks.cs similarity index 84% rename from src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs rename to src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooks.cs index 9c91823669..5d1ac74dee 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/ArticleHooks.cs @@ -10,9 +10,9 @@ namespace JsonApiDotNetCoreExample.Definitions { - public class ArticleHooksDefinition : ResourceHooksDefinition
+ public class ArticleHooks : ResourceHooksDefinition
{ - public ArticleHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public ArticleHooks(IResourceGraph resourceGraph) : base(resourceGraph) { } public override IEnumerable
OnReturn(HashSet
resources, ResourcePipeline pipeline) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooks.cs similarity index 90% rename from src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs rename to src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooks.cs index 2e211305ca..b219e7691e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooks.cs @@ -10,9 +10,9 @@ namespace JsonApiDotNetCoreExample.Definitions { - public class PassportHooksDefinition : ResourceHooksDefinition + public class PassportHooks : ResourceHooksDefinition { - public PassportHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) + public PassportHooks(IResourceGraph resourceGraph) : base(resourceGraph) { } diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooks.cs similarity index 100% rename from src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooksDefinition.cs rename to src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooks.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooks.cs similarity index 100% rename from src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooksDefinition.cs rename to src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooks.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooks.cs similarity index 100% rename from src/Examples/JsonApiDotNetCoreExample/Definitions/TodoHooksDefinition.cs rename to src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooks.cs diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs new file mode 100644 index 0000000000..6f3adb85f8 --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace JsonApiDotNetCore.Configuration +{ + // TODO add comments + public interface IJsonApiControllerGenerator : IApplicationFeatureProvider { } +} diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 6fd6a62b39..6d44ecc3ba 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -187,6 +187,12 @@ public interface IJsonApiOptions /// JsonSerializerSettings SerializerSettings { get; } + /// + /// Determines whether JsonApiDotNetCore will dynamically register controllers for every registered resource + /// in the resource graph. Defaults to true. + /// + bool AutoGenerateControllers { get; } + internal DefaultContractResolver SerializerContractResolver => (DefaultContractResolver) SerializerSettings.ContractResolver; } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index 0f9b9a079c..dd472cebf8 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -37,7 +37,8 @@ internal sealed class JsonApiApplicationBuilder : IJsonApiApplicationBuilder, ID private readonly ResourceGraphBuilder _resourceGraphBuilder; private readonly ServiceDiscoveryFacade _serviceDiscoveryFacade; private readonly ServiceProvider _intermediateProvider; - + private IResourceGraph _resourceGraph; + public Action ConfigureMvcOptions { get; set; } public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder) @@ -83,8 +84,8 @@ public void AddResourceGraph(ICollection dbContextTypes, Action @@ -92,7 +93,6 @@ public void AddResourceGraph(ICollection dbContextTypes, Action public void ConfigureMvc() { - _mvcBuilder.AddMvcOptions(options => { options.EnableEndpointRouting = true; @@ -102,7 +102,10 @@ public void ConfigureMvc() ConfigureMvcOptions?.Invoke(options); }); - _mvcBuilder.ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(new JsonApiControllerFeatureProvider())); + if (_options.AutoGenerateControllers) + { + RegisterControllerFeatureProvider(); + } if (_options.ValidateModelState) { @@ -111,6 +114,27 @@ public void ConfigureMvc() } } + private void RegisterControllerFeatureProvider() + { + IJsonApiControllerGenerator controllerFeatureProvider; + if (_services.Any(descriptor => + { + return descriptor.ServiceType == typeof(IJsonApiControllerGenerator); + })) + { + using (var provider = _services.BuildServiceProvider()) + { + controllerFeatureProvider = provider.GetRequiredService(); + } + } + else + { + controllerFeatureProvider = new JsonApiControllerGenerator(_resourceGraph); + } + + _mvcBuilder.ConfigureApplicationPartManager(manager => manager.FeatureProviders.Add(controllerFeatureProvider)); + } + /// /// Discovers DI registrable services in the assemblies marked for discovery. /// diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs b/src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs deleted file mode 100644 index 7ad8798c74..0000000000 --- a/src/JsonApiDotNetCore/Configuration/JsonApiControllerFeatureProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using JsonApiDotNetCore.Controllers; -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.AspNetCore.Mvc.Controllers; - -namespace JsonApiDotNetCore.Configuration -{ - public class JsonApiControllerFeatureProvider : IApplicationFeatureProvider - { - public void PopulateFeature(IEnumerable parts, ControllerFeature feature) - { - var currentAssembly = Assembly.GetCallingAssembly(); - - var resourceDescriptors = currentAssembly - .GetExportedTypes() - .Select(TypeLocator.TryGetResourceDescriptor) - .Where(descriptor => descriptor != null); - - foreach (var descriptor in resourceDescriptors) - { - feature.Controllers.Add(typeof(BaseJsonApiController<,>).MakeGenericType(descriptor.ResourceType, descriptor.IdType).GetTypeInfo()); - } - } - } -} diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiControllerGenerator.cs b/src/JsonApiDotNetCore/Configuration/JsonApiControllerGenerator.cs new file mode 100644 index 0000000000..564c704c4f --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/JsonApiControllerGenerator.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JsonApiDotNetCore.Controllers; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace JsonApiDotNetCore.Configuration +{ + public class JsonApiControllerGenerator : IJsonApiControllerGenerator + { + private readonly IResourceGraph _resourceGraph; + private readonly Type _controllerOpenType; + private readonly Type _baseControllerOpenType; + + public JsonApiControllerGenerator(IResourceGraph resourceGraph) + { + _resourceGraph = resourceGraph ?? throw new ArgumentNullException(nameof(resourceGraph)); + _controllerOpenType = typeof(JsonApiController<,>); + _baseControllerOpenType = typeof(BaseJsonApiController<,>); + } + + public void PopulateFeature(IEnumerable parts, ControllerFeature feature) + { + var exposedTypes = parts.SelectMany(part => ((AssemblyPart)part).Types).ToList(); + foreach (var resourceContext in _resourceGraph.GetResourceContexts()) + { + RegisterControllerForResource(feature, resourceContext, exposedTypes); + } + } + + + private void RegisterControllerForResource(ControllerFeature feature, ResourceContext resourceContext, List exposedTypes) + { + if (resourceContext != null && !resourceContext.ResourceType.IsAbstract) + { + var existingControllerType = _baseControllerOpenType.MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType).GetTypeInfo(); + if (!exposedTypes.Any(exposedType => existingControllerType.IsAssignableFrom(exposedType))) + { + var controllerType = GetControllerType(resourceContext); + feature.Controllers.Add(controllerType); + } + } + } + + protected virtual TypeInfo GetControllerType(ResourceContext resourceContext) + { + var controllerType = _controllerOpenType.MakeGenericType(resourceContext.ResourceType, resourceContext.IdentityType).GetTypeInfo(); + return controllerType; + } + } +} diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index c999508574..2404e155ce 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -76,6 +76,9 @@ public sealed class JsonApiOptions : IJsonApiOptions } }; + /// + public bool AutoGenerateControllers { get; set; } = true; + // Workaround for https://github.com/dotnet/efcore/issues/21026 internal bool DisableTopPagination { get; set; } internal bool DisableChildrenPagination { get; set; } diff --git a/src/JsonApiDotNetCore/Middleware/IControllerResourceMapping.cs b/src/JsonApiDotNetCore/Middleware/IControllerResourceMapping.cs index de48544e79..19ac3d7e19 100644 --- a/src/JsonApiDotNetCore/Middleware/IControllerResourceMapping.cs +++ b/src/JsonApiDotNetCore/Middleware/IControllerResourceMapping.cs @@ -10,6 +10,6 @@ public interface IControllerResourceMapping /// /// Get the associated resource type for the provided controller name. /// - Type GetAssociatedResource(string controllerName); + Type GetResourceForEndpoint(string controllerName); } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index fee92a14dc..49ab589dfa 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -12,6 +12,7 @@ using JsonApiDotNetCore.Serialization.Objects; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; @@ -44,9 +45,8 @@ public async Task Invoke(HttpContext httpContext, if (request == null) throw new ArgumentNullException(nameof(request)); if (resourceContextProvider == null) throw new ArgumentNullException(nameof(resourceContextProvider)); - var routeValues = httpContext.GetRouteData().Values; - - var primaryResourceContext = CreatePrimaryResourceContext(routeValues, controllerResourceMapping, resourceContextProvider); + // TODO: endpoint is null when route is not found. Instead of doing half work here, we should consider just throwing a 404 here already. + var primaryResourceContext = GetPrimaryResourceContext(httpContext.GetEndpoint(), controllerResourceMapping, resourceContextProvider); if (primaryResourceContext != null) { if (!await ValidateContentTypeHeaderAsync(httpContext, options.SerializerSettings) || @@ -55,7 +55,7 @@ public async Task Invoke(HttpContext httpContext, return; } - SetupRequest((JsonApiRequest)request, primaryResourceContext, routeValues, options, resourceContextProvider, httpContext.Request); + SetupRequest((JsonApiRequest)request, primaryResourceContext, httpContext.GetRouteData().Values, options, resourceContextProvider, httpContext.Request); httpContext.RegisterJsonApiRequest(); } @@ -63,13 +63,14 @@ public async Task Invoke(HttpContext httpContext, await _next(httpContext); } - private static ResourceContext CreatePrimaryResourceContext(RouteValueDictionary routeValues, - IControllerResourceMapping controllerResourceMapping, IResourceContextProvider resourceContextProvider) + private static ResourceContext GetPrimaryResourceContext(Endpoint endpoint, IControllerResourceMapping controllerResourceMapping, IResourceContextProvider resourceContextProvider) { - var controllerName = (string) routeValues["controller"]; + var controllerDescriptor = (ControllerActionDescriptor)endpoint?.Metadata.Single(meta => meta is ControllerActionDescriptor); + var controllerName = controllerDescriptor?.ControllerTypeInfo.FullName; + if (controllerName != null) { - var resourceType = controllerResourceMapping.GetAssociatedResource(controllerName); + var resourceType = controllerResourceMapping.GetResourceForEndpoint(controllerName); if (resourceType != null) { return resourceContextProvider.GetResourceContext(resourceType); diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index 81192b1c29..c074d54386 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -32,10 +32,9 @@ public class JsonApiRoutingConvention : IJsonApiRoutingConvention { private readonly IJsonApiOptions _options; private readonly IResourceGraph _resourceGraph; - private readonly HashSet _registeredTemplates = new HashSet(); + private readonly Dictionary _endpointsByRoutes = new Dictionary(); - private readonly Dictionary _registeredResources = - new Dictionary(); + private readonly Dictionary _resourcesByEndpoint = new Dictionary(); public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resourceGraph) { @@ -44,11 +43,11 @@ public JsonApiRoutingConvention(IJsonApiOptions options, IResourceGraph resource } /// - public Type GetAssociatedResource(string controllerName) + public Type GetResourceForEndpoint(string controllerName) { if (controllerName == null) throw new ArgumentNullException(nameof(controllerName)); - if (_registeredResources.TryGetValue(controllerName, out var resourceContext)) + if (_resourcesByEndpoint.TryGetValue(controllerName, out var resourceContext)) { return resourceContext.ResourceType; } @@ -61,36 +60,71 @@ public void Apply(ApplicationModel application) { if (application == null) throw new ArgumentNullException(nameof(application)); - foreach (var controller in application.Controllers) + RegisterResourcesByEndpoint(application.Controllers); + RegisterRoutesForEndpoints(application.Controllers); + } + + private void RegisterRoutesForEndpoints(IEnumerable controllers) + { + foreach (var model in controllers) { - var resourceType = ExtractResourceTypeFromController(controller.ControllerType); + if (!RoutingConventionDisabled(model)) + { + continue; + } + + var template = GetRouteTemplateForEndpoint(model); + + model.Selectors[0].AttributeRouteModel = new AttributeRouteModel {Template = template}; + _endpointsByRoutes.Add(template, model); + } + } + + private string GetRouteTemplateForEndpoint(ControllerModel controllerModel) + { + var template = TryGetResourceBasedTemplate(controllerModel); + if (template == null || _endpointsByRoutes.ContainsKey(template)) + { + template = TryGetControllerBasedTemplate(controllerModel); + } + + if (template == null) + { + throw new InvalidConfigurationException($"Failed to create a template for {controllerModel.ControllerType.FullName}. "); + } + + if (_endpointsByRoutes.ContainsKey(template)) + { + var overlappingEndpoint = _endpointsByRoutes[template]; + throw new InvalidConfigurationException( + $"Cannot register template {template} for {controllerModel.ControllerType.FullName} " + + $"because it is already registered for {overlappingEndpoint.ControllerType.FullName}."); + } + + return template; + } + private void RegisterResourcesByEndpoint(IEnumerable controllers) + { + foreach (var model in controllers) + { + var resourceType = ExtractResourceTypeForEndpoint(model.ControllerType); if (resourceType != null) { var resourceContext = _resourceGraph.GetResourceContext(resourceType); - if (resourceContext != null) { - _registeredResources.Add(controller.ControllerName, resourceContext); - } - } + if (resourceContext.ResourceType.Name.Contains("Address")) + { - if (!RoutingConventionDisabled(controller)) - { - continue; - } - - var template = TemplateFromResource(controller) ?? TemplateFromController(controller); - if (template == null) - { - throw new InvalidConfigurationException( - $"Controllers with overlapping route templates detected: {controller.ControllerType.FullName}"); + } + _resourcesByEndpoint.Add(model.ControllerType.FullName, resourceContext); + } } - - controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel { Template = template }; } } + /// /// Verifies if routing convention should be enabled for this controller. /// @@ -101,18 +135,11 @@ private bool RoutingConventionDisabled(ControllerModel controller) return notDisabled && type.IsSubclassOf(typeof(CoreJsonApiController)); } - /// - /// Derives a template from the resource type, and checks if this template was already registered. - /// - private string TemplateFromResource(ControllerModel model) + private string TryGetResourceBasedTemplate(ControllerModel model) { - if (_registeredResources.TryGetValue(model.ControllerName, out var resourceContext)) + if (_resourcesByEndpoint.TryGetValue(model.ControllerType.FullName, out var resourceContext)) { - var template = $"{_options.Namespace}/{resourceContext.PublicName}"; - if (_registeredTemplates.Add(template)) - { - return template; - } + return $"{_options.Namespace}/{resourceContext.PublicName}"; } return null; @@ -121,25 +148,21 @@ private string TemplateFromResource(ControllerModel model) /// /// Derives a template from the controller name, and checks if this template was already registered. /// - private string TemplateFromController(ControllerModel model) + private string TryGetControllerBasedTemplate(ControllerModel model) { - string controllerName = - _options.SerializerContractResolver.NamingStrategy.GetPropertyName(model.ControllerName, false); - - var template = $"{_options.Namespace}/{controllerName}"; - - if (_registeredTemplates.Add(template)) + if (!model.ControllerType.IsGenericType || model.ControllerType.GetGenericTypeDefinition() != typeof(BaseJsonApiController<,>)) { - return template; + var controllerName = _options.SerializerContractResolver.NamingStrategy!.GetPropertyName(model.ControllerName, false); + return $"{_options.Namespace}/{controllerName}"; } - + return null; } /// /// Determines the resource associated to a controller by inspecting generic arguments in its inheritance tree. /// - private Type ExtractResourceTypeFromController(Type type) + private Type ExtractResourceTypeForEndpoint(Type type) { var aspNetControllerType = typeof(ControllerBase); var coreControllerType = typeof(CoreJsonApiController); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs rename to test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs diff --git a/test/JsonApiDotNetCoreExampleTests/Factories/ResourceHooksApplicationFactory.cs b/test/JsonApiDotNetCoreExampleTests/Factories/ResourceHooksApplicationFactory.cs deleted file mode 100644 index 72f879054b..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/Factories/ResourceHooksApplicationFactory.cs +++ /dev/null @@ -1,33 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; - -namespace JsonApiDotNetCoreExampleTests -{ - public class ResourceHooksApplicationFactory : CustomApplicationFactoryBase - { - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - base.ConfigureWebHost(builder); - - builder.ConfigureServices(services => - { - services.AddClientSerialization(); - }); - - builder.ConfigureTestServices(services => - { - services.AddJsonApi(options => - { - options.Namespace = "api/v1"; - options.DefaultPageSize = new PageSize(5); - options.IncludeTotalResourceCount = true; - options.EnableResourceHooks = true; - options.LoadDatabaseValues = true; - options.IncludeExceptionStackTraceInErrors = true; - }, - discovery => discovery.AddAssembly(typeof(JsonApiDotNetCoreExample.Program).Assembly)); - }); - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs deleted file mode 100644 index f264c043e3..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/CarsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys -{ - public sealed class CarsController : JsonApiController - { - public CarsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs deleted file mode 100644 index 53b4f281e1..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/DealershipsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys -{ - public sealed class DealershipsController : JsonApiController - { - public DealershipsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs deleted file mode 100644 index 4833292cd8..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/CompositeKeys/EnginesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.CompositeKeys -{ - public sealed class EnginesController : JsonApiController - { - public EnginesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs deleted file mode 100644 index d99ab9bd6a..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/PoliciesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ContentNegotiation -{ - public sealed class PoliciesController : JsonApiController - { - public PoliciesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs deleted file mode 100644 index 4a0b9bb366..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/BuildingsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading -{ - public sealed class BuildingsController : JsonApiController - { - public BuildingsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs deleted file mode 100644 index 28c8b795b8..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StatesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading -{ - public sealed class StatesController : JsonApiController - { - public StatesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs deleted file mode 100644 index e6b4eda7e1..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/EagerLoading/StreetsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.EagerLoading -{ - public sealed class StreetsController : JsonApiController - { - public StreetsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Filtering/FilterableResourcesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Filtering/FilterableResourcesController.cs deleted file mode 100644 index 7314d155d0..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Filtering/FilterableResourcesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Filtering -{ - public sealed class FilterableResourcesController : JsonApiController - { - public FilterableResourcesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs deleted file mode 100644 index 91793dfc8c..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/BankAccountsController.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation -{ - public sealed class BankAccountsController : ObfuscatedIdentifiableController - { - public BankAccountsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs deleted file mode 100644 index b72cea109e..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCardsController.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation -{ - public sealed class DebitCardsController : ObfuscatedIdentifiableController - { - public DebitCardsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs deleted file mode 100644 index 5228903c5d..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemDirectoriesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ModelStateValidation -{ - public sealed class SystemDirectoriesController : JsonApiController - { - public SystemDirectoriesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs deleted file mode 100644 index 9278f59766..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ModelStateValidation/SystemFilesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ModelStateValidation -{ - public sealed class SystemFilesController : JsonApiController - { - public SystemFilesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/RgbColorsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/RgbColorsController.cs deleted file mode 100644 index 9d8e32bc25..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/RgbColorsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ReadWrite -{ - public sealed class RgbColorsController : JsonApiController - { - public RgbColorsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/UserAccountsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/UserAccountsController.cs deleted file mode 100644 index 9d409bbb57..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/UserAccountsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ReadWrite -{ - public sealed class UserAccountsController : JsonApiController - { - public UserAccountsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemGroupsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemGroupsController.cs deleted file mode 100644 index c6b00f25e3..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemGroupsController.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ReadWrite -{ - public sealed class WorkItemGroupsController : JsonApiController - { - public WorkItemGroupsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemsController.cs deleted file mode 100644 index e3e90fe0f3..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ReadWrite/WorkItemsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ReadWrite -{ - public sealed class WorkItemsController : JsonApiController - { - public WorkItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/CustomersController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/CustomersController.cs deleted file mode 100644 index d611659bd0..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/CustomersController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.RequiredRelationships -{ - public sealed class CustomersController : JsonApiController - { - public CustomersController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/OrdersController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/OrdersController.cs deleted file mode 100644 index c6373a9c95..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/OrdersController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.RequiredRelationships -{ - public sealed class OrdersController : JsonApiController - { - public OrdersController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/ShipmentsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/ShipmentsController.cs deleted file mode 100644 index 03f1e2c82f..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/ShipmentsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.RequiredRelationships -{ - public sealed class ShipmentsController : JsonApiController - { - public ShipmentsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/CallableResourcesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/CallableResourcesController.cs deleted file mode 100644 index fba77b6b8f..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/CallableResourcesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceDefinitions -{ - public sealed class CallableResourcesController : JsonApiController - { - public CallableResourcesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Book.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Book.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Book.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Book.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/CompanyHealthInsurance.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/CompanyHealthInsurance.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/CompanyHealthInsurance.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/CompanyHealthInsurance.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/ContentItem.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ContentItem.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/ContentItem.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ContentItem.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/FamilyHealthInsurance.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/FamilyHealthInsurance.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/FamilyHealthInsurance.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/FamilyHealthInsurance.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/HealthInsurance.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/HealthInsurance.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/HealthInsurance.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/HealthInsurance.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Human.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Human.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Human.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Human.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/HumanFavoriteContentItem.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/HumanFavoriteContentItem.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/HumanFavoriteContentItem.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/HumanFavoriteContentItem.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Man.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Man.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Man.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Man.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/MenController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/MenController.cs deleted file mode 100644 index 43b6fd97fa..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/MenController.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public sealed class MenController : JsonApiController - { - public MenController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) { } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Video.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Video.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Video.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Video.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Woman.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Woman.cs similarity index 100% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Woman.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Woman.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/CompaniesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/CompaniesController.cs deleted file mode 100644 index 17fe2da387..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/CompaniesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion -{ - public sealed class CompaniesController : JsonApiController - { - public CompaniesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/DepartmentsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/DepartmentsController.cs deleted file mode 100644 index 01a77f9b08..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/DepartmentsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion -{ - public sealed class DepartmentsController : JsonApiController - { - public DepartmentsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/GamesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/GamesController.cs deleted file mode 100644 index 2dc928de67..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/GamesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ZeroKeys -{ - public sealed class GamesController : JsonApiController - { - public GamesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/MapsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/MapsController.cs deleted file mode 100644 index d7fb295509..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/MapsController.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ZeroKeys -{ - public sealed class MapsController : JsonApiController - { - public MapsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/PlayersController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/PlayersController.cs deleted file mode 100644 index 3313d7e4e3..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ZeroKeys/PlayersController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ZeroKeys -{ - public sealed class PlayersController : JsonApiController - { - public PlayersController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} From b93630da10bd7182d9176a0402508bff6a54dfcc Mon Sep 17 00:00:00 2001 From: maurei Date: Wed, 2 Dec 2020 21:19:05 +0000 Subject: [PATCH 3/7] fix: hook tests --- .../Definitions/PersonHooks.cs | 4 +- .../Definitions/TagHooks.cs | 4 +- .../Definitions/TodoItemHooks.cs | 4 +- .../ResourceHooks/ResourceHooksStartup.cs | 42 ++ .../ResourceHooks/ResourceHooksTests.cs | 547 +++++++++--------- 5 files changed, 313 insertions(+), 288 deletions(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksStartup.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooks.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooks.cs index 42c1b405e4..f640151443 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooks.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PersonHooks.cs @@ -6,9 +6,9 @@ namespace JsonApiDotNetCoreExample.Definitions { - public class PersonHooksDefinition : LockableHooksDefinition + public class PersonHooks : LockableHooksDefinition { - public PersonHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public PersonHooks(IResourceGraph resourceGraph) : base(resourceGraph) { } public override IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary resourcesByRelationship, ResourcePipeline pipeline) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooks.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooks.cs index 4ff4508bf7..90836b4482 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooks.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TagHooks.cs @@ -7,9 +7,9 @@ namespace JsonApiDotNetCoreExample.Definitions { - public class TagHooksDefinition : ResourceHooksDefinition + public class TagHooks : ResourceHooksDefinition { - public TagHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public TagHooks(IResourceGraph resourceGraph) : base(resourceGraph) { } public override IEnumerable BeforeCreate(IResourceHashSet affected, ResourcePipeline pipeline) { diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooks.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooks.cs index c3fc7af2e5..30b9ddc2fe 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooks.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/TodoItemHooks.cs @@ -9,9 +9,9 @@ namespace JsonApiDotNetCoreExample.Definitions { - public class TodoHooksDefinition : LockableHooksDefinition + public class TodoItemHooks : LockableHooksDefinition { - public TodoHooksDefinition(IResourceGraph resourceGraph) : base(resourceGraph) { } + public TodoItemHooks(IResourceGraph resourceGraph) : base(resourceGraph) { } public override void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null) { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksStartup.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksStartup.cs new file mode 100644 index 0000000000..f36175d0e5 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksStartup.cs @@ -0,0 +1,42 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCoreExample.Data; +using JsonApiDotNetCoreExample.Definitions; +using JsonApiDotNetCoreExample.Models; +using JsonApiDotNetCoreExampleTests.IntegrationTests; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace JsonApiDotNetCoreExampleTests.Acceptance +{ + public sealed class ResourceHooksStartup : TestableStartup + { + public ResourceHooksStartup(IConfiguration configuration) : base(configuration) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddClientSerialization(); + + services.AddSingleton(); + + services.AddTransient, ArticleHooks>(); + services.AddTransient, PassportHooks>(); + services.AddTransient, TagHooks>(); + services.AddTransient, PersonHooks>(); + services.AddTransient, TodoItemHooks>(); + + base.ConfigureServices(services); + } + + protected override void SetJsonApiOptions(JsonApiOptions options) + { + base.SetJsonApiOptions(options); + + options.EnableResourceHooks = true; + options.LoadDatabaseValues = true; + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs index d3929c6a6c..2c55038e6d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs @@ -1,24 +1,28 @@ +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; using System.Threading.Tasks; using Bogus; using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Serialization.Client.Internal; using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCoreExampleTests.Acceptance.Spec; +using JsonApiDotNetCoreExampleTests.Helpers.Models; +using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; using Xunit; using Person = JsonApiDotNetCoreExample.Models.Person; namespace JsonApiDotNetCoreExampleTests.Acceptance { - public sealed class ResourceDefinitionTests : FunctionalTestCollection + public sealed class ResourceDefinitionTests : IClassFixture> { private readonly Faker _userFaker; private readonly Faker _todoItemFaker; @@ -27,8 +31,13 @@ public sealed class ResourceDefinitionTests : FunctionalTestCollection _authorFaker; private readonly Faker _tagFaker; - public ResourceDefinitionTests(ResourceHooksApplicationFactory factory) : base(factory) + private readonly IntegrationTestContext _testContext; + private readonly IResponseDeserializer _deserializer; + + public ResourceDefinitionTests(IntegrationTestContext testContext) { + _testContext = testContext; + _authorFaker = new Faker() .RuleFor(a => a.LastName, f => f.Random.Words(2)); @@ -36,8 +45,11 @@ public ResourceDefinitionTests(ResourceHooksApplicationFactory factory) : base(f .RuleFor(a => a.Caption, f => f.Random.AlphaNumeric(10)) .RuleFor(a => a.Author, f => _authorFaker.Generate()); + var systemClock = testContext.Factory.Services.GetRequiredService(); + var options = testContext.Factory.Services.GetRequiredService>(); + var tempDbContext = new AppDbContext(options, systemClock); _userFaker = new Faker() - .CustomInstantiator(f => new User(_dbContext)) + .CustomInstantiator(f => new User(tempDbContext)) .RuleFor(u => u.UserName, f => f.Internet.UserName()) .RuleFor(u => u.Password, f => f.Internet.Password()); @@ -54,9 +66,10 @@ public ResourceDefinitionTests(ResourceHooksApplicationFactory factory) : base(f .CustomInstantiator(f => new Tag()) .RuleFor(a => a.Name, f => f.Random.AlphaNumeric(10)); - var options = (JsonApiOptions) _factory.Services.GetRequiredService(); - options.DisableTopPagination = false; - options.DisableChildrenPagination = false; + _deserializer = GetDeserializer(); + // var options = (JsonApiOptions) _factory.Services.GetRequiredService(); + // options.DisableTopPagination = false; + // options.DisableChildrenPagination = false; } [Fact] @@ -64,35 +77,27 @@ public async Task Can_Create_User_With_Password() { // Arrange var user = _userFaker.Generate(); - var serializer = GetSerializer(p => new { p.Password, p.UserName }); - - var httpMethod = new HttpMethod("POST"); - var route = "/api/v1/users"; - - var request = new HttpRequestMessage(httpMethod, route) - { - Content = new StringContent(serializer.Serialize(user)) - }; - request.Content.Headers.ContentType = new MediaTypeHeaderValue(HeaderConstants.MediaType); + var route = "/users"; // Act - var response = await _client.SendAsync(request); + var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, serializer.Serialize(user)); // Assert - Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal(HttpStatusCode.Created, httpResponse.StatusCode); - // response assertions - var body = await response.Content.ReadAsStringAsync(); + var body = await httpResponse.Content.ReadAsStringAsync(); var returnedUser = _deserializer.DeserializeSingle(body).Data; var document = JsonConvert.DeserializeObject(body); Assert.False(document.SingleData.Attributes.ContainsKey("password")); Assert.Equal(user.UserName, document.SingleData.Attributes["userName"]); - // db assertions - var dbUser = await _dbContext.Users.FindAsync(returnedUser.Id); - Assert.Equal(user.UserName, dbUser.UserName); - Assert.Equal(user.Password, dbUser.Password); + await _testContext.RunOnDatabaseAsync(async dbContext => + { + var dbUser = await dbContext.Users.FindAsync(returnedUser.Id); + Assert.Equal(user.UserName, dbUser.UserName); + Assert.Equal(user.Password, dbUser.Password); + }); } [Fact] @@ -100,101 +105,87 @@ public async Task Can_Update_User_Password() { // Arrange var user = _userFaker.Generate(); - _dbContext.Users.Add(user); - await _dbContext.SaveChangesAsync(); - + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Users.Add(user); + await dbContext.SaveChangesAsync(); + }); + user.Password = _userFaker.Generate().Password; var serializer = GetSerializer(p => new { p.Password }); - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/users/{user.Id}"; - var request = new HttpRequestMessage(httpMethod, route) - { - Content = new StringContent(serializer.Serialize(user)) - }; - request.Content.Headers.ContentType = new MediaTypeHeaderValue(HeaderConstants.MediaType); + var route = $"/users/{user.Id}"; // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, serializer.Serialize((user))); + // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - // response assertions - var body = await response.Content.ReadAsStringAsync(); - var document = JsonConvert.DeserializeObject(body); - Assert.False(document.SingleData.Attributes.ContainsKey("password")); - Assert.Equal(user.UserName, document.SingleData.Attributes["userName"]); - - // db assertions - var dbUser = _dbContext.Users.AsNoTracking().Single(u => u.Id == user.Id); - Assert.Equal(user.Password, dbUser.Password); + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + Assert.False(responseDocument.SingleData.Attributes.ContainsKey("password")); + Assert.Equal(user.UserName, responseDocument.SingleData.Attributes["userName"]); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + var dbUser = dbContext.Users.Single(u => u.Id == user.Id); + Assert.Equal(user.Password, dbUser.Password); + }); } - + [Fact] public async Task Unauthorized_TodoItem() { // Arrange - var route = "/api/v1/todoItems/1337"; - + var route = "/todoItems/1337"; + // Act - var response = await _client.GetAsync(route); - + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update the author of todo items.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update the author of todo items.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Unauthorized_Passport() { // Arrange - var route = "/api/v1/people/1?include=passport"; - + var route = "/people/1?include=passport"; + // Act - var response = await _client.GetAsync(route); - + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to include passports on individual persons.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to include passports on individual persons.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Unauthorized_Article() { // Arrange var article = _articleFaker.Generate(); article.Caption = "Classified"; - _dbContext.Articles.Add(article); - await _dbContext.SaveChangesAsync(); - - var route = $"/api/v1/articles/{article.Id}"; + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Articles.Add(article); + await dbContext.SaveChangesAsync(); + }); + var route = $"/articles/{article.Id}"; + // Act - var response = await _client.GetAsync(route); - + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to see this article.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to see this article.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Article_Is_Hidden() { @@ -202,21 +193,21 @@ public async Task Article_Is_Hidden() var articles = _articleFaker.Generate(3); string toBeExcluded = "This should not be included"; articles[0].Caption = toBeExcluded; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Articles.AddRange(articles); + await dbContext.SaveChangesAsync(); + }); - _dbContext.Articles.AddRange(articles); - await _dbContext.SaveChangesAsync(); - - var route = "/api/v1/articles"; - + var route = "/articles"; + // Act - var response = await _client.GetAsync(route); - - // Assert - var body = await response.Content.ReadAsStringAsync(); - Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with body: {body}"); - Assert.DoesNotContain(toBeExcluded, body); + var (httpResponse, responseBody) = await _testContext.ExecuteGetAsync(route); + + Assert.DoesNotContain(toBeExcluded, responseBody); } - + [Fact] public async Task Tag_Is_Hidden() { @@ -225,7 +216,7 @@ public async Task Tag_Is_Hidden() var tags = _tagFaker.Generate(2); string toBeExcluded = "This should not be included"; tags[0].Name = toBeExcluded; - + var articleTags = new[] { new ArticleTag @@ -239,23 +230,23 @@ public async Task Tag_Is_Hidden() Tag = tags[1] } }; - _dbContext.ArticleTags.AddRange(articleTags); - await _dbContext.SaveChangesAsync(); - + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.ArticleTags.AddRange(articleTags); + await dbContext.SaveChangesAsync(); + }); + // Workaround for https://github.com/dotnet/efcore/issues/21026 - var options = (JsonApiOptions) _factory.Services.GetRequiredService(); + var options = (JsonApiOptions) _testContext.Factory.Services.GetRequiredService(); options.DisableTopPagination = false; options.DisableChildrenPagination = true; - - var route = "/api/v1/articles?include=tags"; - + + var route = "/articles?include=tags"; + // Act - var response = await _client.GetAsync(route); - - // Assert - var body = await response.Content.ReadAsStringAsync(); - Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with body: {body}"); - Assert.DoesNotContain(toBeExcluded, body); + var (httpResponse, responseBody) = await _testContext.ExecuteGetAsync(route); + + Assert.DoesNotContain(toBeExcluded, responseBody); } ///// ///// In the Cascade Permission Error tests, we ensure that all the relevant @@ -270,11 +261,15 @@ public async Task Cascade_Permission_Error_Create_ToOne_Relationship() // Arrange var lockedPerson = _personFaker.Generate(); lockedPerson.IsLocked = true; - var passport = new Passport(_dbContext); - lockedPerson.Passport = passport; - _dbContext.People.AddRange(lockedPerson); - await _dbContext.SaveChangesAsync(); - + Passport passport; + await _testContext.RunOnDatabaseAsync(async dbContext => + { + passport = new Passport(dbContext); + lockedPerson.Passport = passport; + dbContext.People.AddRange(lockedPerson); + await dbContext.SaveChangesAsync(); + }); + var content = new { data = new @@ -290,41 +285,36 @@ public async Task Cascade_Permission_Error_Create_ToOne_Relationship() } } }; - - var httpMethod = new HttpMethod("POST"); - var route = "/api/v1/people"; - var request = new HttpRequestMessage(httpMethod, route); - - string serializedContent = JsonConvert.SerializeObject(content); - request.Content = new StringContent(serializedContent); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(HeaderConstants.MediaType); - + + var route = "/people"; + // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, content); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Cascade_Permission_Error_Updating_ToOne_Relationship() { // Arrange var person = _personFaker.Generate(); - var passport = new Passport(_dbContext) { IsLocked = true }; - person.Passport = passport; - _dbContext.People.AddRange(person); - var newPassport = new Passport(_dbContext); - _dbContext.Passports.Add(newPassport); - await _dbContext.SaveChangesAsync(); - + Passport passport = null; + Passport newPassport = null; + await _testContext.RunOnDatabaseAsync(async dbContext => + { + passport = new Passport(dbContext) { IsLocked = true }; + person.Passport = passport; + newPassport = new Passport(dbContext); + dbContext.People.AddRange(person); + dbContext.Passports.Add(newPassport); + await dbContext.SaveChangesAsync(); + }); + var content = new { data = new @@ -341,40 +331,35 @@ public async Task Cascade_Permission_Error_Updating_ToOne_Relationship() } } }; - - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/people/{person.Id}"; - var request = new HttpRequestMessage(httpMethod, route); - - string serializedContent = JsonConvert.SerializeObject(content); - request.Content = new StringContent(serializedContent); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(HeaderConstants.MediaType); - + + var route = $"/people/{person.Id}"; + // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, content); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update fields or relationships of locked persons.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update fields or relationships of locked persons.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Cascade_Permission_Error_Updating_ToOne_Relationship_Deletion() { // Arrange var person = _personFaker.Generate(); - var passport = new Passport(_dbContext) { IsLocked = true }; - person.Passport = passport; - _dbContext.People.AddRange(person); - var newPassport = new Passport(_dbContext); - _dbContext.Passports.Add(newPassport); - await _dbContext.SaveChangesAsync(); + Passport passport = null; + Passport newPassport = null; + await _testContext.RunOnDatabaseAsync(async dbContext => + { + passport = new Passport(dbContext) { IsLocked = true }; + person.Passport = passport; + newPassport = new Passport(dbContext); + dbContext.People.AddRange(person); + dbContext.Passports.Add(newPassport); + await dbContext.SaveChangesAsync(); + }); var content = new { @@ -392,58 +377,46 @@ public async Task Cascade_Permission_Error_Updating_ToOne_Relationship_Deletion( } } }; - - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/people/{person.Id}"; - var request = new HttpRequestMessage(httpMethod, route); - - string serializedContent = JsonConvert.SerializeObject(content); - request.Content = new StringContent(serializedContent); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(HeaderConstants.MediaType); - + + var route = $"/people/{person.Id}"; + // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, content); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update fields or relationships of locked persons.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update fields or relationships of locked persons.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Cascade_Permission_Error_Delete_ToOne_Relationship() { // Arrange var lockedPerson = _personFaker.Generate(); lockedPerson.IsLocked = true; - var passport = new Passport(_dbContext); - lockedPerson.Passport = passport; - _dbContext.People.AddRange(lockedPerson); - await _dbContext.SaveChangesAsync(); - - var httpMethod = new HttpMethod("DELETE"); - var route = $"/api/v1/passports/{lockedPerson.Passport.StringId}"; - var request = new HttpRequestMessage(httpMethod, route); - + Passport passport = null; + await _testContext.RunOnDatabaseAsync(async dbContext => + { + passport = new Passport(dbContext); + lockedPerson.Passport = passport; + dbContext.People.AddRange(lockedPerson); + await dbContext.SaveChangesAsync(); + }); + + var route = $"/passports/{lockedPerson.Passport.StringId}"; + // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Cascade_Permission_Error_Create_ToMany_Relationship() { @@ -452,9 +425,12 @@ public async Task Cascade_Permission_Error_Create_ToMany_Relationship() var lockedTodo = _todoItemFaker.Generate(); lockedTodo.IsLocked = true; lockedTodo.StakeHolders = persons.ToHashSet(); - _dbContext.TodoItems.Add(lockedTodo); - await _dbContext.SaveChangesAsync(); - + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.TodoItems.Add(lockedTodo); + await dbContext.SaveChangesAsync(); + }); + var content = new { data = new @@ -469,35 +445,25 @@ public async Task Cascade_Permission_Error_Create_ToMany_Relationship() new { type = "people", id = persons[0].StringId }, new { type = "people", id = persons[1].StringId } } - + } } } } }; - - var httpMethod = new HttpMethod("POST"); - var route = "/api/v1/todoItems"; - var request = new HttpRequestMessage(httpMethod, route); - - string serializedContent = JsonConvert.SerializeObject(content); - request.Content = new StringContent(serializedContent); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(HeaderConstants.MediaType); - + + var route = "/todoItems"; + // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, content); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Cascade_Permission_Error_Updating_ToMany_Relationship() { @@ -506,11 +472,14 @@ public async Task Cascade_Permission_Error_Updating_ToMany_Relationship() var lockedTodo = _todoItemFaker.Generate(); lockedTodo.IsLocked = true; lockedTodo.StakeHolders = persons.ToHashSet(); - _dbContext.TodoItems.Add(lockedTodo); var unlockedTodo = _todoItemFaker.Generate(); - _dbContext.TodoItems.Add(unlockedTodo); - await _dbContext.SaveChangesAsync(); - + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.TodoItems.Add(lockedTodo); + dbContext.TodoItems.Add(unlockedTodo); + await dbContext.SaveChangesAsync(); + }); + var content = new { data = new @@ -526,35 +495,25 @@ public async Task Cascade_Permission_Error_Updating_ToMany_Relationship() new { type = "people", id = persons[0].StringId }, new { type = "people", id = persons[1].StringId } } - + } } } } }; - - var httpMethod = new HttpMethod("PATCH"); - var route = $"/api/v1/todoItems/{unlockedTodo.Id}"; - var request = new HttpRequestMessage(httpMethod, route); - - string serializedContent = JsonConvert.SerializeObject(content); - request.Content = new StringContent(serializedContent); - request.Content.Headers.ContentType = new MediaTypeHeaderValue(HeaderConstants.MediaType); - + + var route = $"/todoItems/{unlockedTodo.Id}"; + // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, content); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); - - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); } - + [Fact] public async Task Cascade_Permission_Error_Delete_ToMany_Relationship() { @@ -563,25 +522,49 @@ public async Task Cascade_Permission_Error_Delete_ToMany_Relationship() var lockedTodo = _todoItemFaker.Generate(); lockedTodo.IsLocked = true; lockedTodo.StakeHolders = persons.ToHashSet(); - _dbContext.TodoItems.Add(lockedTodo); - await _dbContext.SaveChangesAsync(); - - var httpMethod = new HttpMethod("DELETE"); - var route = $"/api/v1/people/{persons[0].Id}"; - var request = new HttpRequestMessage(httpMethod, route); - + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.TodoItems.Add(lockedTodo); + await dbContext.SaveChangesAsync(); + }); + var route = $"/people/{persons[0].Id}"; + // Act - var response = await _client.SendAsync(request); - + var (httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync(route); + // Assert - var body = await response.Content.ReadAsStringAsync(); - AssertEqualStatusCode(HttpStatusCode.Forbidden, response); + Assert.Single(responseDocument.Errors); + Assert.Equal(HttpStatusCode.Forbidden, responseDocument.Errors[0].StatusCode); + Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", responseDocument.Errors[0].Title); + Assert.Null(responseDocument.Errors[0].Detail); + } - var errorDocument = JsonConvert.DeserializeObject(body); - Assert.Single(errorDocument.Errors); - Assert.Equal(HttpStatusCode.Forbidden, errorDocument.Errors[0].StatusCode); - Assert.Equal("You are not allowed to update fields or relationships of locked todo items.", errorDocument.Errors[0].Title); - Assert.Null(errorDocument.Errors[0].Detail); + private IRequestSerializer GetSerializer(Expression> attributes = null, Expression> relationships = null) where TResource : class, IIdentifiable + { + var serializer = _testContext.Factory.Services.GetService(); + var graph = _testContext.Factory.Services.GetService(); + serializer.AttributesToSerialize = attributes != null ? graph.GetAttributes(attributes) : null; + serializer.RelationshipsToSerialize = relationships != null ? graph.GetRelationships(relationships) : null; + return serializer; + } + + private IResponseDeserializer GetDeserializer() + { + var options = _testContext.Factory.Services.GetService(); + var formatter = new ResourceNameFormatter(options); + var resourcesContexts = _testContext.Factory.Services.GetService().GetResourceContexts(); + var builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance); + foreach (var rc in resourcesContexts) + { + if (rc.ResourceType == typeof(TodoItem) || rc.ResourceType == typeof(TodoItemCollection)) + { + continue; + } + builder.Add(rc.ResourceType, rc.IdentityType, rc.PublicName); + } + builder.Add(formatter.FormatResourceName(typeof(TodoItem))); + builder.Add(formatter.FormatResourceName(typeof(TodoItemCollection))); + return new ResponseDeserializer(builder.Build(), new ResourceFactory(_testContext.Factory.Services)); } } } From 0a7b18512dd32abe515205aa25f41428dded4243 Mon Sep 17 00:00:00 2001 From: maurei Date: Wed, 2 Dec 2020 21:21:20 +0000 Subject: [PATCH 4/7] fix: remove unused verbose constructor in JsonApiController so that DI container can instantiate it --- .../Controllers/JsonApiController.cs | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index b403b72dbd..3912aefe94 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -26,24 +26,6 @@ public JsonApiController( : base(options, loggerFactory, resourceService) { } - /// - public JsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IGetAllService getAll = null, - IGetByIdService getById = null, - IGetSecondaryService getSecondary = null, - IGetRelationshipService getRelationship = null, - ICreateService create = null, - IAddToRelationshipService addToRelationship = null, - IUpdateService update = null, - ISetRelationshipService setRelationship = null, - IDeleteService delete = null, - IRemoveFromRelationshipService removeFromRelationship = null) - : base(options, loggerFactory,getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, - setRelationship, delete, removeFromRelationship) - { } - /// [HttpGet] public override async Task GetAsync(CancellationToken cancellationToken) @@ -127,23 +109,5 @@ public JsonApiController( IResourceService resourceService) : base(options, loggerFactory, resourceService) { } - - /// - public JsonApiController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IGetAllService getAll = null, - IGetByIdService getById = null, - IGetSecondaryService getSecondary = null, - IGetRelationshipService getRelationship = null, - ICreateService create = null, - IAddToRelationshipService addToRelationship = null, - IUpdateService update = null, - ISetRelationshipService setRelationship = null, - IDeleteService delete = null, - IRemoveFromRelationshipService removeFromRelationship = null) - : base(options, loggerFactory, getAll, getById, getSecondary, getRelationship, create, addToRelationship, update, - setRelationship, delete, removeFromRelationship) - { } } } From 9c9f4415e40d162edb0f28590cfb24a9c9233bcb Mon Sep 17 00:00:00 2001 From: maurei Date: Wed, 2 Dec 2020 21:21:34 +0000 Subject: [PATCH 5/7] fix: remaining tests --- .../IdObfuscation/DebitCard.cs | 1 + .../IdObfuscation/IdObfuscationStartup.cs | 27 ++++++++++++++++++ .../IdObfuscation/IdObfuscationTests.cs | 7 ++--- .../ObfuscatedIdentifiableController.cs | 4 +-- .../ObfuscatedJsonApiControllerGenerator.cs | 28 +++++++++++++++++++ .../Middleware/JsonApiMiddlewareTests.cs | 6 +++- .../Middleware/JsonApiRequestTests.cs | 8 ++++-- 7 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedJsonApiControllerGenerator.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs index 9bd4bcc789..fcbf03974b 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs @@ -1,3 +1,4 @@ +using System; using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs new file mode 100644 index 0000000000..2e991f214a --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs @@ -0,0 +1,27 @@ +using JsonApiDotNetCore.Configuration; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation +{ + public sealed class IdObfuscationStartup : TestableStartup + { + public IdObfuscationStartup(IConfiguration configuration) : base(configuration) + { + } + + protected override void SetJsonApiOptions(JsonApiOptions options) + { + base.SetJsonApiOptions(options); + + options.ValidateModelState = true; + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + base.ConfigureServices(services); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs index 811f58a7a3..5f3452abbc 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationTests.cs @@ -7,13 +7,12 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation { - public sealed class IdObfuscationTests - : IClassFixture, ObfuscationDbContext>> + public sealed class IdObfuscationTests : IClassFixture> { - private readonly IntegrationTestContext, ObfuscationDbContext> _testContext; + private readonly IntegrationTestContext _testContext; private readonly ObfuscationFakers _fakers = new ObfuscationFakers(); - public IdObfuscationTests(IntegrationTestContext, ObfuscationDbContext> testContext) + public IdObfuscationTests(IntegrationTestContext testContext) { _testContext = testContext; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs index c762873886..849944fde1 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedIdentifiableController.cs @@ -10,10 +10,10 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation { - public abstract class ObfuscatedIdentifiableController : BaseJsonApiController + public sealed class ObfuscatedIdentifiableController : BaseJsonApiController where TResource : class, IIdentifiable { - protected ObfuscatedIdentifiableController(IJsonApiOptions options, ILoggerFactory loggerFactory, + public ObfuscatedIdentifiableController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) : base(options, loggerFactory, resourceService) { diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedJsonApiControllerGenerator.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedJsonApiControllerGenerator.cs new file mode 100644 index 0000000000..467f82c382 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/ObfuscatedJsonApiControllerGenerator.cs @@ -0,0 +1,28 @@ +using System; +using System.Reflection; +using JsonApiDotNetCore.Configuration; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation +{ + public sealed class ObfuscatedJsonApiControllerGenerator : JsonApiControllerGenerator + { + private readonly Type _obfuscatedIdentifiableType; + private readonly Type _obfuscatedControllerOpenType; + + public ObfuscatedJsonApiControllerGenerator(IResourceGraph resourceGraph) : base(resourceGraph) + { + _obfuscatedIdentifiableType = typeof(ObfuscatedIdentifiable); + _obfuscatedControllerOpenType = typeof(ObfuscatedIdentifiableController<>); + } + + protected override TypeInfo GetControllerType(ResourceContext resourceContext) + { + if (_obfuscatedIdentifiableType.IsAssignableFrom(resourceContext.ResourceType)) + { + return _obfuscatedControllerOpenType.MakeGenericType(resourceContext.ResourceType).GetTypeInfo(); + } + + return base.GetControllerType(resourceContext); + } + } +} diff --git a/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs b/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs index 5ab2fcd1e0..34a57cccc9 100644 --- a/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs +++ b/test/UnitTests/Middleware/JsonApiMiddlewareTests.cs @@ -1,12 +1,14 @@ using System; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources.Annotations; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Controllers; using Moq; using Xunit; @@ -81,6 +83,8 @@ private Task RunMiddlewareTask(InvokeConfiguration holder) { var controllerResourceMapping = holder.ControllerResourceMapping.Object; var context = holder.HttpContext; + var endpointMetadata = new EndpointMetadataCollection(new ControllerActionDescriptor {ControllerTypeInfo = typeof(object).GetTypeInfo()}); + context.SetEndpoint(new Endpoint(null, endpointMetadata, null)); var options = holder.Options.Object; var request = holder.Request; var resourceGraph = holder.ResourceGraph.Object; @@ -98,7 +102,7 @@ private InvokeConfiguration GetConfiguration(string path, string resourceName = }); var forcedNamespace = "api/v1"; var mockMapping = new Mock(); - mockMapping.Setup(x => x.GetAssociatedResource(It.IsAny())).Returns(typeof(string)); + mockMapping.Setup(x => x.GetResourceForEndpoint(It.IsAny())).Returns(typeof(string)); Mock mockOptions = CreateMockOptions(forcedNamespace); var mockGraph = CreateMockResourceGraph(resourceName, includeRelationship: relType != null); diff --git a/test/UnitTests/Middleware/JsonApiRequestTests.cs b/test/UnitTests/Middleware/JsonApiRequestTests.cs index f3b2a62508..aaa8e804be 100644 --- a/test/UnitTests/Middleware/JsonApiRequestTests.cs +++ b/test/UnitTests/Middleware/JsonApiRequestTests.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; @@ -7,6 +8,7 @@ using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; @@ -47,7 +49,7 @@ public async Task Sets_request_properties_correctly(string requestMethod, string var controllerResourceMappingMock = new Mock(); controllerResourceMappingMock - .Setup(x => x.GetAssociatedResource(It.IsAny())) + .Setup(x => x.GetResourceForEndpoint(It.IsAny())) .Returns(typeof(Article)); var httpContext = new DefaultHttpContext(); @@ -98,8 +100,10 @@ private static void SetupRoutes(HttpContext httpContext, string requestMethod, s feature.RouteValues["action"] = "Relationship"; } + + var endpointMetadata = new EndpointMetadataCollection(new ControllerActionDescriptor {ControllerTypeInfo = typeof(object).GetTypeInfo()}); httpContext.Features.Set(feature); - httpContext.SetEndpoint(new Endpoint(null, new EndpointMetadataCollection(), null)); + httpContext.SetEndpoint(new Endpoint(null, endpointMetadata, null)); } } } From bb94639915da62ee34be87263fd622b4fc928900 Mon Sep 17 00:00:00 2001 From: maurei Date: Wed, 2 Dec 2020 21:50:08 +0000 Subject: [PATCH 6/7] review --- .../IJsonApiControllerGenerator.cs | 6 ++++- .../JsonApiApplicationBuilder.cs | 5 +--- .../Middleware/JsonApiMiddleware.cs | 27 +++++++++++-------- .../Middleware/JsonApiRoutingConvention.cs | 21 +++++++-------- .../IdObfuscation/DebitCard.cs | 1 - .../IdObfuscation/IdObfuscationStartup.cs | 1 - 6 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs index 6f3adb85f8..7734f37392 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiControllerGenerator.cs @@ -3,6 +3,10 @@ namespace JsonApiDotNetCore.Configuration { - // TODO add comments + /// + /// Service that allows customization of the dynamic controller generation. Register a + /// custom implementation before calling + /// in order to override the default controller generation behaviour. + /// public interface IJsonApiControllerGenerator : IApplicationFeatureProvider { } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs index dd472cebf8..8dac2b59e1 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs @@ -117,10 +117,7 @@ public void ConfigureMvc() private void RegisterControllerFeatureProvider() { IJsonApiControllerGenerator controllerFeatureProvider; - if (_services.Any(descriptor => - { - return descriptor.ServiceType == typeof(IJsonApiControllerGenerator); - })) + if (_services.Any(descriptor => descriptor.ServiceType == typeof(IJsonApiControllerGenerator))) { using (var provider = _services.BuildServiceProvider()) { diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 49ab589dfa..8a7d14eef5 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -46,18 +46,23 @@ public async Task Invoke(HttpContext httpContext, if (resourceContextProvider == null) throw new ArgumentNullException(nameof(resourceContextProvider)); // TODO: endpoint is null when route is not found. Instead of doing half work here, we should consider just throwing a 404 here already. - var primaryResourceContext = GetPrimaryResourceContext(httpContext.GetEndpoint(), controllerResourceMapping, resourceContextProvider); - if (primaryResourceContext != null) + var endpoint = httpContext.GetEndpoint(); + if (endpoint != null) { - if (!await ValidateContentTypeHeaderAsync(httpContext, options.SerializerSettings) || - !await ValidateAcceptHeaderAsync(httpContext, options.SerializerSettings)) + var primaryResourceContext = GetPrimaryResourceContext(endpoint, controllerResourceMapping, resourceContextProvider); + if (primaryResourceContext != null) { - return; + if (!await ValidateContentTypeHeaderAsync(httpContext, options.SerializerSettings) || + !await ValidateAcceptHeaderAsync(httpContext, options.SerializerSettings)) + { + return; + } + + var routeValues = httpContext.GetRouteData().Values; + SetupRequest((JsonApiRequest)request, primaryResourceContext, routeValues, options, resourceContextProvider, httpContext.Request); + + httpContext.RegisterJsonApiRequest(); } - - SetupRequest((JsonApiRequest)request, primaryResourceContext, httpContext.GetRouteData().Values, options, resourceContextProvider, httpContext.Request); - - httpContext.RegisterJsonApiRequest(); } await _next(httpContext); @@ -65,8 +70,8 @@ public async Task Invoke(HttpContext httpContext, private static ResourceContext GetPrimaryResourceContext(Endpoint endpoint, IControllerResourceMapping controllerResourceMapping, IResourceContextProvider resourceContextProvider) { - var controllerDescriptor = (ControllerActionDescriptor)endpoint?.Metadata.Single(meta => meta is ControllerActionDescriptor); - var controllerName = controllerDescriptor?.ControllerTypeInfo.FullName; + var controllerDescriptor = (ControllerActionDescriptor)endpoint.Metadata.Single(meta => meta is ControllerActionDescriptor); + var controllerName = controllerDescriptor.ControllerTypeInfo.FullName; if (controllerName != null) { diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index c074d54386..63c003490b 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -60,11 +60,11 @@ public void Apply(ApplicationModel application) { if (application == null) throw new ArgumentNullException(nameof(application)); - RegisterResourcesByEndpoint(application.Controllers); - RegisterRoutesForEndpoints(application.Controllers); + RegisterResources(application.Controllers); + RegisterRoutes(application.Controllers); } - private void RegisterRoutesForEndpoints(IEnumerable controllers) + private void RegisterRoutes(IEnumerable controllers) { foreach (var model in controllers) { @@ -90,7 +90,8 @@ private string GetRouteTemplateForEndpoint(ControllerModel controllerModel) if (template == null) { - throw new InvalidConfigurationException($"Failed to create a template for {controllerModel.ControllerType.FullName}. "); + throw new InvalidConfigurationException($"Failed to create a template for {controllerModel.ControllerType.FullName} " + + $"based on the controller and resource name"); } if (_endpointsByRoutes.ContainsKey(template)) @@ -104,21 +105,17 @@ private string GetRouteTemplateForEndpoint(ControllerModel controllerModel) return template; } - private void RegisterResourcesByEndpoint(IEnumerable controllers) + private void RegisterResources(IEnumerable controllers) { foreach (var model in controllers) { - var resourceType = ExtractResourceTypeForEndpoint(model.ControllerType); + var resourceType = ExtractResourceTypeFromEndpoint(model.ControllerType); if (resourceType != null) { var resourceContext = _resourceGraph.GetResourceContext(resourceType); if (resourceContext != null) { - if (resourceContext.ResourceType.Name.Contains("Address")) - { - - } - _resourcesByEndpoint.Add(model.ControllerType.FullName, resourceContext); + _resourcesByEndpoint.Add(model.ControllerType.FullName!, resourceContext); } } } @@ -162,7 +159,7 @@ private string TryGetControllerBasedTemplate(ControllerModel model) /// /// Determines the resource associated to a controller by inspecting generic arguments in its inheritance tree. /// - private Type ExtractResourceTypeForEndpoint(Type type) + private Type ExtractResourceTypeFromEndpoint(Type type) { var aspNetControllerType = typeof(ControllerBase); var coreControllerType = typeof(CoreJsonApiController); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs index fcbf03974b..9bd4bcc789 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/DebitCard.cs @@ -1,4 +1,3 @@ -using System; using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCoreExampleTests.IntegrationTests.IdObfuscation diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs index 2e991f214a..2261d7fac1 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/IdObfuscation/IdObfuscationStartup.cs @@ -1,5 +1,4 @@ using JsonApiDotNetCore.Configuration; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; From d05bc2650f0cc1da617ee421ba479d2f7420c123 Mon Sep 17 00:00:00 2001 From: maurei Date: Wed, 2 Dec 2020 22:01:20 +0000 Subject: [PATCH 7/7] fix: build --- .../Acceptance/ResourceHooks/ResourceHooksTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs index 2c55038e6d..8cced822f5 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceHooks/ResourceHooksTests.cs @@ -125,7 +125,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - var dbUser = dbContext.Users.Single(u => u.Id == user.Id); + var dbUser = await dbContext.Users.SingleAsync(u => u.Id == user.Id); Assert.Equal(user.Password, dbUser.Password); }); }