Skip to content

Commit 1275c40

Browse files
committed
More progress
1 parent d4b91f4 commit 1275c40

File tree

6 files changed

+110
-45
lines changed

6 files changed

+110
-45
lines changed

src/Foundatio.Mediator.SourceGenerator/HandlerGenerator.cs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ private static void GenerateHandleMethod(IndentedStringBuilder source, HandlerIn
9292
string stronglyTypedMethodName = GetHandlerMethodName(handler);
9393

9494
string asyncModifier = handler.IsAsync ? "async " : "";
95-
string result, accessor, parameters;
95+
string result, accessor, parameters, defaultValue;
9696
string returnType = handler.ReturnType.FullName;
9797

9898
var variables = new Dictionary<string, string>();
9999

100100
var beforeMiddleware = handler.Middleware.Where(m => m.BeforeMethod != null).Select(m => (Method: m.BeforeMethod!.Value, Middleware: m)).ToList();
101-
var afterMiddleware = handler.Middleware.Where(m => m.AfterMethod != null).Select(m => (Method: m.AfterMethod!.Value, Middleware: m)).ToList();
102-
var finallyMiddleware = handler.Middleware.Where(m => m.FinallyMethod != null).Select(m => (Method: m.FinallyMethod!.Value, Middleware: m)).ToList();
101+
var afterMiddleware = handler.Middleware.Where(m => m.AfterMethod != null).Reverse().Select(m => (Method: m.AfterMethod!.Value, Middleware: m)).ToList();
102+
var finallyMiddleware = handler.Middleware.Where(m => m.FinallyMethod != null).Reverse().Select(m => (Method: m.FinallyMethod!.Value, Middleware: m)).ToList();
103103

104104
var shouldUseTryCatch = finallyMiddleware.Any();
105105

@@ -124,38 +124,44 @@ private static void GenerateHandleMethod(IndentedStringBuilder source, HandlerIn
124124
// build middleware instances
125125
foreach (var m in handler.Middleware.Where(m => m.IsStatic == false))
126126
{
127-
source.AppendLine($"var middleware{m.Identifier} = global::Foundatio.Mediator.Mediator.GetOrCreateMiddleware<{m.FullName}>(serviceProvider);");
127+
source.AppendLine($"var {m.Identifier.ToCamelCase()} = global::Foundatio.Mediator.Mediator.GetOrCreateMiddleware<{m.FullName}>(serviceProvider);");
128128
}
129129

130130
source.AppendLine();
131131

132132
// build result variables for before methods
133133
foreach (var m in beforeMiddleware.Where(m => m.Method.HasReturnValue))
134134
{
135-
var defaultValue = m.Method.ReturnType.IsNullable ? "null" : "default";
136-
source.AppendLine($"global::{m.Method.ReturnType.FullName} result{m.Middleware.Identifier} = {defaultValue};");
135+
bool allowNull = m.Method.ReturnType.IsNullable || m.Method.ReturnType.IsReferenceType;
136+
defaultValue = allowNull ? "null" : "default";
137+
var prefix = m.Method.ReturnType.IsTuple ? "" : "global::";
138+
source.AppendLine($"{prefix}{m.Method.ReturnType.FullName}{(allowNull ? "?" : "")} {m.Middleware.Identifier.ToCamelCase()}Result = {defaultValue};");
137139
}
138140

139141
source.AppendLine();
142+
defaultValue = handler.ReturnType.IsNullable ? "null" : "default";
143+
source.AppendLine($"{handler.ReturnType.UnwrappedFullName} handlerResult = {defaultValue};");
140144

141145
if (shouldUseTryCatch)
142146
{
143147
source.AppendLine("""
144-
global::System.Exception? exception = null;");
148+
global::System.Exception? exception = null;
145149
146150
try
147151
{
148152
""");
149153

154+
variables["System.Exception"] = "exception";
155+
150156
source.IncrementIndent();
151157
}
152158

153159
// call before middleware
154160
foreach (var m in beforeMiddleware)
155161
{
156162
asyncModifier = m.Middleware.IsAsync ? "await " : "";
157-
result = m.Method.ReturnType.IsVoid ? "" : $"result{m.Middleware.Identifier} = ";
158-
accessor = m.Middleware.IsStatic ? m.Middleware.FullName : $"middleware{m.Middleware.Identifier}";
163+
result = m.Method.ReturnType.IsVoid ? "" : $"{m.Middleware.Identifier.ToCamelCase()}Result = ";
164+
accessor = m.Middleware.IsStatic ? m.Middleware.FullName : $"{m.Middleware.Identifier.ToCamelCase()}";
159165
parameters = BuildParameters(m.Method.Parameters);
160166

161167
source.AppendLine($"{result}{asyncModifier}{accessor}.{m.Method.MethodName}({parameters});");
@@ -164,24 +170,27 @@ private static void GenerateHandleMethod(IndentedStringBuilder source, HandlerIn
164170

165171
// call handler
166172
asyncModifier = handler.IsAsync ? "await " : "";
167-
result = handler.ReturnType.IsVoid ? "" : $"var handlerResult = ";
173+
result = handler.ReturnType.IsVoid ? "" : shouldUseTryCatch ? "handlerResult = " : "return ";
168174
accessor = handler.IsStatic ? handler.FullName : $"handlerInstance";
169175
parameters = BuildParameters(handler.Parameters);
176+
170177
source.AppendLineIf("var handlerInstance = GetOrCreateHandler(serviceProvider);", !handler.IsStatic);
171178
source.AppendLine($"{result}{asyncModifier}{accessor}.{handler.MethodName}({parameters});");
172-
source.AppendLine();
179+
source.AppendLineIf(handler.HasReturnValue);
173180

174181
// call after middleware
175182
foreach (var m in afterMiddleware)
176183
{
177184
asyncModifier = m.Middleware.IsAsync ? "await " : "";
178-
accessor = m.Middleware.IsStatic ? m.Middleware.FullName : $"middleware{m.Middleware.Identifier}";
179-
parameters = BuildParameters(m.Method.Parameters);
185+
accessor = m.Middleware.IsStatic ? m.Middleware.FullName : $"{m.Middleware.Identifier.ToCamelCase()}";
186+
parameters = BuildParameters(m.Method.Parameters, variables);
180187

181188
source.AppendLine($"{asyncModifier}{accessor}.{m.Method.MethodName}({parameters});");
182189
}
183190
source.AppendLineIf(afterMiddleware.Any());
184191

192+
source.AppendLineIf("return handlerResult;", handler.HasReturnValue);
193+
185194
if (shouldUseTryCatch)
186195
{
187196
source.DecrementIndent();
@@ -203,30 +212,23 @@ private static void GenerateHandleMethod(IndentedStringBuilder source, HandlerIn
203212
foreach (var m in finallyMiddleware)
204213
{
205214
asyncModifier = m.Method.IsAsync ? "await " : "";
206-
accessor = m.Method.IsStatic ? m.Middleware.FullName : $"middleware{m.Middleware.Identifier}";
207-
parameters = BuildParameters(m.Method.Parameters);
215+
accessor = m.Method.IsStatic ? m.Middleware.FullName : $"{m.Middleware.Identifier.ToCamelCase()}";
216+
parameters = BuildParameters(m.Method.Parameters, variables);
208217

209218
source.AppendLine($"{asyncModifier}{accessor}.{m.Method.MethodName}({parameters});");
210219
}
211220

212221
source.DecrementIndent();
213222

214223
source.AppendLine("}");
215-
source.AppendLine();
216-
217-
source.DecrementIndent();
218-
}
219-
220-
if (handler.HasReturnValue)
221-
{
222-
source.AppendLine("return handlerResult;");
223224
}
224225

226+
source.DecrementIndent();
225227
source.AppendLine("}");
226228
source.AppendLine();
227229
}
228230

229-
private static string BuildParameters(EquatableArray<ParameterInfo> parameters)
231+
private static string BuildParameters(EquatableArray<ParameterInfo> parameters, Dictionary<string, string>? variables = null)
230232
{
231233
var parameterValues = new List<string>();
232234

@@ -236,14 +238,25 @@ private static string BuildParameters(EquatableArray<ParameterInfo> parameters)
236238
{
237239
parameterValues.Add("message");
238240
}
241+
else if (param.Type.IsObject && param.Name == "handlerResult")
242+
{
243+
parameterValues.Add("handlerResult");
244+
}
239245
else if (param.Type.IsCancellationToken)
240246
{
241247
parameterValues.Add("cancellationToken");
242248
}
249+
else if (variables != null && variables.TryGetValue(param.Type.FullName, out string? variableName))
250+
{
251+
parameterValues.Add(variableName);
252+
}
253+
else if (variables != null && variables.TryGetValue(param.Type.UnwrappedFullName, out string? unwrappedVariableName))
254+
{
255+
parameterValues.Add(unwrappedVariableName);
256+
}
243257
else
244258
{
245-
// This is a dependency that needs to be resolved from DI
246-
parameterValues.Add($"serviceProvider.GetRequiredService<{param.Type.FullName}>()");
259+
parameterValues.Add($"serviceProvider.GetRequiredService<global::{param.Type.FullName}>()");
247260
}
248261
}
249262

src/Foundatio.Mediator.SourceGenerator/Models/TypeSymbolInfo.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ internal readonly record struct TypeSymbolInfo
2121
/// </summary>
2222
public bool IsNullable { get; init; }
2323
/// <summary>
24+
/// Indicates if the type is a reference type, which means it is not a value type and can be null.
25+
/// </summary>
26+
public bool IsReferenceType { get; init; }
27+
/// <summary>
2428
/// Indicates if the type is a Result type, which is a wrapper around a value that can be null or an error.
2529
/// </summary>
2630
public bool IsResult { get; init; }
@@ -65,6 +69,7 @@ public static TypeSymbolInfo From(ITypeSymbol typeSymbol, Compilation compilatio
6569
bool isTask = typeSymbol.IsTask(compilation);
6670
var unwrappedType = typeSymbol.UnwrapTask(compilation);
6771
bool isNullable = unwrappedType.IsNullable(compilation);
72+
bool isReferenceType = unwrappedType.IsReferenceType;
6873
var unwrappedNullableType = unwrappedType.UnwrapNullable(compilation);
6974

7075
// void or Task or ValueTask
@@ -84,6 +89,7 @@ public static TypeSymbolInfo From(ITypeSymbol typeSymbol, Compilation compilatio
8489
FullName = typeSymbol.ToDisplayString(),
8590
UnwrappedFullName = unwrappedType.ToDisplayString(),
8691
IsNullable = isNullable,
92+
IsReferenceType = isReferenceType,
8793
IsResult = isResult,
8894
IsVoid = isVoid,
8995
IsTask = isTask,

src/Foundatio.Mediator.SourceGenerator/Utility/Helpers.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,12 @@ public static string ToIdentifier(this string name)
2020

2121
return new String(name.Select(c => char.IsLetterOrDigit(c) || c == '_' ? c : '_').ToArray());
2222
}
23+
24+
public static string ToCamelCase(this string name)
25+
{
26+
if (string.IsNullOrEmpty(name))
27+
return String.Empty;
28+
29+
return char.ToLower(name[0]) + name.Substring(1);
30+
}
2331
}

src/Foundatio.Mediator.SourceGenerator/Utility/IndentedStringBuilder.cs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,40 @@ public virtual IndentedStringBuilder AppendLine()
180180
/// <returns>This builder so that additional calls can be chained.</returns>
181181
public virtual IndentedStringBuilder AppendLine(string value)
182182
{
183-
if (value.Length != 0)
184-
DoIndent();
183+
if (value.Length == 0)
184+
{
185+
_stringBuilder.AppendLine();
186+
_indentPending = true;
187+
return this;
188+
}
189+
190+
// Use StringReader to properly handle all line ending types
191+
using var reader = new StringReader(value);
192+
bool isFirstLine = true;
193+
194+
while (reader.ReadLine() is { } line)
195+
{
196+
if (!isFirstLine)
197+
{
198+
_stringBuilder.AppendLine();
199+
_indentPending = true;
200+
}
185201

186-
_stringBuilder.AppendLine(value);
202+
if (line.Length > 0)
203+
{
204+
DoIndent();
205+
_stringBuilder.Append(line);
206+
}
207+
else
208+
{
209+
// Empty line - just ensure indentation state is correct
210+
_indentPending = true;
211+
}
212+
213+
isFirstLine = false;
214+
}
187215

216+
_stringBuilder.AppendLine();
188217
_indentPending = true;
189218

190219
return this;

src/Foundatio.Mediator/HandlerRegistration.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class HandlerRegistration
1212
/// <param name="handleAsync">The delegate to handle the message asynchronously</param>
1313
/// <param name="handle">The delegate to handle the message synchronously (null for async-only handlers)</param>
1414
/// <param name="isAsync">Whether the handler supports async operations</param>
15-
public HandlerRegistration(string messageTypeName, Func<IMediator, object, CancellationToken, Type?, ValueTask<object?>> handleAsync, Func<IMediator, object, CancellationToken, Type?, object?>? handle, bool isAsync)
15+
public HandlerRegistration(string messageTypeName, HandleAsyncDelegate handleAsync, HandleDelegate? handle, bool isAsync)
1616
{
1717
MessageTypeName = messageTypeName;
1818
HandleAsync = handleAsync;
@@ -28,15 +28,18 @@ public HandlerRegistration(string messageTypeName, Func<IMediator, object, Cance
2828
/// <summary>
2929
/// The delegate to handle the message
3030
/// </summary>
31-
public Func<IMediator, object, CancellationToken, Type?, ValueTask<object?>> HandleAsync { get; }
31+
public HandleAsyncDelegate HandleAsync { get; }
3232

3333
/// <summary>
3434
/// The delegate to handle the message synchronously (null for async-only handlers)
3535
/// </summary>
36-
public Func<IMediator, object, CancellationToken, Type?, object?>? Handle { get; }
36+
public HandleDelegate? Handle { get; }
3737

3838
/// <summary>
3939
/// Whether the handler supports async operations
4040
/// </summary>
4141
public bool IsAsync { get; }
4242
}
43+
44+
public delegate ValueTask<object?> HandleAsyncDelegate(IMediator mediator, object message, CancellationToken cancellationToken, Type? returnType);
45+
public delegate object? HandleDelegate(IMediator mediator, object message, CancellationToken cancellationToken, Type? returnType);

src/Foundatio.Mediator/Mediator.cs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
1-
#nullable enable
2-
31
using System.Collections.Concurrent;
42
using System.Diagnostics;
5-
using System.Diagnostics.CodeAnalysis;
63
using Microsoft.Extensions.DependencyInjection;
74

85
namespace Foundatio.Mediator;
96

10-
[ExcludeFromCodeCoverage]
117
public class Mediator : IMediator, IServiceProvider
128
{
139
private readonly IServiceProvider _serviceProvider;
1410

15-
private static readonly ConcurrentDictionary<Type, Func<IMediator, object, CancellationToken, ValueTask>> _invokeAsyncCache = new();
16-
private static readonly ConcurrentDictionary<Type, Func<IMediator, object, CancellationToken, object?>> _invokeCache = new();
17-
private static readonly ConcurrentDictionary<(Type MessageType, Type ResponseType), Func<IMediator, object, CancellationToken, ValueTask<object?>>> _invokeAsyncWithResponseCache = new();
18-
private static readonly ConcurrentDictionary<(Type MessageType, Type ResponseType), Func<IMediator, object, CancellationToken, object?>> _invokeWithResponseCache = new();
19-
private static readonly ConcurrentDictionary<Type, Func<IMediator, object, CancellationToken, ValueTask<object?>>[]> _publishCache = new();
20-
2111
[DebuggerStepThrough]
2212
public Mediator(IServiceProvider serviceProvider)
2313
{
@@ -28,7 +18,7 @@ public Mediator(IServiceProvider serviceProvider)
2818
public object? GetService(Type serviceType) => _serviceProvider.GetService(serviceType);
2919

3020
[DebuggerStepThrough]
31-
private Func<IMediator, object, CancellationToken, ValueTask<object?>>[] GetAllApplicableHandlers(object message)
21+
private PublishAsyncDelegate[] GetAllApplicableHandlers(object message)
3222
{
3323
var messageType = message.GetType();
3424

@@ -57,7 +47,7 @@ public Mediator(IServiceProvider serviceProvider)
5747
}
5848

5949
return allHandlers.Distinct()
60-
.Select<HandlerRegistration, Func<IMediator, object, CancellationToken, ValueTask<object?>>>(h => (mediator, message, cancellationToken) => h.HandleAsync(mediator, message, cancellationToken, null)).ToArray();
50+
.Select<HandlerRegistration, PublishAsyncDelegate>(h => async (mediator, message, cancellationToken) => await h.HandleAsync(mediator, message, cancellationToken, null)).ToArray();
6151
});
6252
}
6353

@@ -129,7 +119,7 @@ public async ValueTask<TResponse> InvokeAsync<TResponse>(object message, Cancell
129119
throw new InvalidOperationException($"Multiple handlers found for message type {key.MessageType.FullName}. Use PublishAsync for multiple handlers.");
130120

131121
var handler = handlersList.First();
132-
return async (mediator, msg, ct) => await handler.HandleAsync(mediator, msg, ct, key.ResponseType);
122+
return (mediator, msg, ct) => handler.HandleAsync(mediator, msg, ct, key.ResponseType);
133123
});
134124

135125
var result = await cachedFunc(this, message, cancellationToken);
@@ -189,4 +179,20 @@ public static T GetOrCreateMiddleware<T>(IServiceProvider serviceProvider) where
189179
return (T)_middlewareCache.GetOrAdd(typeof(T), type =>
190180
ActivatorUtilities.CreateInstance<T>(serviceProvider));
191181
}
182+
183+
private delegate ValueTask InvokeAsyncDelegate(IMediator mediator, object message, CancellationToken cancellationToken);
184+
private static readonly ConcurrentDictionary<Type, InvokeAsyncDelegate> _invokeAsyncCache = new();
185+
186+
private delegate void InvokeDelegate(IMediator mediator, object message, CancellationToken cancellationToken);
187+
private static readonly ConcurrentDictionary<Type, InvokeDelegate> _invokeCache = new();
188+
189+
private delegate ValueTask<object?> InvokeAsyncResponseDelegate(IMediator mediator, object message, CancellationToken cancellationToken);
190+
private static readonly ConcurrentDictionary<(Type MessageType, Type ResponseType), InvokeAsyncResponseDelegate> _invokeAsyncWithResponseCache = new();
191+
192+
private delegate object? InvokeResponseDelegate(IMediator mediator, object message, CancellationToken cancellationToken);
193+
private static readonly ConcurrentDictionary<(Type MessageType, Type ResponseType), InvokeResponseDelegate> _invokeWithResponseCache = new();
194+
195+
private delegate ValueTask PublishAsyncDelegate(IMediator mediator, object message, CancellationToken cancellationToken);
196+
private static readonly ConcurrentDictionary<Type, PublishAsyncDelegate[]> _publishCache = new();
197+
192198
}

0 commit comments

Comments
 (0)