From eda041b6cdb112cc034cdf625af5b098afead80b Mon Sep 17 00:00:00 2001 From: vinhdev17 Date: Sat, 29 Nov 2025 11:22:40 +0700 Subject: [PATCH 1/3] fix: enhance module type visibility checks in CarterExtensions --- src/Carter/CarterExtensions.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Carter/CarterExtensions.cs b/src/Carter/CarterExtensions.cs index 476da22..5df7cdc 100644 --- a/src/Carter/CarterExtensions.cs +++ b/src/Carter/CarterExtensions.cs @@ -254,7 +254,12 @@ private static IEnumerable GetNewModules(CarterConfigurator carterConfigur !t.IsAbstract && typeof(ICarterModule).IsAssignableFrom(t) && t != typeof(ICarterModule) && - (t.IsPublic || t.IsNestedPublic) + ( + t.IsPublic || // public top-level class + (t.IsNotPublic && !t.IsNested) || // internal top-level class + t.IsNestedPublic || // public nested class + t.IsNestedAssembly // internal nested class + ) )); carterConfigurator.ModuleTypes.AddRange(modules); From e2e6041b1c3f9be6a44a28481052b9890fb0733b Mon Sep 17 00:00:00 2001 From: vinhdev17 Date: Sat, 29 Nov 2025 21:27:33 +0700 Subject: [PATCH 2/3] fix: add internal modules and validators with corresponding tests --- test/Carter.Tests/CarterExtensionTests.cs | 182 +++++++++++++++++- .../InternalResponseNegotiator.cs | 19 ++ .../InternalRooms/InternalRoomModel.cs | 6 + .../InternalRoomModelValidator.cs | 11 ++ .../InternalRooms/InternalRoomModule.cs | 17 ++ .../NestedInternalResponseNegotiator.cs | 23 +++ .../NestedInternalRoomModelValidator.cs | 15 ++ .../InternalRooms/NestedInternalRoomModule.cs | 22 +++ 8 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs create mode 100644 test/Carter.Tests/InternalRooms/InternalRoomModel.cs create mode 100644 test/Carter.Tests/InternalRooms/InternalRoomModelValidator.cs create mode 100644 test/Carter.Tests/InternalRooms/InternalRoomModule.cs create mode 100644 test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs create mode 100644 test/Carter.Tests/InternalRooms/NestedInternalRoomModelValidator.cs create mode 100644 test/Carter.Tests/InternalRooms/NestedInternalRoomModule.cs diff --git a/test/Carter.Tests/CarterExtensionTests.cs b/test/Carter.Tests/CarterExtensionTests.cs index 77e8b22..a36e9ee 100644 --- a/test/Carter.Tests/CarterExtensionTests.cs +++ b/test/Carter.Tests/CarterExtensionTests.cs @@ -2,6 +2,7 @@ namespace Carter.Tests { using System.Linq; using Carter.Tests.ContentNegotiation; + using Carter.Tests.InternalRooms; using Carter.Tests.ModelBinding; using Carter.Tests.StreamTests; using FluentValidation; @@ -123,7 +124,7 @@ public void Should_register_responsenegotiators_passed_in_by_configurator_and_de } [Fact] - public void Should_register_multiple_responsenegotiators_passed_in_by_configurator_and_default_json_negotiator() + public void Should_register_multiple_response_negotiators_passed_in_by_configurator_and_default_json_negotiator() { //Given var serviceCollection = new ServiceCollection(); @@ -132,8 +133,8 @@ public void Should_register_multiple_responsenegotiators_passed_in_by_configurat serviceCollection.AddCarter(configurator: configurator => configurator.WithResponseNegotiators(typeof(TestResponseNegotiator), typeof(TestXmlResponseNegotiator))); //Then - var responsenegotiators = serviceCollection.Where(x => x.ServiceType == typeof(IResponseNegotiator)); - Assert.Equal(3,responsenegotiators.Count()); + var responseNegotiators = serviceCollection.Where(x => x.ServiceType == typeof(IResponseNegotiator)); + Assert.Equal(3, responseNegotiators.Count()); } [Fact] @@ -177,5 +178,180 @@ public void Should_register_no_response_negotiators_passed_in_by_configurator() var responseNegotiators = serviceCollection.Where(x => x.ServiceType == typeof(IResponseNegotiator)); Assert.Single(responseNegotiators); } + + [Fact] + public void Should_register_internal_modules_when_assembly_scanned() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(); + + //Then + var modules = serviceCollection.Where(x => x.ServiceType == typeof(ICarterModule)); + var internalModule = modules.FirstOrDefault(x => x.ImplementationType == typeof(InternalRoomModule)); + Assert.NotNull(internalModule); + } + + [Fact] + public void Should_register_internal_module_passed_in_by_configurator() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => configurator.WithModule()); + + //Then + var modules = serviceCollection.Where(x => x.ServiceType == typeof(ICarterModule)); + var internalModule = modules.FirstOrDefault(x => x.ImplementationType == typeof(InternalRoomModule)); + Assert.NotNull(internalModule); + } + + [Fact] + public void Should_register_multiple_internal_modules_passed_in_by_configurator() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => + configurator.WithModules(typeof(InternalRoomModule), typeof(NestedInternalRoomModuleWrapper.NestedInternalRoomModule))); + + //Then + var modules = serviceCollection.Where(x => x.ServiceType == typeof(ICarterModule)).ToList(); + Assert.Contains(modules, m => m.ImplementationType == typeof(InternalRoomModule)); + Assert.Contains(modules, m => m.ImplementationType == typeof(NestedInternalRoomModuleWrapper.NestedInternalRoomModule)); + Assert.Equal(2, modules.Count()); + } + + [Fact] + public void Should_register_internal_validators_when_assembly_scanned() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(); + + //Then + var validators = serviceCollection.Where(x => x.ServiceType == typeof(IValidator)); + var internalValidator = validators.FirstOrDefault(x => x.ImplementationType == typeof(InternalRoomModelValidator)); + Assert.NotNull(internalValidator); + } + + [Fact] + public void Should_register_internal_validator_passed_in_by_configurator() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => configurator.WithValidator()); + + //Then + var validators = serviceCollection.Where(x => x.ServiceType == typeof(IValidator)).ToList(); + var internalValidator = validators.FirstOrDefault(x => x.ImplementationType == typeof(InternalRoomModelValidator)); + Assert.NotNull(internalValidator); + Assert.Single(validators); + } + + [Fact] + public void Should_register_multiple_internal_validators_passed_in_by_configurator() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => + configurator.WithValidators(typeof(InternalRoomModelValidator), typeof(NestedInternalTestModelValidatorWrapper.InternalTestModelValidator))); + + //Then + var validators = serviceCollection.Where(x => x.ServiceType == typeof(IValidator)).ToList(); + Assert.Contains(validators, v => v.ImplementationType == typeof(InternalRoomModelValidator)); + Assert.Contains(validators, v => v.ImplementationType == typeof(NestedInternalTestModelValidatorWrapper.InternalTestModelValidator)); + Assert.Equal(2, validators.Count()); + } + + [Fact] + public void Should_register_internal_response_negotiators_when_assembly_scanned() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(); + + //Then + var responseNegotiators = serviceCollection.Where(x => x.ServiceType == typeof(IResponseNegotiator)); + var internalNegotiator = responseNegotiators.FirstOrDefault(x => x.ImplementationType == typeof(InternalResponseNegotiator)); + Assert.NotNull(internalNegotiator); + } + + [Fact] + public void Should_register_internal_response_negotiator_passed_in_by_configurator() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => configurator.WithResponseNegotiator()); + + //Then + var responseNegotiators = serviceCollection.Where(x => x.ServiceType == typeof(IResponseNegotiator)); + var internalNegotiator = responseNegotiators.FirstOrDefault(x => x.ImplementationType == typeof(InternalResponseNegotiator)); + Assert.NotNull(internalNegotiator); + } + + [Fact] + public void Should_register_multiple_internal_response_negotiators_passed_in_by_configurator() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => + configurator.WithResponseNegotiators(typeof(InternalResponseNegotiator), typeof(NestedInternalResponseNegotiatorWrapper.NestedInternalResponseNegotiator))); + + //Then + var responseNegotiators = serviceCollection.Where(x => x.ServiceType == typeof(IResponseNegotiator)).ToList(); + Assert.Contains(responseNegotiators, r => r.ImplementationType == typeof(InternalResponseNegotiator)); + Assert.Contains(responseNegotiators, r => r.ImplementationType == typeof(NestedInternalResponseNegotiatorWrapper.NestedInternalResponseNegotiator)); + } + + [Fact] + public void Should_register_mix_of_public_and_internal_modules() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => + configurator.WithModules(typeof(TestModule), typeof(InternalRoomModule))); + + //Then + var modules = serviceCollection.Where(x => x.ServiceType == typeof(ICarterModule)).ToList(); + Assert.Contains(modules, m => m.ImplementationType == typeof(TestModule)); + Assert.Contains(modules, m => m.ImplementationType == typeof(InternalRoomModule)); + Assert.Equal(2, modules.Count()); + } + + [Fact] + public void Should_register_mix_of_public_and_internal_validators() + { + //Given + var serviceCollection = new ServiceCollection(); + + //When + serviceCollection.AddCarter(configurator: configurator => + configurator.WithValidators(typeof(TestModelValidator), typeof(InternalRoomModelValidator))); + + //Then + var validators = serviceCollection.Where(x => x.ServiceType == typeof(IValidator)).ToList(); + Assert.Contains(validators, v => v.ImplementationType == typeof(TestModelValidator)); + Assert.Contains(validators, v => v.ImplementationType == typeof(InternalRoomModelValidator)); + Assert.Equal(2, validators.Count()); + } } } diff --git a/test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs b/test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs new file mode 100644 index 0000000..f67de0d --- /dev/null +++ b/test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs @@ -0,0 +1,19 @@ +namespace Carter.Tests.InternalRooms; + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +internal class InternalResponseNegotiator: IResponseNegotiator +{ + public bool CanHandle(MediaTypeHeaderValue accept) + { + return true; + } + + public Task Handle(HttpRequest req, HttpResponse res, T model, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/test/Carter.Tests/InternalRooms/InternalRoomModel.cs b/test/Carter.Tests/InternalRooms/InternalRoomModel.cs new file mode 100644 index 0000000..7a6931f --- /dev/null +++ b/test/Carter.Tests/InternalRooms/InternalRoomModel.cs @@ -0,0 +1,6 @@ +namespace Carter.Tests.InternalRooms; + +internal sealed class InternalRoomModel +{ + public string Name { get; set; } +} \ No newline at end of file diff --git a/test/Carter.Tests/InternalRooms/InternalRoomModelValidator.cs b/test/Carter.Tests/InternalRooms/InternalRoomModelValidator.cs new file mode 100644 index 0000000..8259d2f --- /dev/null +++ b/test/Carter.Tests/InternalRooms/InternalRoomModelValidator.cs @@ -0,0 +1,11 @@ +namespace Carter.Tests.InternalRooms; + +using FluentValidation; + +internal class InternalRoomModelValidator : AbstractValidator +{ + public InternalRoomModelValidator() + { + this.RuleFor(x => x.Name).NotEmpty(); + } +} diff --git a/test/Carter.Tests/InternalRooms/InternalRoomModule.cs b/test/Carter.Tests/InternalRooms/InternalRoomModule.cs new file mode 100644 index 0000000..ef8c045 --- /dev/null +++ b/test/Carter.Tests/InternalRooms/InternalRoomModule.cs @@ -0,0 +1,17 @@ +namespace Carter.Tests.InternalRooms; + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +internal class InternalRoomModule : ICarterModule +{ + public void AddRoutes(IEndpointRouteBuilder app) + { + app.MapGet("/", (HttpResponse res) => + { + res.StatusCode = 409; + return Results.Text("There's no place like 127.0.0.1"); + }); + } +} diff --git a/test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs b/test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs new file mode 100644 index 0000000..d73acfd --- /dev/null +++ b/test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs @@ -0,0 +1,23 @@ +namespace Carter.Tests.InternalRooms; + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +internal static class NestedInternalResponseNegotiatorWrapper +{ + internal class NestedInternalResponseNegotiator: IResponseNegotiator + { + public bool CanHandle(MediaTypeHeaderValue accept) + { + return true; + } + + public Task Handle(HttpRequest req, HttpResponse res, T model, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} + diff --git a/test/Carter.Tests/InternalRooms/NestedInternalRoomModelValidator.cs b/test/Carter.Tests/InternalRooms/NestedInternalRoomModelValidator.cs new file mode 100644 index 0000000..da2bdf2 --- /dev/null +++ b/test/Carter.Tests/InternalRooms/NestedInternalRoomModelValidator.cs @@ -0,0 +1,15 @@ +namespace Carter.Tests.InternalRooms; + +using FluentValidation; + +internal static class NestedInternalTestModelValidatorWrapper +{ + internal class InternalTestModelValidator : AbstractValidator + { + public InternalTestModelValidator() + { + this.RuleFor(x => x.Name).NotEmpty(); + } + } +} + diff --git a/test/Carter.Tests/InternalRooms/NestedInternalRoomModule.cs b/test/Carter.Tests/InternalRooms/NestedInternalRoomModule.cs new file mode 100644 index 0000000..e65afed --- /dev/null +++ b/test/Carter.Tests/InternalRooms/NestedInternalRoomModule.cs @@ -0,0 +1,22 @@ +namespace Carter.Tests.InternalRooms; + +using Carter; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +internal static class NestedInternalRoomModuleWrapper +{ + internal class NestedInternalRoomModule : ICarterModule + { + public void AddRoutes(IEndpointRouteBuilder app) + { + app.MapGet("/nested-room", (HttpResponse res) => + { + res.StatusCode = 409; + return Results.Text("There's no place like 127.0.0.1"); + }); + } + } +} + From b5d7adb60162380308d5b8f0b77d2c6e06a5d66d Mon Sep 17 00:00:00 2001 From: vinhdev17 Date: Sun, 30 Nov 2025 13:36:26 +0700 Subject: [PATCH 3/3] fix: implement negotiation helper and add TestNegotiator attribute --- .../Attributes/TestNegotiatorAttribute.cs | 6 +++ src/Carter/Helpers/NegotiationHelper.cs | 50 +++++++++++++++++++ src/Carter/Response/ResponseExtensions.cs | 38 +++++--------- .../NewtonsoftJsonResponseNegotiatorTests.cs | 1 + .../ContentNegotiation/NegotiatorModule.cs | 1 - .../ResponseNegotiatorTests.cs | 5 ++ .../TestResponseExtensions.cs | 22 ++++++++ .../InternalResponseNegotiator.cs | 2 + .../NestedInternalResponseNegotiator.cs | 2 + 9 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 src/Carter/Attributes/TestNegotiatorAttribute.cs create mode 100644 src/Carter/Helpers/NegotiationHelper.cs create mode 100644 test/Carter.Tests/ContentNegotiation/TestResponseExtensions.cs diff --git a/src/Carter/Attributes/TestNegotiatorAttribute.cs b/src/Carter/Attributes/TestNegotiatorAttribute.cs new file mode 100644 index 0000000..7ffc1f6 --- /dev/null +++ b/src/Carter/Attributes/TestNegotiatorAttribute.cs @@ -0,0 +1,6 @@ +namespace Carter.Attributes; + +using System; + +[AttributeUsage(AttributeTargets.Class)] +public class TestNegotiatorAttribute : Attribute { } diff --git a/src/Carter/Helpers/NegotiationHelper.cs b/src/Carter/Helpers/NegotiationHelper.cs new file mode 100644 index 0000000..2543ca4 --- /dev/null +++ b/src/Carter/Helpers/NegotiationHelper.cs @@ -0,0 +1,50 @@ +namespace Carter.Helpers; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Carter.Attributes; +using Carter.Response; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +public static class NegotiationHelper +{ + /// + /// Selects the most appropriate for content negotiation based on the current 's "Accept" headers, + /// or defaults to if none match. + /// + /// Current + /// List of available instances + /// The selected for the response. + public static IResponseNegotiator SelectNegotiator(HttpContext httpContext, List negotiators) + { + IResponseNegotiator negotiator = null; + + MediaTypeHeaderValue.TryParseList(httpContext.Request.Headers["Accept"], out var accept); + if (accept != null) + { + var ordered = accept.OrderByDescending(x => x.Quality ?? 1); + + foreach (var acceptHeader in ordered) + { + negotiator = negotiators.FirstOrDefault(x => x.CanHandle(acceptHeader)); + if (negotiator != null) + { + break; + } + } + } + + if (negotiator == null) + { + negotiator = negotiators.First(x => x.GetType() == typeof(DefaultJsonResponseNegotiator)); + } + + return negotiator; + } + + public static bool IsTestNegotiator(IResponseNegotiator negotiator) + => negotiator.GetType().IsDefined(typeof(TestNegotiatorAttribute), inherit: true); +} diff --git a/src/Carter/Response/ResponseExtensions.cs b/src/Carter/Response/ResponseExtensions.cs index 904f8a6..bff7787 100644 --- a/src/Carter/Response/ResponseExtensions.cs +++ b/src/Carter/Response/ResponseExtensions.cs @@ -7,6 +7,7 @@ namespace Carter.Response; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; +using Carter.Helpers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; @@ -23,30 +24,14 @@ public static class ResponseExtensions /// public static Task Negotiate(this HttpResponse response, T model, CancellationToken cancellationToken = default) { - var negotiators = response.HttpContext.RequestServices.GetServices().ToList(); - IResponseNegotiator negotiator = null; - - MediaTypeHeaderValue.TryParseList(response.HttpContext.Request.Headers["Accept"], out var accept); - if (accept != null) - { - var ordered = accept.OrderByDescending(x => x.Quality ?? 1); - - foreach (var acceptHeader in ordered) - { - negotiator = negotiators.FirstOrDefault(x => x.CanHandle(acceptHeader)); - if (negotiator != null) - { - break; - } - } - } - - if (negotiator == null) - { - negotiator = negotiators.First(x => x.CanHandle(new MediaTypeHeaderValue("application/json"))); - } - - return negotiator.Handle(response.HttpContext.Request, response, model, cancellationToken); + var negotiators = response.HttpContext.RequestServices + .GetServices() + .Where(n => !NegotiationHelper.IsTestNegotiator(n)) + .ToList(); + + var chosenNegotiator = NegotiationHelper.SelectNegotiator(response.HttpContext, negotiators); + + return chosenNegotiator.Handle(response.HttpContext.Request, response, model, cancellationToken); } /// @@ -58,7 +43,10 @@ public static Task Negotiate(this HttpResponse response, T model, Cancellatio /// public static Task AsJson(this HttpResponse response, T model, CancellationToken cancellationToken = default) { - var negotiators = response.HttpContext.RequestServices.GetServices(); + var negotiators = response.HttpContext.RequestServices + .GetServices() + .Where(n => !NegotiationHelper.IsTestNegotiator(n)) + .ToList(); var negotiator = negotiators.First(x => x.CanHandle(new MediaTypeHeaderValue("application/json"))); diff --git a/test/Carter.ResponseNegotiators.Newtonsoft.Tests/NewtonsoftJsonResponseNegotiatorTests.cs b/test/Carter.ResponseNegotiators.Newtonsoft.Tests/NewtonsoftJsonResponseNegotiatorTests.cs index a470139..2e19a9a 100644 --- a/test/Carter.ResponseNegotiators.Newtonsoft.Tests/NewtonsoftJsonResponseNegotiatorTests.cs +++ b/test/Carter.ResponseNegotiators.Newtonsoft.Tests/NewtonsoftJsonResponseNegotiatorTests.cs @@ -89,6 +89,7 @@ public async Task Should_pick_default_json_processor_last() } } + //TODO: Add [TestNegotiator] attribute when Carter supports it in this project internal class TestJsonResponseNegotiator : IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept) => accept diff --git a/test/Carter.Tests/ContentNegotiation/NegotiatorModule.cs b/test/Carter.Tests/ContentNegotiation/NegotiatorModule.cs index 9b75848..3f2f91d 100644 --- a/test/Carter.Tests/ContentNegotiation/NegotiatorModule.cs +++ b/test/Carter.Tests/ContentNegotiation/NegotiatorModule.cs @@ -1,6 +1,5 @@ namespace Carter.Tests.ContentNegotiation { - using Carter.Response; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; diff --git a/test/Carter.Tests/ContentNegotiation/ResponseNegotiatorTests.cs b/test/Carter.Tests/ContentNegotiation/ResponseNegotiatorTests.cs index 2a93e16..ecc44df 100644 --- a/test/Carter.Tests/ContentNegotiation/ResponseNegotiatorTests.cs +++ b/test/Carter.Tests/ContentNegotiation/ResponseNegotiatorTests.cs @@ -5,6 +5,7 @@ namespace Carter.Tests.ContentNegotiation using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; + using Carter.Attributes; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -125,6 +126,7 @@ public async Task Should_pick_default_json_processor_last() } } + [TestNegotiator] internal class TestResponseNegotiator : IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept) => @@ -137,6 +139,7 @@ public async Task Handle(HttpRequest req, HttpResponse res, T model, } } + [TestNegotiator] internal class TestHtmlResponseNegotiator : IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept) => @@ -149,6 +152,7 @@ public async Task Handle(HttpRequest req, HttpResponse res, T model, } } + [TestNegotiator] internal class TestXmlResponseNegotiator : IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept) => @@ -161,6 +165,7 @@ public async Task Handle(HttpRequest req, HttpResponse res, T model, } } + [TestNegotiator] internal class TestJsonResponseNegotiator : IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept) => accept diff --git a/test/Carter.Tests/ContentNegotiation/TestResponseExtensions.cs b/test/Carter.Tests/ContentNegotiation/TestResponseExtensions.cs new file mode 100644 index 0000000..357d336 --- /dev/null +++ b/test/Carter.Tests/ContentNegotiation/TestResponseExtensions.cs @@ -0,0 +1,22 @@ +namespace Carter.Tests.ContentNegotiation; + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Carter.Helpers; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +public static class TestResponseExtensions +{ + public static Task Negotiate(this HttpResponse response, T model, CancellationToken cancellationToken = default) + { + var negotiators = response.HttpContext.RequestServices + .GetServices() + .ToList(); + + var chosenNegotiator = NegotiationHelper.SelectNegotiator(response.HttpContext, negotiators); + + return chosenNegotiator.Handle(response.HttpContext.Request, response, model, cancellationToken); + } +} diff --git a/test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs b/test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs index f67de0d..e32bc93 100644 --- a/test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs +++ b/test/Carter.Tests/InternalRooms/InternalResponseNegotiator.cs @@ -2,9 +2,11 @@ namespace Carter.Tests.InternalRooms; using System.Threading; using System.Threading.Tasks; +using Carter.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; +[TestNegotiator] internal class InternalResponseNegotiator: IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept) diff --git a/test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs b/test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs index d73acfd..a4a978a 100644 --- a/test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs +++ b/test/Carter.Tests/InternalRooms/NestedInternalResponseNegotiator.cs @@ -2,11 +2,13 @@ namespace Carter.Tests.InternalRooms; using System.Threading; using System.Threading.Tasks; +using Carter.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; internal static class NestedInternalResponseNegotiatorWrapper { + [TestNegotiator] internal class NestedInternalResponseNegotiator: IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept)