Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Mocha/Mocha.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<Project Path="src/Mocha.Inbox/Mocha.Inbox.csproj" />
<Project Path="src/Mocha.Outbox/Mocha.Outbox.csproj" />
<Project Path="src/Mocha.Threading/Mocha.Threading.csproj" />
<Project Path="src/Mocha.Mediator/Mocha.Mediator.csproj" />
<Project Path="src/Mocha.Analyzers/Mocha.Analyzers.csproj" />
<Project Path="src/Mocha.Transport.InMemory/Mocha.Transport.InMemory.csproj" />
<Project Path="src/Mocha.Transport.RabbitMQ/Mocha.Transport.RabbitMQ.csproj" />
<Project Path="src/Mocha.Transport.Postgres/Mocha.Transport.Postgres.csproj" />
Expand Down
3 changes: 3 additions & 0 deletions src/Mocha/benchmarks/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'debug'">
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0" />
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.15.0" />
Expand Down
2 changes: 1 addition & 1 deletion src/Mocha/benchmarks/Mocha.Mediator.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
var config = DefaultConfig.Instance
.AddJob(Job.Default.WithToolchain(
CsProjCoreToolchain.From(
new NetCoreAppSettings("net9.0", null, ".NET 9.0"))));
new NetCoreAppSettings("net10.0", null, ".NET 10.0"))));

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
Original file line number Diff line number Diff line change
Expand Up @@ -42,112 +42,77 @@ public void WriteBeginRegistrationMethod()
Writer.DecreaseIndent();
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();

Writer.WriteIndentedLine("var services = builder.Services;");
Writer.WriteIndentedLine("var lifetime = builder.Options.ServiceLifetime;");
}

public void WriteHandlerRegistration(HandlerInfo handler)
{
switch (handler.Kind)
{
case HandlerKind.CommandVoid:
WriteServiceDescriptor(
"global::Mocha.Mediator.ICommandHandler<{0}>",
handler.HandlerTypeName,
handler.MessageTypeName);
break;
case HandlerKind.CommandResponse:
WriteServiceDescriptor(
"global::Mocha.Mediator.ICommandHandler<{0}, {1}>",
handler.HandlerTypeName,
handler.MessageTypeName,
handler.ResponseTypeName!);
break;
case HandlerKind.Query:
WriteServiceDescriptor(
"global::Mocha.Mediator.IQueryHandler<{0}, {1}>",
handler.HandlerTypeName,
handler.MessageTypeName,
handler.ResponseTypeName!);
break;
}
}

public void WriteNotificationHandlerRegistration(NotificationHandlerInfo handler)
{
// Use TryAddEnumerable to prevent duplicate handler registrations
// when the generated Add{Module} extension is called more than once
// (e.g. in tests or modular startup).
Writer.WriteIndentedLine(
"global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddEnumerable(services, new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor(typeof(global::Mocha.Mediator.INotificationHandler<{0}>), typeof({1}), lifetime));",
handler.NotificationTypeName,
handler.HandlerTypeName);
}

/// <summary>
/// Writes the opening of a ConfigureMediator lambda for deferred pipeline registrations.
/// </summary>
public void WriteBeginConfigureMediator()
{
Writer.WriteIndentedLine("global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator(builder, static b =>");
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();
}

/// <summary>
/// Writes the closing of a ConfigureMediator lambda.
/// </summary>
public void WriteEndConfigureMediator()
{
Writer.DecreaseIndent();
Writer.WriteIndentedLine("});");
}

/// <summary>
/// Writes a pipeline registration for a handler (inside ConfigureMediator lambda, using 'b').
/// Writes an AddHandlerConfiguration call for a command/query handler.
/// </summary>
public void WritePipelineRegistration(HandlerInfo handler)
public void WriteHandlerConfiguration(HandlerInfo handler)
{
var (terminalMethod, responseType) = handler.Kind switch
var (kindEnum, terminalCall) = handler.Kind switch
{
HandlerKind.CommandVoid => ($"BuildVoidCommandTerminal<{handler.MessageTypeName}>()", null),
HandlerKind.CommandResponse => ($"BuildCommandTerminal<{handler.MessageTypeName}, {handler.ResponseTypeName}>()", handler.ResponseTypeName),
HandlerKind.Query => ($"BuildQueryTerminal<{handler.MessageTypeName}, {handler.ResponseTypeName}>()", handler.ResponseTypeName),
HandlerKind.Command => (
"Command",
$"BuildCommandPipeline<{handler.HandlerTypeName}, {handler.MessageTypeName}>()"),
HandlerKind.CommandResponse => (
"CommandResponse",
$"BuildCommandResponsePipeline<{handler.HandlerTypeName}, {handler.MessageTypeName}, {handler.ResponseTypeName}>()"),
HandlerKind.Query => (
"Query",
$"BuildQueryPipeline<{handler.HandlerTypeName}, {handler.MessageTypeName}, {handler.ResponseTypeName}>()"),
_ => throw new ArgumentOutOfRangeException()
};

WritePipelineConfiguration(handler.MessageTypeName, responseType, terminalMethod);
WriteAddHandlerConfiguration(
handler.HandlerTypeName,
handler.MessageTypeName,
handler.ResponseTypeName,
kindEnum,
$"global::Mocha.Mediator.PipelineBuilder.{terminalCall}");
}

