diff --git a/docs/intro.md b/docs/intro.md
index 8a9f0d01..84fd9241 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -23,6 +23,7 @@
- [ASP.Net Core](#aspnet-core)
- [Modularization of configuration](#modularization-of-configuration)
- [Auto registration of consumers and interceptors](#auto-registration-of-consumers-and-interceptors)
+ - [Message Scope Accessor](#message-scope-accessor)
- [Serialization](#serialization)
- [Multiple message types on one topic (or queue)](#multiple-message-types-on-one-topic-or-queue)
- [Message Type Resolver](#message-type-resolver)
@@ -674,6 +675,14 @@ services.AddSlimMessageBus(mbb =>
});
```
+### Message Scope Accessor
+
+During normal consumer/handler and interceptor life cycles, we can inject any scoped dependencies (services) using the constructor. All is nicely handled by MSDI.
+
+However, for advanced framework integration, if there is a need to get ahold of the `IServiceProvider` tied to the scope of the currently consumed message the [`IMessageScopeAccessor`](../src/SlimMessageBus.Host/Consumer/IMessageScope.cs) can be used.
+It works in a similar way how the [`IHttpContextAccessor`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.ihttpcontextaccessor?view=aspnetcore-8.0) works in ASP.NET Core to lookup the current ongoing HTTP request and the per request scoped services.
+This is useful when the other framework is not managed by MSDI and we still want to hook into the current message scope.
+
## Serialization
SMB uses serialization plugins to serialize (and deserialize) the messages into the desired format.
diff --git a/docs/intro.t.md b/docs/intro.t.md
index 4c718cb0..ff4b09c7 100644
--- a/docs/intro.t.md
+++ b/docs/intro.t.md
@@ -23,6 +23,7 @@
- [ASP.Net Core](#aspnet-core)
- [Modularization of configuration](#modularization-of-configuration)
- [Auto registration of consumers and interceptors](#auto-registration-of-consumers-and-interceptors)
+ - [Message Scope Accessor](#message-scope-accessor)
- [Serialization](#serialization)
- [Multiple message types on one topic (or queue)](#multiple-message-types-on-one-topic-or-queue)
- [Message Type Resolver](#message-type-resolver)
@@ -674,6 +675,14 @@ services.AddSlimMessageBus(mbb =>
});
```
+### Message Scope Accessor
+
+During normal consumer/handler and interceptor life cycles, we can inject any scoped dependencies (services) using the constructor. All is nicely handled by MSDI.
+
+However, for advanced framework integration, if there is a need to get ahold of the `IServiceProvider` tied to the scope of the currently consumed message the [`IMessageScopeAccessor`](../src/SlimMessageBus.Host/Consumer/IMessageScope.cs) can be used.
+It works in a similar way how the [`IHttpContextAccessor`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.ihttpcontextaccessor?view=aspnetcore-8.0) works in ASP.NET Core to lookup the current ongoing HTTP request and the per request scoped services.
+This is useful when the other framework is not managed by MSDI and we still want to hook into the current message scope.
+
## Serialization
SMB uses serialization plugins to serialize (and deserialize) the messages into the desired format.
diff --git a/src/SlimMessageBus.Host.Configuration/SlimMessageBus.Host.Configuration.csproj b/src/SlimMessageBus.Host.Configuration/SlimMessageBus.Host.Configuration.csproj
index a1b90c4e..239663e6 100644
--- a/src/SlimMessageBus.Host.Configuration/SlimMessageBus.Host.Configuration.csproj
+++ b/src/SlimMessageBus.Host.Configuration/SlimMessageBus.Host.Configuration.csproj
@@ -6,7 +6,7 @@
Core configuration interfaces of SlimMessageBus
SlimMessageBus
SlimMessageBus.Host
- 2.4.0
+ 2.5.0-rc1
diff --git a/src/SlimMessageBus.Host/Consumer/IMessageScopeAccessor.cs b/src/SlimMessageBus.Host/Consumer/IMessageScopeAccessor.cs
new file mode 100644
index 00000000..025cd785
--- /dev/null
+++ b/src/SlimMessageBus.Host/Consumer/IMessageScopeAccessor.cs
@@ -0,0 +1,13 @@
+namespace SlimMessageBus.Host.Consumer;
+
+///
+/// Allows to get ahold of the for the current message scope.
+///
+public interface IMessageScopeAccessor
+{
+ ///
+ /// If the running code is within a message scope of a consumer, this property will return the for the current message scope.
+ /// Otherwise it will return null.
+ ///
+ IServiceProvider Current { get; }
+}
\ No newline at end of file
diff --git a/src/SlimMessageBus.Host/Consumer/MessageScopeAccessor.cs b/src/SlimMessageBus.Host/Consumer/MessageScopeAccessor.cs
new file mode 100644
index 00000000..74890f55
--- /dev/null
+++ b/src/SlimMessageBus.Host/Consumer/MessageScopeAccessor.cs
@@ -0,0 +1,6 @@
+namespace SlimMessageBus.Host.Consumer;
+
+internal sealed class MessageScopeAccessor : IMessageScopeAccessor
+{
+ public IServiceProvider Current => MessageScope.Current;
+}
\ No newline at end of file
diff --git a/src/SlimMessageBus.Host/Consumer/MessageScopeWrapper.cs b/src/SlimMessageBus.Host/Consumer/MessageScopeWrapper.cs
index 9fb82834..3ba9da75 100644
--- a/src/SlimMessageBus.Host/Consumer/MessageScopeWrapper.cs
+++ b/src/SlimMessageBus.Host/Consumer/MessageScopeWrapper.cs
@@ -1,5 +1,9 @@
namespace SlimMessageBus.Host.Consumer;
+///
+/// Used by consumers to wrap the message processing in a message scope (MSDI).
+/// The is being adjusted as part of this wrapper.
+///
public sealed class MessageScopeWrapper : IMessageScope
{
private readonly ILogger _logger;
diff --git a/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs b/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs
index 5db30fe7..ac3edf82 100644
--- a/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs
+++ b/src/SlimMessageBus.Host/DependencyResolver/ServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
using System.Reflection;
+using SlimMessageBus.Host.Consumer;
using SlimMessageBus.Host.Hybrid;
public static class ServiceCollectionExtensions
@@ -95,6 +96,8 @@ public static IServiceCollection AddSlimMessageBus(this IServiceCollection servi
services.TryAddSingleton();
services.TryAddEnumerable(ServiceDescriptor.Singleton());
+ services.TryAddSingleton();
+
services.AddHostedService();
return services;
diff --git a/src/Tests/SlimMessageBus.Host.Integration.Test/MessageBusCurrent/MessageBusCurrentTests.cs b/src/Tests/SlimMessageBus.Host.Integration.Test/MessageBusCurrent/MessageBusCurrentTests.cs
index aaf2d874..c04f67e4 100644
--- a/src/Tests/SlimMessageBus.Host.Integration.Test/MessageBusCurrent/MessageBusCurrentTests.cs
+++ b/src/Tests/SlimMessageBus.Host.Integration.Test/MessageBusCurrent/MessageBusCurrentTests.cs
@@ -3,13 +3,13 @@
using SlimMessageBus.Host;
using SlimMessageBus.Host.Memory;
+///
+/// This test verifies that the MessageBus.Current accessor works correctly and looks up in the current message scope.
+///
+///
[Trait("Category", "Integration")]
-public class MessageBusCurrentTests : BaseIntegrationTest
+public class MessageBusCurrentTests(ITestOutputHelper testOutputHelper) : BaseIntegrationTest(testOutputHelper)
{
- public MessageBusCurrentTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
- {
- }
-
protected override void SetupServices(ServiceCollection services, IConfigurationRoot configuration)
{
services.AddSlimMessageBus(mbb =>
@@ -42,37 +42,36 @@ public async Task Given_MemoryConsumer_When_MessageBusCurrentCalledInsideConsume
var valueHolder = scope.ServiceProvider.GetRequiredService();
valueHolder.Value.Should().Be(value);
}
-}
-
-public record SetValueCommand(Guid Value);
+ public record SetValueCommand(Guid Value);
-public class SetValueCommandHandler : IRequestHandler
-{
- public async Task OnHandle(SetValueCommand request)
+ public class SetValueCommandHandler : IRequestHandler
{
- // Some other logic here ...
+ public async Task OnHandle(SetValueCommand request)
+ {
+ // Some other logic here ...
- // and then notify about the value change using the MessageBus.Current accessor which should look up in the current message scope
- await MessageBus.Current.Publish(new ValueChangedEvent(request.Value));
+ // and then notify about the value change using the MessageBus.Current accessor which should look up in the current message scope
+ await MessageBus.Current.Publish(new ValueChangedEvent(request.Value));
+ }
}
-}
-public record ValueChangedEvent(Guid Value);
+ public record ValueChangedEvent(Guid Value);
-public class ValueChangedEventHandler(ValueHolder valueHolder) : IRequestHandler
-{
- public Task OnHandle(ValueChangedEvent request)
+ public class ValueChangedEventHandler(ValueHolder valueHolder) : IRequestHandler
{
- valueHolder.Value = request.Value;
- return Task.CompletedTask;
+ public Task OnHandle(ValueChangedEvent request)
+ {
+ valueHolder.Value = request.Value;
+ return Task.CompletedTask;
+ }
}
-}
-///
-/// Holds the value (per scope lifetime).
-///
-public class ValueHolder
-{
- public Guid Value { get; set; }
+ ///
+ /// Holds the value (per scope lifetime).
+ ///
+ public class ValueHolder
+ {
+ public Guid Value { get; set; }
+ }
}
diff --git a/src/Tests/SlimMessageBus.Host.Integration.Test/MessageScopeAccessor/MessageScopeAccessorTests.cs b/src/Tests/SlimMessageBus.Host.Integration.Test/MessageScopeAccessor/MessageScopeAccessorTests.cs
new file mode 100644
index 00000000..6db21509
--- /dev/null
+++ b/src/Tests/SlimMessageBus.Host.Integration.Test/MessageScopeAccessor/MessageScopeAccessorTests.cs
@@ -0,0 +1,66 @@
+namespace SlimMessageBus.Host.Integration.Test.MessageScopeAccessor;
+
+using SlimMessageBus.Host;
+using SlimMessageBus.Host.Consumer;
+using SlimMessageBus.Host.Memory;
+
+///
+/// This test verifies that the correctly looks up the for the current message scope.
+///
+///
+[Trait("Category", "Integration")]
+public class MessageScopeAccessorTests(ITestOutputHelper testOutputHelper)
+ : BaseIntegrationTest(testOutputHelper)
+{
+ protected override void SetupServices(ServiceCollection services, IConfigurationRoot configuration)
+ {
+ services.AddSlimMessageBus(mbb =>
+ {
+ mbb.AddChildBus("Memory", builder =>
+ {
+ builder
+ .WithProviderMemory()
+ .AutoDeclareFrom(Assembly.GetExecutingAssembly(), t => t.Namespace.Contains("MessageScopeAccessor"))
+ .PerMessageScopeEnabled();
+ });
+ mbb.AddServicesFromAssemblyContaining();
+ });
+ services.AddScoped();
+ }
+
+ [Fact]
+ public async Task Given_MemoryConsumer_When_MessageScopeAccessorCalledInsideConsumer_Then_LooksUpInTheMessageScope()
+ {
+ // Arrange
+ using var scope = ServiceProvider.CreateScope();
+ var bus = scope.ServiceProvider.GetRequiredService();
+
+ var value = Guid.NewGuid();
+
+ // Act
+ await bus.Publish(new TestMessage(value));
+
+ // Assert
+ var holder = scope.ServiceProvider.GetRequiredService();
+ holder.ServiceProvider.Should().BeSameAs(holder.MessageScopeAccessorServiceProvider);
+ }
+
+ public record TestMessage(Guid Value);
+
+ public class TestMessageConsumer(TestValueHolder holder, IServiceProvider serviceProvider, IMessageScopeAccessor messageScopeAccessor) : IRequestHandler
+ {
+ public Task OnHandle(TestMessage request)
+ {
+ holder.ServiceProvider = serviceProvider;
+ holder.MessageScopeAccessorServiceProvider = messageScopeAccessor.Current;
+ return Task.CompletedTask;
+ }
+ }
+
+ public class TestValueHolder
+ {
+ public IServiceProvider ServiceProvider { get; set; }
+ public IServiceProvider MessageScopeAccessorServiceProvider { get; set; }
+ }
+
+}