Skip to content

Commit e5bed9a

Browse files
committed
Use scoped mediator instead of handler scope
1 parent 576da48 commit e5bed9a

6 files changed

Lines changed: 262 additions & 243 deletions

File tree

src/Foundatio.Mediator.Abstractions/HandlerContext.cs

Lines changed: 0 additions & 107 deletions
This file was deleted.

src/Foundatio.Mediator.Abstractions/Mediator.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,53 @@ public ValueTask PublishAsync(object message, CancellationToken cancellationToke
5656
return _configuration.NotificationPublisher.PublishAsync(this, handlersList, message, cancellationToken);
5757
}
5858

59+
/// <summary>
60+
/// Internal method to invoke a handler with a specific mediator instance (used by ScopedMediator).
61+
/// </summary>
62+
internal ValueTask InvokeAsyncWithMediator(IMediator mediator, object message, CancellationToken cancellationToken)
63+
{
64+
var handlerFunc = GetInvokeAsyncDelegate(message.GetType());
65+
return handlerFunc(mediator, message, cancellationToken);
66+
}
67+
68+
/// <summary>
69+
/// Internal method to invoke a handler with a specific mediator instance (used by ScopedMediator).
70+
/// </summary>
71+
internal async ValueTask<TResponse> InvokeAsyncWithMediator<TResponse>(IMediator mediator, object message, CancellationToken cancellationToken)
72+
{
73+
var handlerFunc = GetInvokeAsyncResponseDelegate(message.GetType(), typeof(TResponse));
74+
object? result = await handlerFunc(mediator, message, cancellationToken);
75+
return (TResponse)result!;
76+
}
77+
78+
/// <summary>
79+
/// Internal method to invoke a handler synchronously with a specific mediator instance (used by ScopedMediator).
80+
/// </summary>
81+
internal void InvokeWithMediator(IMediator mediator, object message, CancellationToken cancellationToken)
82+
{
83+
var handlerFunc = GetInvokeDelegate(message.GetType());
84+
handlerFunc(mediator, message, cancellationToken);
85+
}
86+
87+
/// <summary>
88+
/// Internal method to invoke a handler synchronously with a specific mediator instance (used by ScopedMediator).
89+
/// </summary>
90+
internal TResponse InvokeWithMediator<TResponse>(IMediator mediator, object message, CancellationToken cancellationToken)
91+
{
92+
var handlerFunc = GetInvokeResponseDelegate(message.GetType(), typeof(TResponse));
93+
object? result = handlerFunc(mediator, message, cancellationToken);
94+
return (TResponse)result!;
95+
}
96+
97+
/// <summary>
98+
/// Internal method to publish a message with a specific mediator instance (used by ScopedMediator).
99+
/// </summary>
100+
internal ValueTask PublishAsyncWithMediator(IMediator mediator, object message, CancellationToken cancellationToken)
101+
{
102+
var handlersList = GetAllApplicableHandlers(message).ToList();
103+
return _configuration.NotificationPublisher.PublishAsync(mediator, handlersList, message, cancellationToken);
104+
}
105+
59106
public void ShowRegisteredHandlers()
60107
{
61108
var registrations = _serviceProvider.GetServices<HandlerRegistration>().ToArray();
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace Foundatio.Mediator;
4+
5+
/// <summary>
6+
/// A mediator wrapper that carries a scoped IServiceProvider through the call chain.
7+
/// This allows nested handler calls, middleware, and cascading messages to share the same DI scope
8+
/// without using AsyncLocal, providing better performance.
9+
/// Uses reference counting to avoid allocations when passed to nested handlers.
10+
/// </summary>
11+
public sealed class ScopedMediator : IMediator, IServiceProvider, IDisposable, IAsyncDisposable
12+
{
13+
private readonly IMediator _rootMediator;
14+
private readonly IServiceProvider _scopedServiceProvider;
15+
private readonly IDisposable? _scope;
16+
private readonly bool _ownsScope;
17+
private int _refCount;
18+
19+
/// <summary>
20+
/// Creates a ScopedMediator that owns and manages the provided scope.
21+
/// </summary>
22+
private ScopedMediator(IMediator rootMediator, IServiceScope scope)
23+
{
24+
_rootMediator = rootMediator is ScopedMediator sm ? sm._rootMediator : rootMediator;
25+
_scopedServiceProvider = scope.ServiceProvider;
26+
_scope = scope;
27+
_ownsScope = true;
28+
_refCount = 1;
29+
}
30+
31+
/// <summary>
32+
/// Gets the scoped service provider. Also available via IServiceProvider.GetService.
33+
/// </summary>
34+
public IServiceProvider Services => _scopedServiceProvider;
35+
36+
/// <inheritdoc />
37+
public object? GetService(Type serviceType) => _scopedServiceProvider.GetService(serviceType);
38+
39+
/// <summary>
40+
/// Adds a reference to this scoped mediator. Call Release() when done.
41+
/// </summary>
42+
private void AddRef()
43+
{
44+
System.Threading.Interlocked.Increment(ref _refCount);
45+
}
46+
47+
/// <summary>
48+
/// Gets or creates a scoped mediator. If already a ScopedMediator, increments ref count and returns the same instance.
49+
/// </summary>
50+
public static ScopedMediator GetOrCreate(IMediator mediator)
51+
{
52+
if (mediator is null) throw new ArgumentNullException(nameof(mediator));
53+
54+
if (mediator is ScopedMediator scopedMediator)
55+
{
56+
scopedMediator.AddRef();
57+
return scopedMediator;
58+
}
59+
60+
var serviceProvider = (IServiceProvider)mediator;
61+
return new ScopedMediator(mediator, serviceProvider.CreateScope());
62+
}
63+
64+
/// <summary>
65+
/// Gets or creates a scoped mediator asynchronously. If already a ScopedMediator, increments ref count and returns the same instance.
66+
/// </summary>
67+
public static ValueTask<ScopedMediator> GetOrCreateAsync(IMediator mediator)
68+
{
69+
if (mediator is null) throw new ArgumentNullException(nameof(mediator));
70+
71+
if (mediator is ScopedMediator scopedMediator)
72+
{
73+
scopedMediator.AddRef();
74+
return new ValueTask<ScopedMediator>(scopedMediator);
75+
}
76+
77+
var serviceProvider = (IServiceProvider)mediator;
78+
return new ValueTask<ScopedMediator>(new ScopedMediator(mediator, serviceProvider.CreateAsyncScope()));
79+
}
80+
81+
/// <inheritdoc />
82+
public ValueTask InvokeAsync(object message, CancellationToken cancellationToken = default)
83+
{
84+
if (_rootMediator is Mediator mediator)
85+
return mediator.InvokeAsyncWithMediator(this, message, cancellationToken);
86+
87+
return _rootMediator.InvokeAsync(message, cancellationToken);
88+
}
89+
90+
/// <inheritdoc />
91+
public ValueTask<TResponse> InvokeAsync<TResponse>(object message, CancellationToken cancellationToken = default)
92+
{
93+
if (_rootMediator is Mediator mediator)
94+
return mediator.InvokeAsyncWithMediator<TResponse>(this, message, cancellationToken);
95+
96+
return _rootMediator.InvokeAsync<TResponse>(message, cancellationToken);
97+
}
98+
99+
/// <inheritdoc />
100+
public void Invoke(object message, CancellationToken cancellationToken = default)
101+
{
102+
if (_rootMediator is Mediator mediator)
103+
{
104+
mediator.InvokeWithMediator(this, message, cancellationToken);
105+
return;
106+
}
107+
_rootMediator.Invoke(message, cancellationToken);
108+
}
109+
110+
/// <inheritdoc />
111+
public TResponse Invoke<TResponse>(object message, CancellationToken cancellationToken = default)
112+
{
113+
if (_rootMediator is Mediator mediator)
114+
return mediator.InvokeWithMediator<TResponse>(this, message, cancellationToken);
115+
116+
return _rootMediator.Invoke<TResponse>(message, cancellationToken);
117+
}
118+
119+
/// <inheritdoc />
120+
public ValueTask PublishAsync(object message, CancellationToken cancellationToken = default)
121+
{
122+
if (_rootMediator is Mediator mediator)
123+
return mediator.PublishAsyncWithMediator(this, message, cancellationToken);
124+
125+
return _rootMediator.PublishAsync(message, cancellationToken);
126+
}
127+
128+
public void Dispose()
129+
{
130+
int remaining = System.Threading.Interlocked.Decrement(ref _refCount);
131+
if (remaining == 0 && _ownsScope)
132+
_scope?.Dispose();
133+
}
134+
135+
public ValueTask DisposeAsync()
136+
{
137+
int remaining = System.Threading.Interlocked.Decrement(ref _refCount);
138+
if (remaining == 0 && _ownsScope && _scope is not null)
139+
{
140+
if (_scope is IAsyncDisposable asyncDisposable)
141+
return asyncDisposable.DisposeAsync();
142+
143+
_scope.Dispose();
144+
}
145+
return default;
146+
}
147+
}

0 commit comments

Comments
 (0)