/// <summary>
/// Writes a pipeline registration for a notification group (inside ConfigureMediator lambda, using 'b').
/// Writes an AddHandlerConfiguration call for a notification handler.
/// </summary>
public void WriteNotificationPipelineRegistration(string notificationType,
List<NotificationHandlerInfo> groupHandlers)
public void WriteNotificationHandlerConfiguration(
string notificationType,
NotificationHandlerInfo handler)
{
var handlerTypeArgs = string.Join(", ",
groupHandlers.Select(h => $"typeof({h.HandlerTypeName})"));

var terminalMethod = $"BuildNotificationTerminal<{notificationType}>(new global::System.Type[] {{ {handlerTypeArgs} }})";
WritePipelineConfiguration(notificationType, null, terminalMethod);
WriteAddHandlerConfiguration(
handler.HandlerTypeName,
notificationType,
responseTypeName: null,
"Notification",
$"global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline<{handler.HandlerTypeName}, {notificationType}>()");
}

private void WritePipelineConfiguration(string messageTypeName, string? responseTypeName, string terminalMethod)
private void WriteAddHandlerConfiguration(
string handlerTypeName,
string messageTypeName,
string? responseTypeName,
string kindEnum,
string terminalCall)
{
Writer.WriteIndentedLine("b.RegisterPipeline(new global::Mocha.Mediator.MediatorPipelineConfiguration");
Writer.WriteIndentedLine(
"global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration<{0}>(builder,",
handlerTypeName);
Writer.IncreaseIndent();
Writer.WriteIndentedLine("new global::Mocha.Mediator.MediatorHandlerConfiguration");
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();
Writer.WriteIndentedLine("HandlerType = typeof({0}),", handlerTypeName);
Writer.WriteIndentedLine("MessageType = typeof({0}),", messageTypeName);

if (responseTypeName is not null)
{
Writer.WriteIndentedLine("ResponseType = typeof({0}),", responseTypeName);
}

Writer.WriteIndentedLine("Terminal = global::Mocha.Mediator.PipelineBuilder.{0}", terminalMethod);
Writer.WriteIndentedLine("Kind = global::Mocha.Mediator.MediatorHandlerKind.{0},", kindEnum);
Writer.WriteIndentedLine("Delegate = {0}", terminalCall);
Writer.DecreaseIndent();
Writer.WriteIndentedLine("});");
Writer.DecreaseIndent();
}

public void WriteSectionComment(string comment)
Expand All @@ -164,17 +129,6 @@ public void WriteEndRegistrationMethod()
Writer.WriteIndentedLine("}");
}

private void WriteServiceDescriptor(string serviceTypeFormat, string implementationType, params object[] typeArgs)
{
// Use TryAdd to prevent duplicate handler registrations
// when the generated Add{Module} extension is called more than once.
var serviceType = string.Format(serviceTypeFormat, typeArgs);
Writer.WriteIndentedLine(
"global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd(services, new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor(typeof({0}), typeof({1}), lifetime));",
serviceType,
implementationType);
}

