From c632bbf59240501d539fb63eeb5eeb0e58625d2f Mon Sep 17 00:00:00 2001 From: Hugo Woodiwiss Date: Sat, 18 Jan 2025 15:20:32 +0000 Subject: [PATCH] Fix StructureMap Transient Middleware Resolution (#1619) * Fix StructureMap Transient Middleware Resolution In StructureMap the Transient lifetime is more akin to MEDI's Scoped lifetime, where a single object instance will be created for each top level call to `Container.GetInstance()`. Currently, the JustSayingRegistry causes a throw at startup if you don't clear and re-register these middleware as AlwaysUnique. https://structuremap.github.io/object-lifecycle/supported-lifecycles/ * Add regression test covering StructureMap middleware lifetime --- .../JustSayingRegistry.cs | 4 +- .../Handle/HandlerMiddlewareBuilder.cs | 2 +- .../StructureMapTests.cs | 41 +++++++++++++++++++ .../JustSaying.TestingFramework/Patiently.cs | 17 +++++--- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/JustSaying.Extensions.DependencyInjection.StructureMap/JustSayingRegistry.cs b/src/JustSaying.Extensions.DependencyInjection.StructureMap/JustSayingRegistry.cs index 59be6c2df..ebd18616e 100644 --- a/src/JustSaying.Extensions.DependencyInjection.StructureMap/JustSayingRegistry.cs +++ b/src/JustSaying.Extensions.DependencyInjection.StructureMap/JustSayingRegistry.cs @@ -41,8 +41,8 @@ public JustSayingRegistry() For().Use(context => context.GetInstance()); For().Use(context => context.GetInstance()); - For().Transient(); - For().Transient(); + For().AlwaysUnique(); + For().AlwaysUnique(); For() .Use( diff --git a/src/JustSaying/Messaging/Middleware/Handle/HandlerMiddlewareBuilder.cs b/src/JustSaying/Messaging/Middleware/Handle/HandlerMiddlewareBuilder.cs index 77c07c11b..608bf07a5 100644 --- a/src/JustSaying/Messaging/Middleware/Handle/HandlerMiddlewareBuilder.cs +++ b/src/JustSaying/Messaging/Middleware/Handle/HandlerMiddlewareBuilder.cs @@ -38,7 +38,7 @@ public HandlerMiddlewareBuilder Use() where TMiddleware : Middlewar { throw new InvalidOperationException( @"Middlewares must be registered into your DI container such that each resolution creates a new instance. -For StructureMap use Transient(), and for Microsoft.Extensions.DependencyInjection, use AddTransient(). +For StructureMap use AlwaysUnique(), and for Microsoft.Extensions.DependencyInjection, use AddTransient(). Please check the documentation for your container for more details."); } diff --git a/tests/JustSaying.Extensions.DependencyInjection.StructureMap.Tests/StructureMapTests.cs b/tests/JustSaying.Extensions.DependencyInjection.StructureMap.Tests/StructureMapTests.cs index 4fc4130d8..d80503ec8 100644 --- a/tests/JustSaying.Extensions.DependencyInjection.StructureMap.Tests/StructureMapTests.cs +++ b/tests/JustSaying.Extensions.DependencyInjection.StructureMap.Tests/StructureMapTests.cs @@ -1,5 +1,9 @@ +using JustSaying.Fluent; using JustSaying.Messaging; using JustSaying.Messaging.MessageHandling; +using JustSaying.Messaging.Middleware; +using JustSaying.Messaging.Middleware.Logging; +using JustSaying.Messaging.Middleware.PostProcessing; using JustSaying.TestingFramework; using Microsoft.Extensions.Logging; using Shouldly; @@ -64,4 +68,41 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue() // Assert handler.ReceivedMessages.ShouldContain(x => x.GetType() == typeof(SimpleMessage)); } + + [AwsFact] + public void Registers_Middleware_With_The_Correct_Lifetime() + { + // Arrange + using var container = new Container( + (registry) => + { + registry.For() + .Use(() => OutputHelper.ToLoggerFactory()) + .Singleton(); + + registry.AddJustSaying( + (builder) => + { + builder.Client((options) => + options.WithBasicCredentials("accessKey", "secretKey") + .WithServiceUri(TestEnvironment.SimulatorUrl)) + .Messaging((options) => options.WithRegion("eu-west-1")); + }); + }); + + var handlerMiddlewareBuilder = new HandlerMiddlewareBuilder(container.GetInstance(), container.GetInstance()); + var useLoggingMiddlewareTwice = () => UseMiddlewareTwice(); + var useSqsPostProcessorMiddlewareTwice = () => UseMiddlewareTwice(); + + void UseMiddlewareTwice() + where T : MiddlewareBase + { + handlerMiddlewareBuilder.Use(); + handlerMiddlewareBuilder.Use(); + } + + // Act + Assert + useLoggingMiddlewareTwice.ShouldNotThrow(); + useSqsPostProcessorMiddlewareTwice.ShouldNotThrow(); + } } diff --git a/tests/JustSaying.TestingFramework/Patiently.cs b/tests/JustSaying.TestingFramework/Patiently.cs index 3d4fae2f8..535caed22 100644 --- a/tests/JustSaying.TestingFramework/Patiently.cs +++ b/tests/JustSaying.TestingFramework/Patiently.cs @@ -31,16 +31,20 @@ public static async Task AssertThatAsync( ITestOutputHelper output, Func func, [System.Runtime.CompilerServices.CallerMemberName] - string memberName = "") - => await AssertThatAsyncInner(output, func, 5.Seconds(), memberName).ConfigureAwait(false); + string memberName = "", + [System.Runtime.CompilerServices.CallerArgumentExpression("func")] + string assertionExpression = "") + => await AssertThatAsyncInner(output, func, 5.Seconds(), memberName, assertionExpression).ConfigureAwait(false); public static async Task AssertThatAsync( ITestOutputHelper output, Func func, TimeSpan timeout, [System.Runtime.CompilerServices.CallerMemberName] - string memberName = "") - => await AssertThatAsyncInner(output, func, timeout, memberName).ConfigureAwait(false); + string memberName = "", + [System.Runtime.CompilerServices.CallerArgumentExpression("func")] + string assertionExpression = "") + => await AssertThatAsyncInner(output, func, timeout, memberName, assertionExpression).ConfigureAwait(false); public static async Task AssertThatAsync(ITestOutputHelper output, Func> func) => await AssertThatAsync(output, func, 5.Seconds()).ConfigureAwait(false); @@ -80,7 +84,8 @@ private static async Task AssertThatAsyncInner( ITestOutputHelper output, Func func, TimeSpan timeout, - string description) + string description, + string assertionExpression) { var watch = new Stopwatch(); watch.Start(); @@ -106,7 +111,7 @@ private static async Task AssertThatAsyncInner( $"Waiting for {watch.Elapsed.TotalMilliseconds} ms - Still waiting for {description}."); } while (watch.Elapsed < timeout); - func.Invoke().ShouldBeTrue(); + func.Invoke().ShouldBeTrue($"Failed to assert that {assertionExpression} within {timeout}"); } }