From 1a17ab07376b4e8634f2f46eb8737ea18f7e77eb Mon Sep 17 00:00:00 2001 From: maurei Date: Thu, 11 Mar 2021 17:01:32 +0100 Subject: [PATCH 1/9] formatters implement IApiRequestFormatMetadataProvider --- .../Middleware/IJsonApiInputFormatter.cs | 3 ++- .../Middleware/IJsonApiOutputFormatter.cs | 3 ++- .../Middleware/JsonApiInputFormatter.cs | 11 +++++++++++ .../Middleware/JsonApiOutputFormatter.cs | 11 +++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs index 268c0a2697..a3199271f0 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs @@ -1,4 +1,5 @@ using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; namespace JsonApiDotNetCore.Middleware @@ -7,7 +8,7 @@ namespace JsonApiDotNetCore.Middleware /// Application-wide entry point for reading JSON:API request bodies. /// [PublicAPI] - public interface IJsonApiInputFormatter : IInputFormatter + public interface IJsonApiInputFormatter : IInputFormatter, IApiRequestFormatMetadataProvider { } } diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs index 725accb03f..dea391cd1b 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs @@ -1,4 +1,5 @@ using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; namespace JsonApiDotNetCore.Middleware @@ -7,7 +8,7 @@ namespace JsonApiDotNetCore.Middleware /// Application-wide entry point for writing JSON:API response bodies. /// [PublicAPI] - public interface IJsonApiOutputFormatter : IOutputFormatter + public interface IJsonApiOutputFormatter : IOutputFormatter, IApiRequestFormatMetadataProvider { } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs index fc5a1e2230..2c936b1837 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs @@ -1,7 +1,10 @@ +using System; +using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; namespace JsonApiDotNetCore.Middleware { @@ -24,5 +27,13 @@ public async Task ReadAsync(InputFormatterContext context) var reader = context.HttpContext.RequestServices.GetRequiredService(); return await reader.ReadAsync(context); } + + public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) + { + return new MediaTypeCollection + { + new MediaTypeHeaderValue(HeaderConstants.MediaType) + }; + } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs index bd66f66067..b8aad15863 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs @@ -1,7 +1,10 @@ +using System; +using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; namespace JsonApiDotNetCore.Middleware { @@ -24,5 +27,13 @@ public async Task WriteAsync(OutputFormatterWriteContext context) var writer = context.HttpContext.RequestServices.GetRequiredService(); await writer.WriteAsync(context); } + + public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) + { + return new MediaTypeCollection + { + new MediaTypeHeaderValue(HeaderConstants.MediaType) + }; + } } } From cc34540ef506a92dc85dbf8ed19ecae237cf7ac0 Mon Sep 17 00:00:00 2001 From: maurei Date: Thu, 11 Mar 2021 21:36:03 +0100 Subject: [PATCH 2/9] add atomic operation content type to base json api formatter --- .../Middleware/IJsonApiInputFormatter.cs | 3 +- .../Middleware/IJsonApiOutputFormatter.cs | 3 +- .../Middleware/JsonApiFormatter.cs | 38 +++++++++++++++++++ .../Middleware/JsonApiInputFormatter.cs | 15 +------- .../Middleware/JsonApiOutputFormatter.cs | 15 +------- 5 files changed, 44 insertions(+), 30 deletions(-) create mode 100644 src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs index a3199271f0..268c0a2697 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiInputFormatter.cs @@ -1,5 +1,4 @@ using JetBrains.Annotations; -using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; namespace JsonApiDotNetCore.Middleware @@ -8,7 +7,7 @@ namespace JsonApiDotNetCore.Middleware /// Application-wide entry point for reading JSON:API request bodies. /// [PublicAPI] - public interface IJsonApiInputFormatter : IInputFormatter, IApiRequestFormatMetadataProvider + public interface IJsonApiInputFormatter : IInputFormatter { } } diff --git a/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs index dea391cd1b..725accb03f 100644 --- a/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/IJsonApiOutputFormatter.cs @@ -1,5 +1,4 @@ using JetBrains.Annotations; -using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; namespace JsonApiDotNetCore.Middleware @@ -8,7 +7,7 @@ namespace JsonApiDotNetCore.Middleware /// Application-wide entry point for writing JSON:API response bodies. /// [PublicAPI] - public interface IJsonApiOutputFormatter : IOutputFormatter, IApiRequestFormatMetadataProvider + public interface IJsonApiOutputFormatter : IOutputFormatter { } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs new file mode 100644 index 0000000000..f155303a88 --- /dev/null +++ b/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Resources; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Net.Http.Headers; + +namespace JsonApiDotNetCore.Middleware +{ + /// + /// Application-wide entry point for writing JSON:API response bodies. + /// + public abstract class JsonApiFormatter : IApiRequestFormatMetadataProvider + { + private readonly Type OperationContainerType = typeof(OperationContainer); + + /// + public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) + { + ArgumentGuard.NotNull(contentType, nameof(contentType)); + ArgumentGuard.NotNull(objectType, nameof(objectType)); + + string mediaType = IsAtomicOperationsType(objectType) ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType; + + return new MediaTypeCollection + { + new MediaTypeHeaderValue(mediaType) + }; + } + + private bool IsAtomicOperationsType(Type objectType) + { + return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == OperationContainerType; + } + } +} diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs index 2c936b1837..1f882ce344 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Net.Http.Headers; namespace JsonApiDotNetCore.Middleware { - /// - public sealed class JsonApiInputFormatter : IJsonApiInputFormatter + /// + public sealed class JsonApiInputFormatter : JsonApiFormatter, IJsonApiInputFormatter { /// public bool CanRead(InputFormatterContext context) @@ -27,13 +24,5 @@ public async Task ReadAsync(InputFormatterContext context) var reader = context.HttpContext.RequestServices.GetRequiredService(); return await reader.ReadAsync(context); } - - public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) - { - return new MediaTypeCollection - { - new MediaTypeHeaderValue(HeaderConstants.MediaType) - }; - } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs index b8aad15863..615231d421 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Net.Http.Headers; namespace JsonApiDotNetCore.Middleware { - /// - public sealed class JsonApiOutputFormatter : IJsonApiOutputFormatter + /// + public sealed class JsonApiOutputFormatter : JsonApiFormatter, IJsonApiOutputFormatter { /// public bool CanWriteResult(OutputFormatterCanWriteContext context) @@ -27,13 +24,5 @@ public async Task WriteAsync(OutputFormatterWriteContext context) var writer = context.HttpContext.RequestServices.GetRequiredService(); await writer.WriteAsync(context); } - - public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) - { - return new MediaTypeCollection - { - new MediaTypeHeaderValue(HeaderConstants.MediaType) - }; - } } } From ede65dce2abc54f37aa364be45f3be283dcdfc81 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 12 Mar 2021 04:11:20 +0100 Subject: [PATCH 3/9] fix: formatter rule --- src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs index f155303a88..9595a0f530 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs @@ -14,7 +14,7 @@ namespace JsonApiDotNetCore.Middleware /// public abstract class JsonApiFormatter : IApiRequestFormatMetadataProvider { - private readonly Type OperationContainerType = typeof(OperationContainer); + private readonly Type _operationContainerType = typeof(OperationContainer); /// public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) @@ -32,7 +32,7 @@ public IReadOnlyList GetSupportedContentTypes(string contentType, Type o private bool IsAtomicOperationsType(Type objectType) { - return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == OperationContainerType; + return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == _operationContainerType; } } } From 0b465975a1be6e2838385ac0aa4fc3e79c8f2fc7 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 12 Mar 2021 11:50:46 +0100 Subject: [PATCH 4/9] remove base JsonApiFormatter --- .../Middleware/JsonApiFormatter.cs | 38 ------------------- .../Middleware/JsonApiInputFormatter.cs | 32 +++++++++++++++- .../Middleware/JsonApiOutputFormatter.cs | 31 ++++++++++++++- 3 files changed, 59 insertions(+), 42 deletions(-) delete mode 100644 src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs deleted file mode 100644 index 9595a0f530..0000000000 --- a/src/JsonApiDotNetCore/Middleware/JsonApiFormatter.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Resources; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Net.Http.Headers; - -namespace JsonApiDotNetCore.Middleware -{ - /// - /// Application-wide entry point for writing JSON:API response bodies. - /// - public abstract class JsonApiFormatter : IApiRequestFormatMetadataProvider - { - private readonly Type _operationContainerType = typeof(OperationContainer); - - /// - public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) - { - ArgumentGuard.NotNull(contentType, nameof(contentType)); - ArgumentGuard.NotNull(objectType, nameof(objectType)); - - string mediaType = IsAtomicOperationsType(objectType) ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType; - - return new MediaTypeCollection - { - new MediaTypeHeaderValue(mediaType) - }; - } - - private bool IsAtomicOperationsType(Type objectType) - { - return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == _operationContainerType; - } - } -} diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs index 1f882ce344..8b54ffb413 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs @@ -1,13 +1,21 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; namespace JsonApiDotNetCore.Middleware { - /// - public sealed class JsonApiInputFormatter : JsonApiFormatter, IJsonApiInputFormatter + public sealed class JsonApiInputFormatter : IJsonApiInputFormatter, IApiRequestFormatMetadataProvider { + private static readonly Type OperationContainerType = typeof(OperationContainer); + /// public bool CanRead(InputFormatterContext context) { @@ -24,5 +32,25 @@ public async Task ReadAsync(InputFormatterContext context) var reader = context.HttpContext.RequestServices.GetRequiredService(); return await reader.ReadAsync(context); } + + + /// + public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) + { + ArgumentGuard.NotNull(contentType, nameof(contentType)); + ArgumentGuard.NotNull(objectType, nameof(objectType)); + + string mediaType = IsAtomicOperationsType(objectType) ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType; + + return new MediaTypeCollection + { + new MediaTypeHeaderValue(mediaType) + }; + } + + private bool IsAtomicOperationsType(Type objectType) + { + return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == OperationContainerType; + } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs index 615231d421..c237a5fab9 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs @@ -1,13 +1,21 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; namespace JsonApiDotNetCore.Middleware { - /// - public sealed class JsonApiOutputFormatter : JsonApiFormatter, IJsonApiOutputFormatter + public sealed class JsonApiOutputFormatter : IJsonApiOutputFormatter, IApiRequestFormatMetadataProvider { + private static readonly Type OperationContainerType = typeof(OperationContainer); + /// public bool CanWriteResult(OutputFormatterCanWriteContext context) { @@ -24,5 +32,24 @@ public async Task WriteAsync(OutputFormatterWriteContext context) var writer = context.HttpContext.RequestServices.GetRequiredService(); await writer.WriteAsync(context); } + + /// + public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) + { + ArgumentGuard.NotNull(contentType, nameof(contentType)); + ArgumentGuard.NotNull(objectType, nameof(objectType)); + + string mediaType = IsAtomicOperationsType(objectType) ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType; + + return new MediaTypeCollection + { + new MediaTypeHeaderValue(mediaType) + }; + } + + private bool IsAtomicOperationsType(Type objectType) + { + return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == OperationContainerType; + } } } From 29fb4ffe637f7bd0eb106334e8c19666f4506b1e Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 15 Mar 2021 12:00:22 +0100 Subject: [PATCH 5/9] Load only controllers that are required for every test --- src/JsonApiDotNetCore/ArgumentGuard.cs | 2 +- .../Middleware/JsonApiInputFormatter.cs | 20 ++------ .../Middleware/JsonApiOutputFormatter.cs | 19 ++----- .../Middleware/JsonApiRoutingConvention.cs | 19 +++---- .../Properties/AssemblyInfo.cs | 1 + .../ApiExplorerConvention.cs | 12 +++++ .../ApiExplorerStartup.cs | 23 +++++++++ .../ApiRequestFormatMedataProviderTests.cs | 49 +++++++++++++++++++ .../OperationsController.cs | 29 +++++++++++ .../ApiRequestFormatMedataProvider/Store.cs | 16 ++++++ .../StoreDbContext.cs | 16 ++++++ .../StoresController.cs | 25 ++++++++++ ...micConstrainedOperationsControllerTests.cs | 2 - .../Controllers/OperationsController.cs | 18 +++++++ .../Creating/AtomicCreateResourceTests.cs | 2 - ...reateResourceWithClientGeneratedIdTests.cs | 2 - ...eateResourceWithToManyRelationshipTests.cs | 2 - ...reateResourceWithToOneRelationshipTests.cs | 2 - .../Deleting/AtomicDeleteResourceTests.cs | 2 - .../Links/AtomicAbsoluteLinksTests.cs | 2 - .../AtomicRelativeLinksWithNamespaceTests.cs | 2 - .../LocalIds/AtomicLocalIdTests.cs | 2 - .../Meta/AtomicResourceMetaTests.cs | 2 - .../Meta/AtomicResponseMetaTests.cs | 2 - .../Mixed/AtomicRequestBodyTests.cs | 2 - .../Mixed/MaximumOperationsPerRequestTests.cs | 2 - .../AtomicModelStateValidationTests.cs | 2 - .../QueryStrings/AtomicQueryStringTests.cs | 2 - ...icSparseFieldSetResourceDefinitionTests.cs | 2 - .../Transactions/AtomicRollbackTests.cs | 2 - .../AtomicTransactionConsistencyTests.cs | 2 - .../AtomicAddToToManyRelationshipTests.cs | 2 - ...AtomicRemoveFromToManyRelationshipTests.cs | 2 - .../AtomicReplaceToManyRelationshipTests.cs | 2 - .../AtomicUpdateToOneRelationshipTests.cs | 2 - .../AtomicReplaceToManyRelationshipTests.cs | 2 - .../Resources/AtomicUpdateResourceTests.cs | 2 - .../AtomicUpdateToOneRelationshipTests.cs | 2 - .../ContentNegotiation/AcceptHeaderTests.cs | 2 - .../ContentTypeHeaderTests.cs | 2 - .../OperationsController.cs | 18 +++++++ .../ResourceHooks/ResourceHooksStartup.cs | 9 ++-- .../ServiceCollectionExtensions.cs | 23 +++++++-- .../Startups/TestableStartup.cs | 2 + .../TestControllerProvider.cs | 27 ++++++++++ 45 files changed, 278 insertions(+), 104 deletions(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerConvention.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerStartup.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoreDbContext.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/OperationsController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/OperationsController.cs create mode 100644 test/TestBuildingBlocks/TestControllerProvider.cs diff --git a/src/JsonApiDotNetCore/ArgumentGuard.cs b/src/JsonApiDotNetCore/ArgumentGuard.cs index c9f9e2d6a7..f27e260a6d 100644 --- a/src/JsonApiDotNetCore/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore/ArgumentGuard.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore { - internal static class ArgumentGuard + public static class ArgumentGuard { [AssertionMethod] [ContractAnnotation("value: null => halt")] diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs index 8b54ffb413..6f6d19b075 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs @@ -1,9 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; @@ -14,8 +11,6 @@ namespace JsonApiDotNetCore.Middleware { public sealed class JsonApiInputFormatter : IJsonApiInputFormatter, IApiRequestFormatMetadataProvider { - private static readonly Type OperationContainerType = typeof(OperationContainer); - /// public bool CanRead(InputFormatterContext context) { @@ -33,24 +28,19 @@ public async Task ReadAsync(InputFormatterContext context) return await reader.ReadAsync(context); } - /// public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) { - ArgumentGuard.NotNull(contentType, nameof(contentType)); ArgumentGuard.NotNull(objectType, nameof(objectType)); - string mediaType = IsAtomicOperationsType(objectType) ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType; + var mediaTypes = new MediaTypeCollection(); - return new MediaTypeCollection + if (contentType == HeaderConstants.MediaType || contentType == HeaderConstants.AtomicOperationsMediaType) { - new MediaTypeHeaderValue(mediaType) - }; - } + mediaTypes.Add(MediaTypeHeaderValue.Parse(contentType)); + } - private bool IsAtomicOperationsType(Type objectType) - { - return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == OperationContainerType; + return mediaTypes; } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs index c237a5fab9..e722e43048 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs @@ -1,9 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; @@ -14,8 +11,6 @@ namespace JsonApiDotNetCore.Middleware { public sealed class JsonApiOutputFormatter : IJsonApiOutputFormatter, IApiRequestFormatMetadataProvider { - private static readonly Type OperationContainerType = typeof(OperationContainer); - /// public bool CanWriteResult(OutputFormatterCanWriteContext context) { @@ -36,20 +31,16 @@ public async Task WriteAsync(OutputFormatterWriteContext context) /// public IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) { - ArgumentGuard.NotNull(contentType, nameof(contentType)); ArgumentGuard.NotNull(objectType, nameof(objectType)); - string mediaType = IsAtomicOperationsType(objectType) ? HeaderConstants.AtomicOperationsMediaType : HeaderConstants.MediaType; + var mediaTypes = new MediaTypeCollection(); - return new MediaTypeCollection + if (contentType == HeaderConstants.MediaType) { - new MediaTypeHeaderValue(mediaType) - }; - } + mediaTypes.Add(MediaTypeHeaderValue.Parse(contentType)); + } - private bool IsAtomicOperationsType(Type objectType) - { - return objectType.GetInterface(nameof(IEnumerable)) != null && objectType.GetGenericArguments().First() == OperationContainerType; + return mediaTypes; } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index 80e2aa5dce..6cccd22c8e 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -33,7 +33,7 @@ public class JsonApiRoutingConvention : IJsonApiRoutingConvention { private readonly IJsonApiOptions _options; private readonly IResourceContextProvider _resourceContextProvider; - private readonly HashSet _registeredTemplates = new HashSet(); + private readonly Dictionary _registeredTemplates = new Dictionary(); private readonly Dictionary _resourceContextPerControllerTypeMap = new Dictionary(); public JsonApiRoutingConvention(IJsonApiOptions options, IResourceContextProvider resourceContextProvider) @@ -89,9 +89,10 @@ public void Apply(ApplicationModel application) string template = TemplateFromResource(controller) ?? TemplateFromController(controller); - if (template == null) + if (!_registeredTemplates.TryAdd(template, controller)) { - throw new InvalidConfigurationException($"Controllers with overlapping route templates detected: {controller.ControllerType.FullName}"); + throw new InvalidConfigurationException( + $"Cannot register '{controller.ControllerType.FullName}' for template '{template}' because '{_registeredTemplates[template].ControllerType.FullName}' was already registered for this template."); } controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel @@ -116,10 +117,7 @@ private string TemplateFromResource(ControllerModel model) { string template = $"{_options.Namespace}/{resourceContext.PublicName}"; - if (_registeredTemplates.Add(template)) - { - return template; - } + return template; } return null; @@ -133,12 +131,7 @@ private string TemplateFromController(ControllerModel model) string controllerName = _options.SerializerNamingStrategy.GetPropertyName(model.ControllerName, false); string template = $"{_options.Namespace}/{controllerName}"; - if (_registeredTemplates.Add(template)) - { - return template; - } - - return null; + return template; } /// diff --git a/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs index 6bd2e5a870..2c359f920d 100644 --- a/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs +++ b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs @@ -4,3 +4,4 @@ [assembly: InternalsVisibleTo("JsonApiDotNetCoreExampleTests")] [assembly: InternalsVisibleTo("UnitTests")] [assembly: InternalsVisibleTo("DiscoveryTests")] +[assembly: InternalsVisibleTo("TestBuildingBlocks")] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerConvention.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerConvention.cs new file mode 100644 index 0000000000..405c8559ed --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerConvention.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + internal sealed class ApiExplorerConvention : IControllerModelConvention + { + public void Apply(ControllerModel controller) + { + controller.ApiExplorer.IsVisible = true; + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerStartup.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerStartup.cs new file mode 100644 index 0000000000..efd4137453 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiExplorerStartup.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCoreExampleTests.Startups; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + public sealed class ApiExplorerStartup : TestableStartup + where TDbContext : DbContext + { + public override void ConfigureServices(IServiceCollection services) + { + IMvcCoreBuilder builder = services.AddMvcCore().AddApiExplorer(); + builder.AddMvcOptions(options => options.Conventions.Add(new ApiExplorerConvention())); + + services.UseControllersFromNamespace(GetType().Namespace); + + services.AddJsonApi(SetJsonApiOptions, mvcBuilder: builder); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs new file mode 100644 index 0000000000..924e3d3b93 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using JsonApiDotNetCore.Middleware; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + public sealed class ApiRequestFormatMedataProviderTests : IClassFixture, StoreDbContext>> + { + private readonly ExampleIntegrationTestContext, StoreDbContext> _testContext; + + public ApiRequestFormatMedataProviderTests(ExampleIntegrationTestContext, StoreDbContext> testContext) + { + _testContext = testContext; + } + + // TODO: Clean up, this is a draft. + [Fact] + public async Task Input_formatters() + { + // Arrange + var provider = _testContext.Factory.Services.GetRequiredService(); + + // Act + IReadOnlyList groups = provider.ApiDescriptionGroups.Items; + + // Assert + List descriptions = groups.Single().Items.Where(description => description.SupportedRequestFormats.Count == 1).ToList(); + + ApiDescription operationsDescription = descriptions.First(descriptor => + ((ControllerActionDescriptor)descriptor.ActionDescriptor).ControllerTypeInfo == typeof(OperationsController)); + + operationsDescription.SupportedRequestFormats.Should().HaveCount(1); + + operationsDescription.SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.AtomicOperationsMediaType.Replace(";", "; ")); + + descriptions.Remove(operationsDescription); + + descriptions.Should().HaveCount(1); + descriptions[0].SupportedRequestFormats.Should().HaveCount(1); + descriptions[0].SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.MediaType); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs new file mode 100644 index 0000000000..7285a4989f --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using JsonApiDotNetCore.AtomicOperations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Resources; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + public sealed class OperationsController : JsonApiOperationsController + { + public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) + : base(options, loggerFactory, processor, request, targetedFields) + { + } + + [Consumes("application/vnd.api+json;ext=\"https://jsonapi.org/ext/atomic\"")] + [HttpPost] + public override Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) + { + return base.PostOperationsAsync(operations, cancellationToken); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs new file mode 100644 index 0000000000..c0a0476ddc --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + public sealed class Store : Identifiable + { + [Attr] + public string Name { get; set; } + + [Attr] + public string Address { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoreDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoreDbContext.cs new file mode 100644 index 0000000000..e1b3d49800 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoreDbContext.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + public sealed class StoreDbContext : DbContext + { + public DbSet Stores { get; set; } + + public StoreDbContext(DbContextOptions options) + : base(options) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs new file mode 100644 index 0000000000..a7f7edf511 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + public sealed class StoresController : JsonApiController + { + public StoresController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + + [Consumes("application/vnd.api+json")] + [HttpPost] + public override async Task PostAsync([FromBody] Store resource, CancellationToken cancellationToken) + { + return await base.PostAsync(resource, cancellationToken); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs index ba68a661b0..301d248362 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/AtomicConstrainedOperationsControllerTests.cs @@ -18,8 +18,6 @@ public sealed class AtomicConstrainedOperationsControllerTests public AtomicConstrainedOperationsControllerTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/OperationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/OperationsController.cs new file mode 100644 index 0000000000..8812dce74e --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Controllers/OperationsController.cs @@ -0,0 +1,18 @@ +using JsonApiDotNetCore.AtomicOperations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Resources; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.Controllers +{ + public sealed class OperationsController : JsonApiOperationsController + { + public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) + : base(options, loggerFactory, processor, request, targetedFields) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs index d40d65b836..96af09798b 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs @@ -22,8 +22,6 @@ public sealed class AtomicCreateResourceTests : IClassFixture, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs index 0bb4edb8e0..1d78d494ea 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs @@ -23,8 +23,6 @@ public AtomicCreateResourceWithClientGeneratedIdTests( { _testContext = testContext; - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.AllowClientGeneratedIds = true; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs index dd6a0e90c6..9c6a68e202 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs @@ -22,8 +22,6 @@ public AtomicCreateResourceWithToManyRelationshipTests( ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs index 84107d9b63..7ed2016030 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs @@ -24,8 +24,6 @@ public AtomicCreateResourceWithToOneRelationshipTests( ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs index ffb3342364..6fd6f313e4 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs @@ -21,8 +21,6 @@ public sealed class AtomicDeleteResourceTests : IClassFixture, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs index 15ebcab128..be50c61c05 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicAbsoluteLinksTests.cs @@ -24,8 +24,6 @@ public AtomicAbsoluteLinksTests(ExampleIntegrationTestContext { - services.AddControllersFromExampleProject(); - services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>)); }); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs index 3fea795b67..f2f2194ebd 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Links/AtomicRelativeLinksWithNamespaceTests.cs @@ -24,8 +24,6 @@ public AtomicRelativeLinksWithNamespaceTests( testContext.ConfigureServicesAfterStartup(services => { - services.AddControllersFromExampleProject(); - services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>)); }); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs index af3c5ab285..bf898c1991 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs @@ -20,8 +20,6 @@ public sealed class AtomicLocalIdTests : IClassFixture, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs index a0df52450c..0ebe607bba 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs @@ -24,8 +24,6 @@ public AtomicResourceMetaTests(ExampleIntegrationTestContext { - services.AddControllersFromExampleProject(); - services.AddScoped, MusicTrackMetaDefinition>(); services.AddScoped, TextLanguageMetaDefinition>(); }); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs index ae24a50a4a..bde6931f28 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResponseMetaTests.cs @@ -25,8 +25,6 @@ public AtomicResponseMetaTests(ExampleIntegrationTestContext { - services.AddControllersFromExampleProject(); - services.AddSingleton(); }); } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs index 7e9453fcd0..ca2616c973 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/AtomicRequestBodyTests.cs @@ -18,8 +18,6 @@ public sealed class AtomicRequestBodyTests : IClassFixture, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs index 84c9f239b7..0f3253b4b7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs @@ -20,8 +20,6 @@ public sealed class MaximumOperationsPerRequestTests public MaximumOperationsPerRequestTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs index 24d3f851dc..539732a088 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ModelStateValidation/AtomicModelStateValidationTests.cs @@ -19,8 +19,6 @@ public sealed class AtomicModelStateValidationTests public AtomicModelStateValidationTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs index 2e41befd2e..35d57e9908 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/QueryStrings/AtomicQueryStringTests.cs @@ -29,8 +29,6 @@ public AtomicQueryStringTests(ExampleIntegrationTestContext { - services.AddControllersFromExampleProject(); - services.AddSingleton(new FrozenSystemClock { UtcNow = FrozenTime diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs index de4c1ecaaf..7e440326e2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/AtomicSparseFieldSetResourceDefinitionTests.cs @@ -24,8 +24,6 @@ public AtomicSparseFieldSetResourceDefinitionTests(ExampleIntegrationTestContext testContext.ConfigureServicesAfterStartup(services => { - services.AddControllersFromExampleProject(); - services.AddSingleton(); services.AddScoped, LyricTextDefinition>(); services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>)); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs index e3ff40fb65..7f1b81ec20 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs @@ -20,8 +20,6 @@ public sealed class AtomicRollbackTests : IClassFixture, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs index 899ee2510d..8bdf86bdd2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs @@ -24,8 +24,6 @@ public AtomicTransactionConsistencyTests(ExampleIntegrationTestContext { - services.AddControllersFromExampleProject(); - services.AddResourceRepository(); services.AddResourceRepository(); services.AddResourceRepository(); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs index 87c407cd41..847190da26 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs @@ -22,8 +22,6 @@ public sealed class AtomicAddToToManyRelationshipTests public AtomicAddToToManyRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs index 4ba1b44634..bf42f9c949 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs @@ -22,8 +22,6 @@ public sealed class AtomicRemoveFromToManyRelationshipTests public AtomicRemoveFromToManyRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs index 9eca84249c..74ad1e556f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs @@ -22,8 +22,6 @@ public sealed class AtomicReplaceToManyRelationshipTests public AtomicReplaceToManyRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs index 54365fd4da..e8d76dd6a7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs @@ -21,8 +21,6 @@ public sealed class AtomicUpdateToOneRelationshipTests public AtomicUpdateToOneRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs index b356585dd2..cf5ddb0110 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs @@ -22,8 +22,6 @@ public sealed class AtomicReplaceToManyRelationshipTests public AtomicReplaceToManyRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs index b870ca48b3..1aefd8bda9 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs @@ -22,8 +22,6 @@ public sealed class AtomicUpdateResourceTests : IClassFixture, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs index 52f58e2506..c4fbc2d27f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs @@ -21,8 +21,6 @@ public sealed class AtomicUpdateToOneRelationshipTests public AtomicUpdateToOneRelationshipTests(ExampleIntegrationTestContext, OperationsDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs index 25ad63189e..f59423e368 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/AcceptHeaderTests.cs @@ -18,8 +18,6 @@ public sealed class AcceptHeaderTests : IClassFixture, PolicyDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs index 3aba63977a..bb231df4e4 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/ContentTypeHeaderTests.cs @@ -17,8 +17,6 @@ public sealed class ContentTypeHeaderTests : IClassFixture, PolicyDbContext> testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/OperationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/OperationsController.cs new file mode 100644 index 0000000000..dc89d1811d --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ContentNegotiation/OperationsController.cs @@ -0,0 +1,18 @@ +using JsonApiDotNetCore.AtomicOperations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Resources; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ContentNegotiation +{ + public sealed class OperationsController : JsonApiOperationsController + { + public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) + : base(options, loggerFactory, processor, request, targetedFields) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceHooks/ResourceHooksStartup.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceHooks/ResourceHooksStartup.cs index 57974b5ede..34415e7840 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceHooks/ResourceHooksStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceHooks/ResourceHooksStartup.cs @@ -1,6 +1,9 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCoreExample.Controllers; +using JsonApiDotNetCoreExample.Startups; using JsonApiDotNetCoreExampleTests.Startups; +using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -12,10 +15,10 @@ public sealed class ResourceHooksStartup : TestableStartup(SetJsonApiOptions); } protected override void SetJsonApiOptions(JsonApiOptions options) diff --git a/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs b/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs index b0ec80ebe3..20a0c50055 100644 --- a/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs +++ b/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs @@ -1,15 +1,30 @@ -using JsonApiDotNetCoreExample.Startups; +using System.Linq; +using JsonApiDotNetCore; using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; +using TestBuildingBlocks; namespace JsonApiDotNetCoreExampleTests { internal static class ServiceCollectionExtensions { - public static void AddControllersFromExampleProject(this IServiceCollection services) + public static void UseControllersFromNamespace(this IServiceCollection services, string @namespace, AssemblyPart assemblyWithNamespace = null) { - var part = new AssemblyPart(typeof(EmptyStartup).Assembly); - services.AddMvcCore().ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); + ArgumentGuard.NotNull(@namespace, nameof(@namespace)); + + services.AddMvcCore().ConfigureApplicationPartManager(manager => + { + if (assemblyWithNamespace != null) + { + manager.ApplicationParts.Add(assemblyWithNamespace); + } + + ControllerFeatureProvider provider = manager.FeatureProviders.OfType().First(); + manager.FeatureProviders.Remove(provider); + + manager.FeatureProviders.Add(new TestControllerProvider(@namespace)); + }); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/Startups/TestableStartup.cs b/test/JsonApiDotNetCoreExampleTests/Startups/TestableStartup.cs index a486046b4e..c6608032e8 100644 --- a/test/JsonApiDotNetCoreExampleTests/Startups/TestableStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Startups/TestableStartup.cs @@ -14,6 +14,8 @@ public class TestableStartup : EmptyStartup { public override void ConfigureServices(IServiceCollection services) { + services.UseControllersFromNamespace(typeof(TDbContext).Namespace); + services.AddJsonApi(SetJsonApiOptions); } diff --git a/test/TestBuildingBlocks/TestControllerProvider.cs b/test/TestBuildingBlocks/TestControllerProvider.cs new file mode 100644 index 0000000000..b6af2531fd --- /dev/null +++ b/test/TestBuildingBlocks/TestControllerProvider.cs @@ -0,0 +1,27 @@ +using System; +using System.Reflection; +using JsonApiDotNetCore; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace TestBuildingBlocks +{ + public sealed class TestControllerProvider : ControllerFeatureProvider + { + private readonly string _controllerNamespace; + + public TestControllerProvider(string controllerNamespace) + { + ArgumentGuard.NotNull(controllerNamespace, nameof(controllerNamespace)); + + _controllerNamespace = controllerNamespace; + } + + protected override bool IsController(TypeInfo typeInfo) + { + bool isController = base.IsController(typeInfo); + + bool controllerInAllowedNamespace = isController && typeInfo.Namespace!.StartsWith(_controllerNamespace, StringComparison.Ordinal); + return controllerInAllowedNamespace; + } + } +} From 37c9b85f0731b98c0c36d50b4f99dc4bc0f73368 Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 15 Mar 2021 12:16:27 +0100 Subject: [PATCH 6/9] Test for retrieving ConsumesAttribute value through ApiExplorer --- src/JsonApiDotNetCore/ArgumentGuard.cs | 2 +- .../ApiRequestFormatMedataProviderTests.cs | 26 ++++----- .../OperationsController.cs | 58 +++++++++---------- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/JsonApiDotNetCore/ArgumentGuard.cs b/src/JsonApiDotNetCore/ArgumentGuard.cs index f27e260a6d..c9f9e2d6a7 100644 --- a/src/JsonApiDotNetCore/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore/ArgumentGuard.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore { - public static class ArgumentGuard + internal static class ArgumentGuard { [AssertionMethod] [ContractAnnotation("value: null => halt")] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs index 924e3d3b93..47da79cf61 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Middleware; @@ -19,9 +20,8 @@ public ApiRequestFormatMedataProviderTests(ExampleIntegrationTestContext(); @@ -30,20 +30,14 @@ public async Task Input_formatters() IReadOnlyList groups = provider.ApiDescriptionGroups.Items; // Assert - List descriptions = groups.Single().Items.Where(description => description.SupportedRequestFormats.Count == 1).ToList(); - - ApiDescription operationsDescription = descriptions.First(descriptor => - ((ControllerActionDescriptor)descriptor.ActionDescriptor).ControllerTypeInfo == typeof(OperationsController)); - - operationsDescription.SupportedRequestFormats.Should().HaveCount(1); - - operationsDescription.SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.AtomicOperationsMediaType.Replace(";", "; ")); - - descriptions.Remove(operationsDescription); - - descriptions.Should().HaveCount(1); - descriptions[0].SupportedRequestFormats.Should().HaveCount(1); - descriptions[0].SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.MediaType); + IReadOnlyList descriptions = groups.Single().Items.ToList(); + MethodInfo postStoresMethod = typeof(StoresController).GetMethod(nameof(StoresController.PostAsync)); + ApiDescription postStoresDescription = descriptions.First(description => (description.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo == + postStoresMethod); + + postStoresDescription.Should().NotBeNull(); + postStoresDescription.SupportedRequestFormats.Should().HaveCount(1); + postStoresDescription.SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.MediaType); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs index 7285a4989f..7ca8686845 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs @@ -1,29 +1,29 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using JsonApiDotNetCore.AtomicOperations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Middleware; -using JsonApiDotNetCore.Resources; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider -{ - public sealed class OperationsController : JsonApiOperationsController - { - public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, - ITargetedFields targetedFields) - : base(options, loggerFactory, processor, request, targetedFields) - { - } - - [Consumes("application/vnd.api+json;ext=\"https://jsonapi.org/ext/atomic\"")] - [HttpPost] - public override Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) - { - return base.PostOperationsAsync(operations, cancellationToken); - } - } -} +// using System.Collections.Generic; +// using System.Threading; +// using System.Threading.Tasks; +// using JsonApiDotNetCore.AtomicOperations; +// using JsonApiDotNetCore.Configuration; +// using JsonApiDotNetCore.Controllers; +// using JsonApiDotNetCore.Middleware; +// using JsonApiDotNetCore.Resources; +// using Microsoft.AspNetCore.Mvc; +// using Microsoft.Extensions.Logging; +// +// namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +// { +// public sealed class OperationsController : JsonApiOperationsController +// { +// public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, +// ITargetedFields targetedFields) +// : base(options, loggerFactory, processor, request, targetedFields) +// { +// } +// +// [Consumes("application/vnd.api+json;ext=\"https://jsonapi.org/ext/atomic\"")] +// [HttpPost] +// public override Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) +// { +// return base.PostOperationsAsync(operations, cancellationToken); +// } +// } +// } From 1f50fd697a6e673903362a27fe415fb125b759e5 Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 15 Mar 2021 13:57:16 +0100 Subject: [PATCH 7/9] added test for response and request metadata providers, self review --- markdownlint.config | 5 - .../Middleware/JsonApiInputFormatter.cs | 29 +++- .../Middleware/JsonApiOutputFormatter.cs | 11 +- .../Properties/AssemblyInfo.cs | 1 - .../ApiRequestFormatMedataProviderTests.cs | 144 ++++++++++++++++-- .../OperationsController.cs | 58 +++---- .../ApiRequestFormatMedataProvider/Product.cs | 16 ++ .../ProductsController.cs | 15 ++ .../{StoreDbContext.cs => ShopDbContext.cs} | 6 +- .../ApiRequestFormatMedataProvider/Store.cs | 4 + .../StoresController.cs | 27 +++- .../PaginationWithoutTotalCountTests.cs | 1 - .../TestControllerProvider.cs | 2 +- .../TestBuildingBlocks.csproj | 2 +- 14 files changed, 263 insertions(+), 58 deletions(-) delete mode 100644 markdownlint.config create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Product.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ProductsController.cs rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/{StoreDbContext.cs => ShopDbContext.cs} (64%) rename test/{TestBuildingBlocks => JsonApiDotNetCoreExampleTests}/TestControllerProvider.cs (95%) diff --git a/markdownlint.config b/markdownlint.config deleted file mode 100644 index 8f376b5c2a..0000000000 --- a/markdownlint.config +++ /dev/null @@ -1,5 +0,0 @@ -{ - "MD033": { - "allowed_elements": [ "p", "img", "p" ] - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs index 6f6d19b075..e581cbd179 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiInputFormatter.cs @@ -1,6 +1,8 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; @@ -35,12 +37,35 @@ public IReadOnlyList GetSupportedContentTypes(string contentType, Type o var mediaTypes = new MediaTypeCollection(); - if (contentType == HeaderConstants.MediaType || contentType == HeaderConstants.AtomicOperationsMediaType) + switch (contentType) { - mediaTypes.Add(MediaTypeHeaderValue.Parse(contentType)); + case HeaderConstants.AtomicOperationsMediaType when IsOperationsType(objectType): + { + mediaTypes.Add(MediaTypeHeaderValue.Parse(HeaderConstants.AtomicOperationsMediaType)); + break; + } + case HeaderConstants.MediaType when IsJsonApiResource(objectType): + { + mediaTypes.Add(MediaTypeHeaderValue.Parse(HeaderConstants.MediaType)); + break; + } } return mediaTypes; } + + private bool IsJsonApiResource(Type type) + { + Type typeToCheck = typeof(IEnumerable).IsAssignableFrom(type) ? type.GetGenericArguments()[0] : type; + + return typeToCheck.IsOrImplementsInterface(typeof(IIdentifiable)) || typeToCheck == typeof(object); + } + + private bool IsOperationsType(Type type) + { + Type typeToCheck = typeof(IEnumerable).IsAssignableFrom(type) ? type.GetGenericArguments()[0] : type; + + return typeToCheck == typeof(OperationContainer); + } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs index e722e43048..789c0644eb 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiOutputFormatter.cs @@ -1,6 +1,8 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; @@ -9,7 +11,7 @@ namespace JsonApiDotNetCore.Middleware { - public sealed class JsonApiOutputFormatter : IJsonApiOutputFormatter, IApiRequestFormatMetadataProvider + public sealed class JsonApiOutputFormatter : IJsonApiOutputFormatter, IApiResponseTypeMetadataProvider { /// public bool CanWriteResult(OutputFormatterCanWriteContext context) @@ -37,7 +39,12 @@ public IReadOnlyList GetSupportedContentTypes(string contentType, Type o if (contentType == HeaderConstants.MediaType) { - mediaTypes.Add(MediaTypeHeaderValue.Parse(contentType)); + Type typeToCheck = typeof(IEnumerable).IsAssignableFrom(objectType) ? objectType.GetGenericArguments()[0] : objectType; + + if (typeToCheck.IsOrImplementsInterface(typeof(IIdentifiable)) || typeToCheck == typeof(object)) + { + mediaTypes.Add(MediaTypeHeaderValue.Parse(contentType)); + } } return mediaTypes; diff --git a/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs index 2c359f920d..6bd2e5a870 100644 --- a/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs +++ b/src/JsonApiDotNetCore/Properties/AssemblyInfo.cs @@ -4,4 +4,3 @@ [assembly: InternalsVisibleTo("JsonApiDotNetCoreExampleTests")] [assembly: InternalsVisibleTo("UnitTests")] [assembly: InternalsVisibleTo("DiscoveryTests")] -[assembly: InternalsVisibleTo("TestBuildingBlocks")] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs index 47da79cf61..5588f3979f 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Common; using JsonApiDotNetCore.Middleware; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Controllers; @@ -11,17 +11,17 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider { - public sealed class ApiRequestFormatMedataProviderTests : IClassFixture, StoreDbContext>> + public sealed class ApiRequestFormatMedataProviderTests : IClassFixture, ShopDbContext>> { - private readonly ExampleIntegrationTestContext, StoreDbContext> _testContext; + private readonly ExampleIntegrationTestContext, ShopDbContext> _testContext; - public ApiRequestFormatMedataProviderTests(ExampleIntegrationTestContext, StoreDbContext> testContext) + public ApiRequestFormatMedataProviderTests(ExampleIntegrationTestContext, ShopDbContext> testContext) { _testContext = testContext; } [Fact] - public void Can_retrieve_content_type_set_with_ConsumesAttribute_value_in_ApiExplorer() + public void Can_retrieve_request_content_type_in_ApiExplorer_when_using_ConsumesAttribute() { // Arrange var provider = _testContext.Factory.Services.GetRequiredService(); @@ -30,14 +30,132 @@ public void Can_retrieve_content_type_set_with_ConsumesAttribute_value_in_ApiExp IReadOnlyList groups = provider.ApiDescriptionGroups.Items; // Assert - IReadOnlyList descriptions = groups.Single().Items.ToList(); - MethodInfo postStoresMethod = typeof(StoresController).GetMethod(nameof(StoresController.PostAsync)); - ApiDescription postStoresDescription = descriptions.First(description => (description.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo == - postStoresMethod); - - postStoresDescription.Should().NotBeNull(); - postStoresDescription.SupportedRequestFormats.Should().HaveCount(1); - postStoresDescription.SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.MediaType); + List descriptions = groups.Single().Items.ToList(); + MethodInfo postStore = typeof(StoresController).GetMethod(nameof(StoresController.PostAsync)); + + ApiDescription postStoreDescription = descriptions.First(description => (description.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo == + postStore); + + postStoreDescription.Should().NotBeNull(); + postStoreDescription.SupportedRequestFormats.Should().HaveCount(1); + postStoreDescription.SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.MediaType); + } + + [Fact] + public void Can_retrieve_atomic_operations_request_content_type_in_ApiExplorer_when_using_ConsumesAttribute() + { + // Arrange + var provider = _testContext.Factory.Services.GetRequiredService(); + + // Act + IReadOnlyList groups = provider.ApiDescriptionGroups.Items; + + // Assert + List descriptions = groups.Single().Items.ToList(); + MethodInfo postOperations = typeof(OperationsController).GetMethod(nameof(OperationsController.PostOperationsAsync)); + + ApiDescription postOperationsDescription = + descriptions.First(description => (description.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo == postOperations); + + postOperationsDescription.Should().NotBeNull(); + postOperationsDescription.SupportedRequestFormats.Should().HaveCount(1); + postOperationsDescription.SupportedRequestFormats[0].MediaType.Should().Be(HeaderConstants.AtomicOperationsMediaType); + } + + [Fact] + public void Cannot_retrieve_request_content_type_in_ApiExplorer_without_usage_of_ConsumesAttribute() + { + // Arrange + var provider = _testContext.Factory.Services.GetRequiredService(); + + // Act + IReadOnlyList groups = provider.ApiDescriptionGroups.Items; + + // Assert + IReadOnlyList descriptions = groups.Single().Items; + + IEnumerable productActionDescriptions = descriptions.Where(description => + (description.ActionDescriptor as ControllerActionDescriptor)?.ControllerTypeInfo == typeof(ProductsController)); + + foreach (ApiDescription description in productActionDescriptions) + { + description.SupportedRequestFormats.Should().NotContain(format => format.MediaType == HeaderConstants.MediaType); + } + } + + [Fact] + public void Cannot_retrieve_atomic_operations_request_content_type_in_ApiExplorer_when_set_on_regular_endpoint() + { + // Arrange + var provider = _testContext.Factory.Services.GetRequiredService(); + + // Act + IReadOnlyList groups = provider.ApiDescriptionGroups.Items; + + // Assert + List descriptions = groups.Single().Items.ToList(); + MethodInfo postRelationship = typeof(StoresController).GetMethod(nameof(StoresController.PostRelationshipAsync)); + + ApiDescription postRelationshipDescription = descriptions.First(description => + (description.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo == postRelationship); + + postRelationshipDescription.Should().NotBeNull(); + postRelationshipDescription.SupportedRequestFormats.Should().HaveCount(0); + } + + [Fact] + public void Can_retrieve_response_content_type_in_ApiExplorer_when_using_ProducesAttribute_with_ProducesResponseTypeAttribute() + { + // Arrange + var provider = _testContext.Factory.Services.GetRequiredService(); + + // Act + IReadOnlyList groups = provider.ApiDescriptionGroups.Items; + + // Assert + List descriptions = groups.Single().Items.ToList(); + + MethodInfo getStores = typeof(StoresController).GetMethods() + .First(method => method.Name == nameof(StoresController.GetAsync) && method.GetParameters().Length == 1); + + ApiDescription getStoresDescription = descriptions.First(description => (description.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo == + getStores); + + getStoresDescription.Should().NotBeNull(); + getStoresDescription.SupportedResponseTypes.Should().HaveCount(1); + + ApiResponseFormat jsonApiResponse = getStoresDescription.SupportedResponseTypes[0].ApiResponseFormats + .FirstOrDefault(format => format.Formatter.GetType().Implements(typeof(IJsonApiOutputFormatter))); + + jsonApiResponse.Should().NotBeNull(); + jsonApiResponse!.MediaType.Should().Be(HeaderConstants.MediaType); + } + + [Fact] + public void Cannot_retrieve_response_content_type_in_ApiExplorer_when_using_ProducesResponseTypeAttribute_without_ProducesAttribute() + { + // Arrange + var provider = _testContext.Factory.Services.GetRequiredService(); + + // Act + IReadOnlyList groups = provider.ApiDescriptionGroups.Items; + + // Assert + List descriptions = groups.Single().Items.ToList(); + + MethodInfo getStores = typeof(StoresController).GetMethods() + .First(method => method.Name == nameof(StoresController.GetAsync) && method.GetParameters().Length == 2); + + ApiDescription getStoresDescription = descriptions.First(description => (description.ActionDescriptor as ControllerActionDescriptor)?.MethodInfo == + getStores); + + getStoresDescription.Should().NotBeNull(); + getStoresDescription.SupportedResponseTypes.Should().HaveCount(1); + + ApiResponseFormat jsonApiResponse = getStoresDescription.SupportedResponseTypes[0].ApiResponseFormats + .FirstOrDefault(format => format.Formatter.GetType().Implements(typeof(IJsonApiOutputFormatter))); + + jsonApiResponse.Should().BeNull(); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs index 7ca8686845..4053064fb2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/OperationsController.cs @@ -1,29 +1,29 @@ -// using System.Collections.Generic; -// using System.Threading; -// using System.Threading.Tasks; -// using JsonApiDotNetCore.AtomicOperations; -// using JsonApiDotNetCore.Configuration; -// using JsonApiDotNetCore.Controllers; -// using JsonApiDotNetCore.Middleware; -// using JsonApiDotNetCore.Resources; -// using Microsoft.AspNetCore.Mvc; -// using Microsoft.Extensions.Logging; -// -// namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider -// { -// public sealed class OperationsController : JsonApiOperationsController -// { -// public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, -// ITargetedFields targetedFields) -// : base(options, loggerFactory, processor, request, targetedFields) -// { -// } -// -// [Consumes("application/vnd.api+json;ext=\"https://jsonapi.org/ext/atomic\"")] -// [HttpPost] -// public override Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) -// { -// return base.PostOperationsAsync(operations, cancellationToken); -// } -// } -// } +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using JsonApiDotNetCore.AtomicOperations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Resources; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + public sealed class OperationsController : JsonApiOperationsController + { + public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) + : base(options, loggerFactory, processor, request, targetedFields) + { + } + + [HttpPost] + [Consumes("application/vnd.api+json; ext=\"https://jsonapi.org/ext/atomic\"")] + public override Task PostOperationsAsync(IList operations, CancellationToken cancellationToken) + { + return base.PostOperationsAsync(operations, cancellationToken); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Product.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Product.cs new file mode 100644 index 0000000000..0845f61403 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Product.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + public sealed class Product : Identifiable + { + [Attr] + public string Name { get; set; } + + [Attr] + public decimal Price { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ProductsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ProductsController.cs new file mode 100644 index 0000000000..b5745d3544 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ProductsController.cs @@ -0,0 +1,15 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider +{ + public sealed class ProductsController : JsonApiController + { + public ProductsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoreDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ShopDbContext.cs similarity index 64% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoreDbContext.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ShopDbContext.cs index e1b3d49800..004712222d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoreDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ShopDbContext.cs @@ -4,11 +4,13 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ApiRequestFormatMedataProvider { [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class StoreDbContext : DbContext + public sealed class ShopDbContext : DbContext { public DbSet Stores { get; set; } - public StoreDbContext(DbContextOptions options) + public DbSet Products { get; set; } + + public ShopDbContext(DbContextOptions options) : base(options) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs index c0a0476ddc..e17283a920 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/Store.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; @@ -12,5 +13,8 @@ public sealed class Store : Identifiable [Attr] public string Address { get; set; } + + [HasMany] + public ICollection Products { get; set; } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs index a7f7edf511..708311378d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/StoresController.cs @@ -1,7 +1,9 @@ +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -15,11 +17,34 @@ public StoresController(IJsonApiOptions options, ILoggerFactory loggerFactory, I { } - [Consumes("application/vnd.api+json")] + [HttpGet] + [Produces("application/vnd.api+json")] + [ProducesResponseType(typeof(IEnumerable), 200)] + public override Task GetAsync(CancellationToken cancellationToken) + { + return base.GetAsync(cancellationToken); + } + + [HttpGet] + [ProducesResponseType(typeof(Store), 200)] + public override Task GetAsync(int id, CancellationToken cancellationToken) + { + return base.GetAsync(id, cancellationToken); + } + [HttpPost] + [Consumes("application/vnd.api+json")] public override async Task PostAsync([FromBody] Store resource, CancellationToken cancellationToken) { return await base.PostAsync(resource, cancellationToken); } + + [HttpPost] + [Consumes("application/vnd.api+json; ext=\"https://jsonapi.org/ext/atomic\"")] + public override Task PostRelationshipAsync(int id, string relationshipName, ISet secondaryResourceIds, + CancellationToken cancellationToken) + { + return base.PostRelationshipAsync(id, relationshipName, secondaryResourceIds, cancellationToken); + } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Pagination/PaginationWithoutTotalCountTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Pagination/PaginationWithoutTotalCountTests.cs index d333c18f19..f45e44daf8 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Pagination/PaginationWithoutTotalCountTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/QueryStrings/Pagination/PaginationWithoutTotalCountTests.cs @@ -26,7 +26,6 @@ public PaginationWithoutTotalCountTests(ExampleIntegrationTestContext(); - options.IncludeTotalResourceCount = false; options.DefaultPageSize = new PageSize(DefaultPageSize); options.AllowUnknownQueryStringParameters = true; diff --git a/test/TestBuildingBlocks/TestControllerProvider.cs b/test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs similarity index 95% rename from test/TestBuildingBlocks/TestControllerProvider.cs rename to test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs index b6af2531fd..e172287da1 100644 --- a/test/TestBuildingBlocks/TestControllerProvider.cs +++ b/test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs @@ -3,7 +3,7 @@ using JsonApiDotNetCore; using Microsoft.AspNetCore.Mvc.Controllers; -namespace TestBuildingBlocks +namespace JsonApiDotNetCoreExampleTests { public sealed class TestControllerProvider : ControllerFeatureProvider { diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 6f81675106..5b21e0056e 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -8,7 +8,7 @@ - + From 72e83db35b3fe882689c7bffae075a408dadac3a Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 15 Mar 2021 14:07:03 +0100 Subject: [PATCH 8/9] code cleanup --- .../ServiceCollectionExtensions.cs | 1 - test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs b/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs index 20a0c50055..3a62eabe57 100644 --- a/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs +++ b/test/JsonApiDotNetCoreExampleTests/ServiceCollectionExtensions.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; -using TestBuildingBlocks; namespace JsonApiDotNetCoreExampleTests { diff --git a/test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs b/test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs index e172287da1..0119f4d807 100644 --- a/test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs +++ b/test/JsonApiDotNetCoreExampleTests/TestControllerProvider.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCoreExampleTests { - public sealed class TestControllerProvider : ControllerFeatureProvider + internal sealed class TestControllerProvider : ControllerFeatureProvider { private readonly string _controllerNamespace; From 8387528a596b1aaf17db199af59814a26c82674e Mon Sep 17 00:00:00 2001 From: maurei Date: Tue, 16 Mar 2021 09:27:29 +0100 Subject: [PATCH 9/9] apply review feedback --- src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs | 4 +++- .../ApiRequestFormatMedataProviderTests.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs index 6cccd22c8e..d5ec6c4ca9 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiRoutingConvention.cs @@ -89,12 +89,14 @@ public void Apply(ApplicationModel application) string template = TemplateFromResource(controller) ?? TemplateFromController(controller); - if (!_registeredTemplates.TryAdd(template, controller)) + if (_registeredTemplates.ContainsKey(template)) { throw new InvalidConfigurationException( $"Cannot register '{controller.ControllerType.FullName}' for template '{template}' because '{_registeredTemplates[template].ControllerType.FullName}' was already registered for this template."); } + _registeredTemplates.Add(template, controller); + controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel { Template = template diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs index 5588f3979f..1745d51c81 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ApiRequestFormatMedataProvider/ApiRequestFormatMedataProviderTests.cs @@ -84,7 +84,7 @@ public void Cannot_retrieve_request_content_type_in_ApiExplorer_without_usage_of } [Fact] - public void Cannot_retrieve_atomic_operations_request_content_type_in_ApiExplorer_when_set_on_regular_endpoint() + public void Cannot_retrieve_atomic_operations_request_content_type_in_ApiExplorer_when_set_on_relationship_endpoint() { // Arrange var provider = _testContext.Factory.Services.GetRequiredService();