#pragma warning disable CA5351 // MD5 is used for non-security hashing (file name salting)
private static readonly MD5 s_md5 = MD5.Create();
#pragma warning restore CA5351
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,57 +37,33 @@ public void Generate(

using var builder = new DependencyInjectionFileBuilder(moduleName, assemblyName);

builder.WriteHeader();
builder.WriteBeginNamespace();
builder.WriteBeginClass();
builder.WriteBeginRegistrationMethod();

// Register handlers
if (handlers.Count > 0)
{
builder.WriteSectionComment("Register handlers");

foreach (var handler in handlers)
{
builder.WriteHandlerRegistration(handler);
}
}

// Register notification handlers
if (notificationHandlers.Count > 0)
{
builder.WriteSectionComment("Register notification handlers");

foreach (var handler in notificationHandlers)
{
builder.WriteNotificationHandlerRegistration(handler);
}
}

// Register pipelines (all handlers + notifications) via deferred ConfigureMediator
var notificationGroups = notificationHandlers
.GroupBy(h => h.NotificationTypeName)
.OrderBy(g => g.Key)
.ToList();

builder.WriteHeader();
builder.WriteBeginNamespace();
builder.WriteBeginClass();
builder.WriteBeginRegistrationMethod();

// Register all handler configurations
if (handlers.Count > 0 || notificationGroups.Count > 0)
{
builder.WriteSectionComment("Register pipelines");
builder.WriteBeginConfigureMediator();
builder.WriteSectionComment("Register handler configurations");

foreach (var handler in handlers)
{
builder.WritePipelineRegistration(handler);
builder.WriteHandlerConfiguration(handler);
}

foreach (var group in notificationGroups)
{
builder.WriteNotificationPipelineRegistration(
group.Key,
group.OrderBy(h => h.HandlerTypeName).ToList());
foreach (var handler in group.OrderBy(h => h.HandlerTypeName))
{
builder.WriteNotificationHandlerConfiguration(group.Key, handler);
}
}

builder.WriteEndConfigureMediator();
}

builder.WriteEndRegistrationMethod();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public sealed class HandlerInspector : ISyntaxInspector

private static readonly HandlerKindDescriptor[] s_handlerKinds =
[
new(static s => s.ICommandHandlerVoid, HandlerKind.CommandVoid, HasResponse: false),
new(static s => s.ICommandHandlerVoid, HandlerKind.Command, HasResponse: false),
new(static s => s.ICommandHandlerResponse, HandlerKind.CommandResponse, HasResponse: true),
new(static s => s.IQueryHandler, HandlerKind.Query, HasResponse: true)
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public bool TryHandle(
return true;
}

syntaxInfo = new MessageTypeInfo(typeName, typeNamespace, MessageKind.CommandVoid, location);
syntaxInfo = new MessageTypeInfo(typeName, typeNamespace, MessageKind.Command, location);
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Mocha/src/Mocha.Analyzers/Models/HandlerKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public enum HandlerKind
/// <summary>
/// A command handler that returns no response.
/// </summary>
CommandVoid,
Command,

/// <summary>
/// A command handler that returns a response.
Expand Down
2 changes: 1 addition & 1 deletion src/Mocha/src/Mocha.Analyzers/Models/MessageKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public enum MessageKind
/// <summary>
/// A void command (no response).
/// </summary>
CommandVoid,
Command,

/// <summary>
/// A command that returns a response.
Expand Down
24 changes: 0 additions & 24 deletions src/Mocha/src/Mocha.Mediator.Abstractions/INotificationStrategy.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Mocha.Features;

namespace Mocha.Mediator;

/// <summary>
/// Base class for mediator configuration objects that support feature-based extensibility through
/// <see cref="IFeatureCollection"/>.
/// </summary>
public abstract class MediatorConfiguration : IFeatureProvider
{
private IFeatureCollection? _features;

/// <summary>
/// Get access to context data that are copied to the type
/// and can be used for customizations.
/// </summary>
public virtual IFeatureCollection Features => _features ??= new FeatureCollection();

/// <summary>
/// Get access to features that are copied to the type
/// and can be used for customizations.
/// </summary>
public IFeatureCollection GetFeatures() => _features ?? FeatureCollection.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace Mocha.Mediator;

/// <summary>
/// Holds the resolved configuration for a mediator handler, including its type metadata,
/// handler kind, and an optional pre-built terminal delegate.
/// </summary>
public class MediatorHandlerConfiguration : MediatorConfiguration
{
/// <summary>
/// Gets or sets the concrete handler implementation type.
/// </summary>
public Type? HandlerType { get; set; }

/// <summary>
/// Gets or sets the message type (command, query, or notification type).
/// </summary>
public Type? MessageType { get; set; }

/// <summary>
/// Gets or sets the response type, or null for void commands and notifications.
/// </summary>
public Type? ResponseType { get; set; }

/// <summary>
/// Gets or sets the kind of handler.
/// </summary>
public MediatorHandlerKind Kind { get; set; }

/// <summary>
/// Gets or sets an optional pre-built delegate. When set, the builder uses this
/// directly (AOT-safe, source-generator path). When null, the builder creates the delegate
/// via reflection (manual AddHandler path).
/// This is the innermost delegate that resolves and invokes the handler.
/// It is wrapped in middleware during pipeline compilation.
/// </summary>
public MediatorDelegate? Delegate { get; set; }
}
Loading
Loading