From 28ef87f163b9815f8da175da7538da29346f34f3 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 13 Jun 2023 17:47:05 +0200 Subject: [PATCH 1/7] Use object to not couple to Json and allow output formatters It would be better to use generics. --- .../API/APIApprovals.Approve_API.verified.txt | 8 ++++---- .../ScatterGather/Get_with_2_gatherers.cs | 1 + .../ScatterGather/DefaultAggregator.cs | 8 ++++---- src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs | 3 +-- .../ScatterGather/HttpGatherer.cs | 2 +- .../ScatterGather/IAggregator.cs | 4 ++-- .../ScatterGather/ScatterGatherOptions.cs | 1 + src/Snippets/ScatterGather/GatherMethodOverride.cs | 5 ++--- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt b/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt index 6488bb5d..cc0828e4 100644 --- a/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt +++ b/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt @@ -64,7 +64,7 @@ namespace ServiceComposer.AspNetCore { protected Gatherer(string key) { } public string Key { get; } - public abstract System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context); + public abstract System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context); } public class HttpGatherer : ServiceComposer.AspNetCore.Gatherer { @@ -72,7 +72,7 @@ namespace ServiceComposer.AspNetCore public System.Func DefaultDestinationUrlMapper { get; } public string DestinationUrl { get; } public System.Func DestinationUrlMapper { get; init; } - public override System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context) { } + public override System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context) { } protected virtual string MapDestinationUrl(Microsoft.AspNetCore.Http.HttpRequest request, string destination) { } protected virtual System.Threading.Tasks.Task> TransformResponse(System.Net.Http.HttpResponseMessage responseMessage) { } } @@ -92,8 +92,8 @@ namespace ServiceComposer.AspNetCore } public interface IAggregator { - void Add(System.Collections.Generic.IEnumerable nodes); - System.Threading.Tasks.Task Aggregate(); + void Add(System.Collections.Generic.IEnumerable nodes); + System.Threading.Tasks.Task Aggregate(); } public interface ICompositionContext { diff --git a/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs index 5a09bb9f..5c002760 100644 --- a/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs +++ b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs @@ -73,6 +73,7 @@ HttpClient ClientProvider(string name) => // TODO: does this need to register a default HTTP client? // services.AddScatterGather(); services.AddRouting(); + services.AddControllers(); services.Replace( new ServiceDescriptor(typeof(IHttpClientFactory), new DelegateHttpClientFactory(ClientProvider))); diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/DefaultAggregator.cs b/src/ServiceComposer.AspNetCore/ScatterGather/DefaultAggregator.cs index e8e8e368..57f31994 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/DefaultAggregator.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/DefaultAggregator.cs @@ -9,17 +9,17 @@ class DefaultAggregator : IAggregator { readonly ConcurrentBag allNodes = new(); - public void Add(IEnumerable nodes) + public void Add(IEnumerable nodes) { foreach (var node in nodes) { - allNodes.Add(node); + allNodes.Add((JsonNode)node); } } - public Task Aggregate() + public Task Aggregate() { var responsesArray = new JsonArray(allNodes.ToArray()); - return Task.FromResult(responsesArray); + return Task.FromResult((object)responsesArray); } } \ No newline at end of file diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs b/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs index 40690125..e67a55a3 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs @@ -14,6 +14,5 @@ protected Gatherer(string key) public string Key { get; } - // TODO: how to use generics to remove the dependency on JSON? - public abstract Task> Gather(HttpContext context); + public abstract Task> Gather(HttpContext context); } \ No newline at end of file diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs b/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs index ed660c13..3a633e3b 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs @@ -55,7 +55,7 @@ protected virtual async Task> TransformResponse(HttpRespon return nodes; } - public override async Task> Gather(HttpContext context) + public override async Task> Gather(HttpContext context) { var factory = context.RequestServices.GetRequiredService(); var client = factory.CreateClient(Key); diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/IAggregator.cs b/src/ServiceComposer.AspNetCore/ScatterGather/IAggregator.cs index 706b21bd..9ca2db66 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/IAggregator.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/IAggregator.cs @@ -6,6 +6,6 @@ namespace ServiceComposer.AspNetCore; public interface IAggregator { - void Add(IEnumerable nodes); - Task Aggregate(); + void Add(IEnumerable nodes); + Task Aggregate(); } \ No newline at end of file diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs index 74d5d4ef..a1ad0ff2 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs @@ -14,6 +14,7 @@ internal IAggregator GetAggregator(HttpContext httpContext) { return (IAggregator)httpContext.RequestServices.GetRequiredService(CustomAggregator); } + return new DefaultAggregator(); } diff --git a/src/Snippets/ScatterGather/GatherMethodOverride.cs b/src/Snippets/ScatterGather/GatherMethodOverride.cs index 251c33f0..bc90359d 100644 --- a/src/Snippets/ScatterGather/GatherMethodOverride.cs +++ b/src/Snippets/ScatterGather/GatherMethodOverride.cs @@ -14,10 +14,9 @@ public class CustomHttpGatherer : HttpGatherer { public CustomHttpGatherer(string key, string destination) : base(key, destination) { } - public override Task> Gather(HttpContext context) + public override Task> Gather(HttpContext context) { - // by overriding this method we can implement custom logic - // to gather the responses from the downstream service. + return base.Gather(context); } } From d5f79b5ce9ac8c3b7852d50054a99383f515592a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 13 Jun 2023 15:47:28 +0000 Subject: [PATCH 2/7] MarkdownSnippets documentation changes --- docs/scatter-gather.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/scatter-gather.md b/docs/scatter-gather.md index 516d4d3e..9edaa627 100644 --- a/docs/scatter-gather.md +++ b/docs/scatter-gather.md @@ -92,13 +92,12 @@ public class CustomHttpGatherer : HttpGatherer { public CustomHttpGatherer(string key, string destination) : base(key, destination) { } - public override Task> Gather(HttpContext context) + public override Task> Gather(HttpContext context) { - // by overriding this method we can implement custom logic - // to gather the responses from the downstream service. + return base.Gather(context); } } ``` -snippet source | anchor +snippet source | anchor From 92f23c94f7bddc6d309d3b45b7af2ba68b52751b Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Tue, 13 Jun 2023 19:20:25 +0200 Subject: [PATCH 3/7] A little bit better API --- .../API/APIApprovals.Approve_API.verified.txt | 16 +++++++++++----- .../ScatterGather/Get_with_2_gatherers.cs | 2 +- .../ScatterGather/When_using_query_string.cs | 2 +- .../ScatterGather/Gatherer.cs | 16 ++++++++++++++-- .../ScatterGather/HttpGatherer.cs | 4 ++-- .../ScatterGather/ScatterGatherOptions.cs | 2 +- .../ScatterGather/CustomizingDownstreamURLs.cs | 2 +- .../ScatterGather/GatherMethodOverride.cs | 2 +- src/Snippets/ScatterGather/Startup.cs | 2 +- 9 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt b/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt index cc0828e4..6dd3cfab 100644 --- a/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt +++ b/src/ServiceComposer.AspNetCore.Tests/API/APIApprovals.Approve_API.verified.txt @@ -60,19 +60,20 @@ namespace ServiceComposer.AspNetCore " removed in v3. Use attribute routing based composition, and CompositionEventHan" + "dler.", true)] public delegate System.Threading.Tasks.Task EventHandler(string requestId, [System.Runtime.CompilerServices.Dynamic] object viewModel, TEvent @event, Microsoft.AspNetCore.Routing.RouteData routeData, Microsoft.AspNetCore.Http.HttpRequest httpRequest); - public abstract class Gatherer + public abstract class Gatherer : ServiceComposer.AspNetCore.IGatherer + where T : class { protected Gatherer(string key) { } public string Key { get; } - public abstract System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context); + public abstract System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context); } - public class HttpGatherer : ServiceComposer.AspNetCore.Gatherer + public class HttpGatherer : ServiceComposer.AspNetCore.Gatherer { public HttpGatherer(string key, string destinationUrl) { } public System.Func DefaultDestinationUrlMapper { get; } public string DestinationUrl { get; } public System.Func DestinationUrlMapper { get; init; } - public override System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context) { } + public override System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context) { } protected virtual string MapDestinationUrl(Microsoft.AspNetCore.Http.HttpRequest request, string destination) { } protected virtual System.Threading.Tasks.Task> TransformResponse(System.Net.Http.HttpResponseMessage responseMessage) { } } @@ -117,6 +118,11 @@ namespace ServiceComposer.AspNetCore System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpRequest request); } public interface IEndpointScopedViewModelFactory : ServiceComposer.AspNetCore.IViewModelFactory { } + public interface IGatherer + { + string Key { get; } + System.Threading.Tasks.Task> Gather(Microsoft.AspNetCore.Http.HttpContext context); + } [System.Obsolete("IHandleRequests is obsoleted and will be treated as an error starting v2 and remo" + "ved in v3. Use attribute routing based composition and ICompositionRequestsHandl" + "er.", true)] @@ -188,7 +194,7 @@ namespace ServiceComposer.AspNetCore { public ScatterGatherOptions() { } public System.Type CustomAggregator { get; set; } - public System.Collections.Generic.IList Gatherers { get; set; } + public System.Collections.Generic.IList Gatherers { get; set; } } public static class ServiceCollectionExtensions { diff --git a/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs index 5c002760..371bb4fd 100644 --- a/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs +++ b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_2_gatherers.cs @@ -85,7 +85,7 @@ HttpClient ClientProvider(string name) => { builder.MapScatterGather(template: "/samples", new ScatterGatherOptions { - Gatherers = new List + Gatherers = new List { new HttpGatherer(key: "ASamplesSource", destinationUrl: "/samples/ASamplesSource"), new HttpGatherer(key: "AnotherSamplesSource", destinationUrl: "/samples/AnotherSamplesSource") diff --git a/src/ServiceComposer.AspNetCore.Tests/ScatterGather/When_using_query_string.cs b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/When_using_query_string.cs index 080ade00..3448d1c7 100644 --- a/src/ServiceComposer.AspNetCore.Tests/ScatterGather/When_using_query_string.cs +++ b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/When_using_query_string.cs @@ -84,7 +84,7 @@ HttpClient ClientProvider(string name) => { builder.MapScatterGather(template: "/samples", new ScatterGatherOptions { - Gatherers = new List + Gatherers = new List { new HttpGatherer(key: "ASamplesSource", destinationUrl: "/samples/ASamplesSource"), new HttpGatherer(key: "AnotherSamplesSource", destinationUrl: "/samples/AnotherSamplesSource") diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs b/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs index e67a55a3..f5dff8f9 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs @@ -1,11 +1,18 @@ using System.Collections.Generic; +using System.Linq; using System.Text.Json.Nodes; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace ServiceComposer.AspNetCore; -public abstract class Gatherer +public interface IGatherer +{ + string Key { get; } + Task> Gather(HttpContext context); +} + +public abstract class Gatherer : IGatherer where T : class { protected Gatherer(string key) { @@ -14,5 +21,10 @@ protected Gatherer(string key) public string Key { get; } - public abstract Task> Gather(HttpContext context); + Task> IGatherer.Gather(HttpContext context) + { + return Gather(context).ContinueWith(t => (IEnumerable)t.Result); + } + + public abstract Task> Gather(HttpContext context); } \ No newline at end of file diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs b/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs index 3a633e3b..0cfcb8ec 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/HttpGatherer.cs @@ -8,7 +8,7 @@ namespace ServiceComposer.AspNetCore; -public class HttpGatherer : Gatherer +public class HttpGatherer : Gatherer { public HttpGatherer(string key, string destinationUrl) : base(key) @@ -55,7 +55,7 @@ protected virtual async Task> TransformResponse(HttpRespon return nodes; } - public override async Task> Gather(HttpContext context) + public override async Task> Gather(HttpContext context) { var factory = context.RequestServices.GetRequiredService(); var client = factory.CreateClient(Key); diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs index a1ad0ff2..0140ef07 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherOptions.cs @@ -18,5 +18,5 @@ internal IAggregator GetAggregator(HttpContext httpContext) return new DefaultAggregator(); } - public IList Gatherers { get; set; } + public IList Gatherers { get; set; } } \ No newline at end of file diff --git a/src/Snippets/ScatterGather/CustomizingDownstreamURLs.cs b/src/Snippets/ScatterGather/CustomizingDownstreamURLs.cs index e6303e6a..c1b98dee 100644 --- a/src/Snippets/ScatterGather/CustomizingDownstreamURLs.cs +++ b/src/Snippets/ScatterGather/CustomizingDownstreamURLs.cs @@ -12,7 +12,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { app.UseEndpoints(builder => builder.MapScatterGather(template: "api/scatter-gather", new ScatterGatherOptions() { - Gatherers = new List + Gatherers = new List { new HttpGatherer("ASamplesSource", "https://a.web.server/api/samples/ASamplesSource") { diff --git a/src/Snippets/ScatterGather/GatherMethodOverride.cs b/src/Snippets/ScatterGather/GatherMethodOverride.cs index bc90359d..96519e86 100644 --- a/src/Snippets/ScatterGather/GatherMethodOverride.cs +++ b/src/Snippets/ScatterGather/GatherMethodOverride.cs @@ -14,7 +14,7 @@ public class CustomHttpGatherer : HttpGatherer { public CustomHttpGatherer(string key, string destination) : base(key, destination) { } - public override Task> Gather(HttpContext context) + public override Task> Gather(HttpContext context) { return base.Gather(context); diff --git a/src/Snippets/ScatterGather/Startup.cs b/src/Snippets/ScatterGather/Startup.cs index 0434c83f..380973f1 100644 --- a/src/Snippets/ScatterGather/Startup.cs +++ b/src/Snippets/ScatterGather/Startup.cs @@ -14,7 +14,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) app.UseRouting(); app.UseEndpoints(builder => builder.MapScatterGather(template: "api/scatter-gather", new ScatterGatherOptions() { - Gatherers = new List + Gatherers = new List { new HttpGatherer(key: "ASamplesSource", destinationUrl: "https://a.web.server/api/samples/ASamplesSource"), new HttpGatherer(key: "AnotherSamplesSource", destinationUrl: "https://another.web.server/api/samples/AnotherSamplesSource") From c4e77730c1a0a344a95bea301dc03d9b1e37f43d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 13 Jun 2023 17:20:49 +0000 Subject: [PATCH 4/7] MarkdownSnippets documentation changes --- docs/scatter-gather.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/scatter-gather.md b/docs/scatter-gather.md index 9edaa627..a01d8278 100644 --- a/docs/scatter-gather.md +++ b/docs/scatter-gather.md @@ -12,7 +12,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) app.UseRouting(); app.UseEndpoints(builder => builder.MapScatterGather(template: "api/scatter-gather", new ScatterGatherOptions() { - Gatherers = new List + Gatherers = new List { new HttpGatherer(key: "ASamplesSource", destinationUrl: "https://a.web.server/api/samples/ASamplesSource"), new HttpGatherer(key: "AnotherSamplesSource", destinationUrl: "https://another.web.server/api/samples/AnotherSamplesSource") @@ -38,7 +38,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { app.UseEndpoints(builder => builder.MapScatterGather(template: "api/scatter-gather", new ScatterGatherOptions() { - Gatherers = new List + Gatherers = new List { new HttpGatherer("ASamplesSource", "https://a.web.server/api/samples/ASamplesSource") { @@ -92,7 +92,7 @@ public class CustomHttpGatherer : HttpGatherer { public CustomHttpGatherer(string key, string destination) : base(key, destination) { } - public override Task> Gather(HttpContext context) + public override Task> Gather(HttpContext context) { return base.Gather(context); From 36f6bb5a6be35a22a8978a76a0bf7e42f3086335 Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Thu, 15 Jun 2023 16:31:39 +0200 Subject: [PATCH 5/7] Fix serialization --- .../ScatterGather/ScatterGatherEndpointBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs index c1a38eb0..0f4c7397 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs @@ -33,7 +33,7 @@ public static IEndpointConventionBuilder MapScatterGather(this IEndpointRouteBui // TODO: support output formatters by using the WriteModelAsync extension method. // It must be under a setting flag, because it requires a dependency on MVC. - await context.Response.WriteAsync(responses.ToJsonString()); + await context.Response.WriteAsync( System.Text.Json.JsonSerializer.Serialize(responses)); }); } } \ No newline at end of file From cfff519aea698dabe335eae6ef412fa0ae3073ff Mon Sep 17 00:00:00 2001 From: Mauro Servienti Date: Fri, 30 Jun 2023 09:28:17 +0200 Subject: [PATCH 6/7] Try to mix and match gatherers --- ...et_with_HttpGatherer_and_CustomGatherer.cs | 113 ++++++++++++++++++ .../ScatterGather/Gatherer.cs | 8 -- .../ScatterGather/IGatherer.cs | 11 ++ .../ScatterGatherEndpointBuilderExtensions.cs | 3 +- .../ScatterGather/TransformResponse.cs | 1 + 5 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_HttpGatherer_and_CustomGatherer.cs create mode 100644 src/ServiceComposer.AspNetCore/ScatterGather/IGatherer.cs diff --git a/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_HttpGatherer_and_CustomGatherer.cs b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_HttpGatherer_and_CustomGatherer.cs new file mode 100644 index 00000000..b81814d8 --- /dev/null +++ b/src/ServiceComposer.AspNetCore.Tests/ScatterGather/Get_with_HttpGatherer_and_CustomGatherer.cs @@ -0,0 +1,113 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using ServiceComposer.AspNetCore.Testing; +using Xunit; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection.Extensions; +using ServiceComposer.AspNetCore.Tests.Utils; + +namespace ServiceComposer.AspNetCore.Tests.ScatterGather; + +public class Get_with_HttpGatherer_and_CustomGatherer +{ + class CustomGatherer : IGatherer + { + public string Key { get; } = "CustomGatherer"; + public Task> Gather(HttpContext context) + { + var data = (IEnumerable)(new []{ new { Value = "ACustomSample" } }); + return Task.FromResult(data); + } + } + + [Fact] + public async Task Returns_expected_response() + { + // Arrange + var aSampleSourceClient = new SelfContainedWebApplicationFactoryWithWebHost + ( + configureServices: services => + { + services.AddRouting(); + }, + configure: app => + { + app.UseRouting(); + app.UseEndpoints(builder => + { + builder.MapGet("/samples/ASamplesSource", () => + { + return new []{ new { Value = "ASample" } }; + }); + }); + } + ).CreateClient(); + + var client = new SelfContainedWebApplicationFactoryWithWebHost + ( + configureServices: services => + { + HttpClient ClientProvider(string name) => + name switch + { + "ASamplesSource" => aSampleSourceClient, + _ => throw new NotSupportedException($"Missing HTTP client for {name}") + }; + + // TODO: does this need to register a default HTTP client? + // services.AddScatterGather(); + services.AddRouting(); + services.AddControllers(); + services.Replace( + new ServiceDescriptor(typeof(IHttpClientFactory), + new DelegateHttpClientFactory(ClientProvider))); + }, + configure: app => + { + app.UseRouting(); + app.UseEndpoints(builder => + { + builder.MapScatterGather(template: "/samples", new ScatterGatherOptions + { + Gatherers = new List + { + new HttpGatherer(key: "ASamplesSource", destinationUrl: "/samples/ASamplesSource"), + new CustomGatherer() + } + }); + }); + } + ).CreateClient(); + + // Act + var response = await client.GetAsync("/samples"); + + // Assert + Assert.True(response.IsSuccessStatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var responseArray = JsonNode.Parse(responseString)!.AsArray(); + var responseArrayAsJsonStrings = new HashSet(responseArray.Select(n=>n.ToJsonString())); + + var expectedArray = JsonNode.Parse(JsonSerializer.Serialize( new[] + { + new {Value = "ASample"}, + new {Value = "ACustomSample"} + }, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }))!.AsArray(); + var expectedArrayAsJsonStrings = new HashSet(expectedArray.Select(n=>n.ToJsonString())); + + Assert.Equal(2, responseArray.Count); + Assert.Equivalent(expectedArrayAsJsonStrings, responseArrayAsJsonStrings); + } +} + diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs b/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs index f5dff8f9..cc5190ef 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/Gatherer.cs @@ -1,17 +1,9 @@ using System.Collections.Generic; -using System.Linq; -using System.Text.Json.Nodes; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace ServiceComposer.AspNetCore; -public interface IGatherer -{ - string Key { get; } - Task> Gather(HttpContext context); -} - public abstract class Gatherer : IGatherer where T : class { protected Gatherer(string key) diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/IGatherer.cs b/src/ServiceComposer.AspNetCore/ScatterGather/IGatherer.cs new file mode 100644 index 00000000..f5fdbc5c --- /dev/null +++ b/src/ServiceComposer.AspNetCore/ScatterGather/IGatherer.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace ServiceComposer.AspNetCore; + +public interface IGatherer +{ + string Key { get; } + Task> Gather(HttpContext context); +} \ No newline at end of file diff --git a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs index 0f4c7397..4f42afa6 100644 --- a/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs +++ b/src/ServiceComposer.AspNetCore/ScatterGather/ScatterGatherEndpointBuilderExtensions.cs @@ -31,8 +31,7 @@ public static IEndpointConventionBuilder MapScatterGather(this IEndpointRouteBui await Task.WhenAll(tasks); var responses = await aggregator.Aggregate(); - // TODO: support output formatters by using the WriteModelAsync extension method. - // It must be under a setting flag, because it requires a dependency on MVC. + // TODO: support output formatters by using the WriteModelAsync extension method. It must be under a setting flag, because it requires a dependency on MVC. await context.Response.WriteAsync( System.Text.Json.JsonSerializer.Serialize(responses)); }); } diff --git a/src/Snippets/ScatterGather/TransformResponse.cs b/src/Snippets/ScatterGather/TransformResponse.cs index 49b7f3de..d5abac67 100644 --- a/src/Snippets/ScatterGather/TransformResponse.cs +++ b/src/Snippets/ScatterGather/TransformResponse.cs @@ -3,6 +3,7 @@ using System.Net.Http; using System.Text.Json.Nodes; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using ServiceComposer.AspNetCore; namespace Snippets.ScatterGather; From 2c5d8355dc7fd1a417be33ebccc390631b86ce4b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 30 Jun 2023 07:28:46 +0000 Subject: [PATCH 7/7] MarkdownSnippets documentation changes --- docs/scatter-gather.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scatter-gather.md b/docs/scatter-gather.md index a01d8278..bdd70841 100644 --- a/docs/scatter-gather.md +++ b/docs/scatter-gather.md @@ -78,7 +78,7 @@ public class CustomHttpGatherer : HttpGatherer } } ``` -snippet source | anchor +snippet source | anchor ### Taking control of the downstream invocation process