Giter Site home page Giter Site logo

martinothamar / mediator Goto Github PK

View Code? Open in Web Editor NEW
1.9K 37.0 66.0 896 KB

A high performance implementation of Mediator pattern in .NET using source generators.

License: MIT License

C# 99.96% Just 0.04% Shell 0.01%
csharp dotnet dotnetcore dotnet-core source-generation source-gen sourcegenerator source-generators dotnet-standard csharp-sourcegenerator

mediator's Introduction

GitHub Workflow Status GitHub Downloads
Abstractions NuGet current SourceGenerator NuGet current Abstractions NuGet prerelease SourceGenerator NuGet prerelease

Mediator

Note

Version 3.0 is currently being developed. See status and provide feedback here (#98)

This is a high performance .NET implementation of the Mediator pattern using the source generators feature introduced in .NET 5. The API and usage is mostly based on the great MediatR library, with some deviations to allow for better performance. Packages are .NET Standard 2.1 compatible.

The mediator pattern is great for implementing cross cutting concern (logging, metrics, etc) and avoiding "fat" constructors due to lots of injected services.

Goals for this library

  • High performance
    • Runtime performance can be the same for both runtime reflection and source generator based approaches, but it's easier to optimize in the latter case
  • AOT friendly
    • MS are investing time in various AOT scenarios, and for example iOS requires AOT compilation
  • Build time errors instead of runtime errors
    • The generator includes diagnostics, i.e. if a handler is not defined for a request, a warning is emitted

In particular, source generators in this library is used to

  • Generate code for DI registration
  • Generate code for IMediator implementation
    • Request/Command/Query Send methods are monomorphized (1 method per T), the generic ISender.Send methods rely on these
    • You can use both IMediator and Mediator, the latter allows for better performance
  • Generate diagnostics related messages and message handlers

See this great video by @Elfocrash / Nick Chapsas, covering both similarities and differences between Mediator and MediatR

Using MediatR in .NET? Maybe replace it with this

Table of Contents

2. Benchmarks

This benchmark exposes the perf overhead of the libraries. Mediator (this library) and MediatR methods show the overhead of the respective mediator implementations. I've also included the MessagePipe library as it also has great performance.

  • <SendRequest | Stream>_Baseline: simple method call into the handler class
  • <SendRequest | Stream>_Mediator: the concrete Mediator class generated by this library
  • <SendRequest | Stream>_MessagePipe: the MessagePipe library
  • <SendRequest | Stream>_IMediator: call through the IMediator interface in this library
  • <SendRequest | Stream>_MediatR: the MediatR library

See benchmarks code for more details on the measurement.

Warning

A current limitation of this library is that performance degrades significantly for projects with a large number of messages (>500) There is ongoing work on resolving this for version 3.0 (#48).

Requests benchmark

Stream benchmark

3. Usage and abstractions

There are two NuGet packages needed to use this library

  • Mediator.SourceGenerator
    • To generate the IMediator implementation and dependency injection setup.
  • Mediator.Abstractions
    • Message types (IRequest<,>, INotification), handler types (IRequestHandler<,>, INotificationHandler<>), pipeline types (IPipelineBehavior)

You install the source generator package into your edge/outermost project (i.e. ASP.NET Core application, Background worker project), and then use the Mediator.Abstractions package wherever you define message types and handlers. Standard message handlers are automatically picked up and added to the DI container in the generated AddMediator method. Pipeline behaviors need to be added manually (including pre/post/exception behaviors).

For example implementations, see the /samples folder. See the ASP.NET Core clean architecture sample for a more real world setup.

3.1. Message types

  • IMessage - marker interface
  • IStreamMessage - marker interface
  • IBaseRequest - marker interface for requests
  • IRequest - a request message, no return value (ValueTask<Unit>)
  • IRequest<out TResponse> - a request message with a response (ValueTask<TResponse>)
  • IStreamRequest<out TResponse> - a request message with a streaming response (IAsyncEnumerable<TResponse>)
  • IBaseCommand - marker interface for commands
  • ICommand - a command message, no return value (ValueTask<Unit>)
  • ICommand<out TResponse> - a command message with a response (ValueTask<TResponse>)
  • IStreamCommand<out TResponse> - a command message with a streaming response (IAsyncEnumerable<TResponse>)
  • IBaseQuery - marker interface for queries
  • IQuery<out TResponse> - a query message with a response (ValueTask<TResponse>)
  • IStreamQuery<out TResponse> - a query message with a streaming response (IAsyncEnumerable<TResponse>)
  • INotification - a notification message, no return value (ValueTask)

As you can see, you can achieve the exact same thing with requests, commands and queries. But I find the distinction in naming useful if you for example use the CQRS pattern or for some reason have a preference on naming in your application.

3.2. Handler types

  • IRequestHandler<in TRequest>
  • IRequestHandler<in TRequest, TResponse>
  • IStreamRequestHandler<in TRequest, out TResponse>
  • ICommandHandler<in TCommand>
  • ICommandHandler<in TCommand, TResponse>
  • IStreamCommandHandler<in TCommand, out TResponse>
  • IQueryHandler<in TQuery, TResponse>
  • IStreamQueryHandler<in TQuery, out TResponse>
  • INotificationHandler<in TNotification>

These types are used in correlation with the message types above.

3.3. Pipeline types

  • IPipelineBehavior<TMessage, TResponse>
  • IStreamPipelineBehavior<TMessage, TResponse>
  • MessagePreProcessor<TMessage, TResponse>
  • MessagePostProcessor<TMessage, TResponse>
  • MessageExceptionHandler<TMessage, TResponse, TException>

3.3.1. Message validation example

// As a normal pipeline behavior
public sealed class MessageValidatorBehaviour<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
    where TMessage : IValidate
{
    public ValueTask<TResponse> Handle(
        TMessage message,
        CancellationToken cancellationToken,
        MessageHandlerDelegate<TMessage, TResponse> next
    )
    {
        if (!message.IsValid(out var validationError))
            throw new ValidationException(validationError);

        return next(message, cancellationToken);
    }
}

// Or as a pre-processor
public sealed class MessageValidatorBehaviour<TMessage, TResponse> : MessagePreProcessor<TMessage, TResponse>
    where TMessage : IValidate
{
    protected override ValueTask Handle(TMessage message, CancellationToken cancellationToken)
    {
        if (!message.IsValid(out var validationError))
            throw new ValidationException(validationError);

        return default;
    }
}

// Register as IPipelineBehavior<,> in either case
services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(MessageValidatorBehaviour<,>))

3.3.2. Error logging example

// As a normal pipeline behavior
public sealed class ErrorLoggingBehaviour<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
    where TMessage : IMessage
{
    private readonly ILogger<ErrorLoggingBehaviour<TMessage, TResponse>> _logger;

    public ErrorLoggingBehaviour(ILogger<ErrorLoggingBehaviour<TMessage, TResponse>> logger)
    {
        _logger = logger;
    }

    public async ValueTask<TResponse> Handle(
        TMessage message,
        CancellationToken cancellationToken,
        MessageHandlerDelegate<TMessage, TResponse> next
    )
    {
        try
        {
            return await next(message, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error handling message of type {messageType}", message.GetType().Name);
            throw;
        }
    }
}

// Or as an exception handler
public sealed class ErrorLoggingBehaviour<TMessage, TResponse> : MessageExceptionHandler<TMessage, TResponse>
    where TMessage : notnull, IMessage
{
    private readonly ILogger<ErrorLoggingBehaviour<TMessage, TResponse>> _logger;

    public ErrorLoggingBehaviour(ILogger<ErrorLoggingBehaviour<TMessage, TResponse>> logger)
    {
        _logger = logger;
    }

    protected override ValueTask<ExceptionHandlingResult<TResponse>> Handle(
        TMessage message,
        Exception exception,
        CancellationToken cancellationToken
    )
    {
        _logger.LogError(exception, "Error handling message of type {messageType}", message.GetType().Name);
        // Let the exception bubble up by using the base class helper NotHandled:
        return NotHandled;
        // Or if the exception is properly handled, you can just return your own response,
        // using the base class helper Handle().
        // This requires you to know something about TResponse,
        // so TResponse needs to be constrained to something,
        // typically with a static abstract member acting as a consructor on an interface or abstract class.
        return Handled(null!);
    }
}

// Register as IPipelineBehavior<,> in either case
services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(ErrorLoggingBehaviour<,>))

3.4. Configuration

There are two ways to configure Mediator. Configuration values are needed during compile-time since this is a source generator:

  • Assembly level attribute for configuration: MediatorOptionsAttribute
  • Options configuration delegate in AddMediator function.
services.AddMediator(options =>
{
    options.Namespace = "SimpleConsole.Mediator";
    options.DefaultServiceLifetime = ServiceLifetime.Transient;
});

// or

[assembly: MediatorOptions(Namespace = "SimpleConsole.Mediator", DefaultServiceLifetime = ServiceLifetime.Transient)]
  • Namespace - where the IMediator implementation is generated
  • DefaultServiceLifetime - the DI service lifetime
    • Singleton - (default value) everything registered as singletons, minimal allocations
    • Transient - mediator and handlers registered as transient
    • Scoped - mediator and handlers registered as scoped

4. Getting started

In this section we will get started with Mediator and go through a sample illustrating the various ways the Mediator pattern can be used in an application.

See the full runnable sample code in the Showcase sample.

4.1. Add package

dotnet add package Mediator.SourceGenerator --version 2.0.*
dotnet add package Mediator.Abstractions --version 2.0.*

or

<PackageReference Include="Mediator.SourceGenerator" Version="2.0.*">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Mediator.Abstractions" Version="2.0.*" />

4.2. Add Mediator to DI container

In ConfigureServices or equivalent, call AddMediator (unless MediatorOptions is configured, default namespace is Mediator). This registers your handler below.

using Mediator;
using Microsoft.Extensions.DependencyInjection;
using System;

var services = new ServiceCollection(); // Most likely IServiceCollection comes from IHostBuilder/Generic host abstraction in Microsoft.Extensions.Hosting

services.AddMediator();
var serviceProvider = services.BuildServiceProvider();

4.3. Create IRequest<> type

var mediator = serviceProvider.GetRequiredService<IMediator>();
var ping = new Ping(Guid.NewGuid());
var pong = await mediator.Send(ping);
Debug.Assert(ping.Id == pong.Id);

// ...

public sealed record Ping(Guid Id) : IRequest<Pong>;

public sealed record Pong(Guid Id);

public sealed class PingHandler : IRequestHandler<Ping, Pong>
{
    public ValueTask<Pong> Handle(Ping request, CancellationToken cancellationToken)
    {
        return new ValueTask<Pong>(new Pong(request.Id));
    }
}

As soon as you code up message types, the source generator will add DI registrations automatically (inside AddMediator). P.S - You can inspect the code yourself - open Mediator.g.cs in VS from Project -> Dependencies -> Analyzers -> Mediator.SourceGenerator -> Mediator.SourceGenerator.MediatorGenerator, or just F12 through the code.

4.4. Use pipeline behaviors

The pipeline behavior below validates all incoming Ping messages. Pipeline behaviors currently must be added manually.

services.AddMediator();
services.AddSingleton<IPipelineBehavior<Ping, Pong>, PingValidator>();

public sealed class PingValidator : IPipelineBehavior<Ping, Pong>
{
    public ValueTask<Pong> Handle(Ping request, MessageHandlerDelegate<Ping, Pong> next, CancellationToken cancellationToken)
    {
        if (request is null || request.Id == default)
            throw new ArgumentException("Invalid input");

        return next(request, cancellationToken);
    }
}

4.5. Constrain IPipelineBehavior<,> message with open generics

Add open generic handler to process all or a subset of messages passing through Mediator. This handler will log any error that is thrown from message handlers (IRequest, ICommand, IQuery). It also publishes a notification allowing notification handlers to react to errors. Message pre- and post-processors along with the exception handlers can also constrain the generic type parameters in the same way.

services.AddMediator();
services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(ErrorLoggerHandler<,>));

public sealed record ErrorMessage(Exception Exception) : INotification;
public sealed record SuccessfulMessage() : INotification;

public sealed class ErrorLoggerHandler<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
    where TMessage : IMessage // Constrained to IMessage, or constrain to IBaseCommand or any custom interface you've implemented
{
    private readonly ILogger<ErrorLoggerHandler<TMessage, TResponse>> _logger;
    private readonly IMediator _mediator;

    public ErrorLoggerHandler(ILogger<ErrorLoggerHandler<TMessage, TResponse>> logger, IMediator mediator)
    {
        _logger = logger;
        _mediator = mediator;
    }

    public async ValueTask<TResponse> Handle(TMessage message, MessageHandlerDelegate<TMessage, TResponse> next, CancellationToken cancellationToken)
    {
        try
        {
            var response = await next(message, cancellationToken);
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error handling message");
            await _mediator.Publish(new ErrorMessage(ex));
            throw;
        }
    }
}

4.6. Use notifications

We can define a notification handler to catch errors from the above pipeline behavior.

// Notification handlers are automatically added to DI container

public sealed class ErrorNotificationHandler : INotificationHandler<ErrorMessage>
{
    public ValueTask Handle(ErrorMessage error, CancellationToken cancellationToken)
    {
        // Could log to application insights or something...
        return default;
    }
}

4.7. Polymorphic dispatch with notification handlers

We can also define a notification handler that receives all notifications.

public sealed class StatsNotificationHandler : INotificationHandler<INotification> // or any other interface deriving from INotification
{
    private long _messageCount;
    private long _messageErrorCount;

    public (long MessageCount, long MessageErrorCount) Stats => (_messageCount, _messageErrorCount);

    public ValueTask Handle(INotification notification, CancellationToken cancellationToken)
    {
        Interlocked.Increment(ref _messageCount);
        if (notification is ErrorMessage)
            Interlocked.Increment(ref _messageErrorCount);
        return default;
    }
}

4.8. Notification handlers also support open generics

public sealed class GenericNotificationHandler<TNotification> : INotificationHandler<TNotification>
    where TNotification : INotification // Generic notification handlers will be registered as open constrained types automatically
{
    public ValueTask Handle(TNotification notification, CancellationToken cancellationToken)
    {
        return default;
    }
}

4.9. Notification publishers

Notification publishers are responsible for dispatching notifications to a collection of handlers. There are two built in implementations:

  • ForeachAwaitPublisher - the default, dispatches the notifications to handlers in order 1-by-1
  • TaskWhenAllPublisher - dispatches notifications in parallel

Both of these try to be efficient by handling a number of special cases (early exit on sync completion, single-handler, array of handlers). Below we implement a custom one by simply using Task.WhenAll.

services.AddMediator(options =>
{
    options.NotificationPublisherType = typeof(FireAndForgetNotificationPublisher);
});

public sealed class FireAndForgetNotificationPublisher : INotificationPublisher
{
    public async ValueTask Publish<TNotification>(
        NotificationHandlers<TNotification> handlers,
        TNotification notification,
        CancellationToken cancellationToken
    )
        where TNotification : INotification
    {
        try
        {
            await Task.WhenAll(handlers.Select(handler => handler.Handle(notification, cancellationToken).AsTask()));
        }
        catch (Exception ex)
        {
            // Notifications should be fire-and-forget, we just need to log it!
            // This way we don't have to worry about exceptions bubbling up when publishing notifications
            Console.Error.WriteLine(ex);

            // NOTE: not necessarily saying this is a good idea!
        }
    }
}

4.10. Use streaming messages

Since version 1.* of this library there is support for streaming using IAsyncEnumerable.

var mediator = serviceProvider.GetRequiredService<IMediator>();

var ping = new StreamPing(Guid.NewGuid());

await foreach (var pong in mediator.CreateStream(ping))
{
    Debug.Assert(ping.Id == pong.Id);
    Console.WriteLine("Received pong!"); // Should log 5 times
}

// ...

public sealed record StreamPing(Guid Id) : IStreamRequest<Pong>;

public sealed record Pong(Guid Id);

public sealed class PingHandler : IStreamRequestHandler<StreamPing, Pong>
{
    public async IAsyncEnumerable<Pong> Handle(StreamPing request, [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(1000, cancellationToken);
            yield return new Pong(request.Id);
        }
    }
}

5. Diagnostics

Since this is a source generator, diagnostics are also included. Examples below

  • Missing request handler

Missing request handler

  • Multiple request handlers found

Multiple request handlers found

6. Differences from MediatR

This is a work in progress list on the differences between this library and MediatR.

  • RequestHandlerDelegate<TResponse>() -> MessageHandlerDelegate<TMessage, TResponse>(TMessage message, CancellationToken cancellationToken)
    • This is to avoid excessive closure allocations. I think it's worthwhile when the cost is simply passing along the message and the cancellation token.
  • No ServiceFactory
    • This library relies on the Microsoft.Extensions.DependencyInjection, so it only works with DI containers that integrate with those abstractions.
  • Singleton service lifetime by default
    • MediatR in combination with MediatR.Extensions.Microsoft.DependencyInjection does transient service registration by default, which leads to a lot of allocations. Even if it is configured for singleton lifetime, IMediator and ServiceFactory services are registered as transient (not configurable).
  • Methods return ValueTask<T> instead of Task<T>, to allow for fewer allocations (for example if the handler completes synchronously, or using async method builder pooling/PoolingAsyncValueTaskMethodBuilder<T>)
  • This library doesn't support generic requests/notifications

7. Versioning

For versioning this library I try to follow semver 2.0 as best as I can, meaning

  • Major bump for breaking changes
  • Minor bump for new backward compatible features
  • Patch bump for bugfixes

mediator's People

Contributors

alaatm avatar anadale avatar feiyun0112 avatar jimwolff avatar martinothamar avatar oskogstad avatar sjmdsky avatar timmoth avatar timothymakkison avatar tornhoof avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mediator's Issues

Error on EF Core Update method after using Mediator

Hi,

Great improvement with Mediator, we have changed over from MediatR recently. However I am getting an error on the EF Core Update method after the update to Mediator and I wanted to check if this is known or if I can get any help solving this issue.

For DI of Mediator:

Services.AddMediator(options => { options.Namespace = DomainName; options.ServiceLifetime = ServiceLifetime.Transient; });

All my other services are also Transient.

However, when calling the EF Core Update method to update a record in the database, I get the error below:

The instance of entity type 'Entity' cannot be tracked because another instance with the key value '{Id: 6629b094-1884-4e5d-8d03-ae2dfd5472a5}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.\r\n

The Add and Find Methods from EF Core are working fine.

I am using version 6.0.10 of the EF Core Nuget Package and using version 2.0.29 of Mediator.

If more info is required, please let me know and I will provide it. I have tried changing the other Lifetimes as well to see if that solves the issue but I still get the same error.

Thank you for the help

Exposing MediatR RequestHandler as minimal web API using attribute routing๏ผŸ

could you use a framework such as MediatR.WebApi which is specifically designed to integrate MediatR with web API's, it provides a simple way to expose your handlers as web api endpoints, it also allows you to specify the url and even verb(GET,POST,DELETE etc) of the request๏ผŸ

Another way to expose a MediatR RequestHandler as a minimal web API is by using C# Source Generators to generate the HTTP endpoints. C# Source Generators are a feature of C# 9.0 and later, which allow you to programmatically generate code at compile-time, based on the code that you've written.

XYZ encountered an error and has been disabled + SyntaxTree is not part of the compilation

Somehow the Mediator generator has stopped working on my machine on visual studio 2022 17.6.0 and later versions. Possibly this only happened after an update previously, I am not sure. Everything still works on Rider, so clearly it is something to do with the interaction with the Mediator codegen and visual studio. Can you advise what I can do to diagnose this further?

When I open the solution, I see a lot of "XYZ encountered an error and has been disabled" messages at the top of visual studio. These XYZ are related to various parts of the RefactoringProvider and all have the exception trace as shown below. I also see some features with the same issue and stack trace (CodeLens references .. ).

If I remove the Mediator code generator from the solution I do not see any of these issues, but obviously, I can't initialise the mediator, so nothing works in a useful way.

If I look at the IncrementalMediatorGenerator Analyzer in solution explorer, the spinning wheel continues forever, but nothing is generated.

I have tried reinstalling visual studio and rebooting and disabling resharper ... any other suggestions?

StreamJsonRpc.RemoteInvocationException: SyntaxTree is not part of the compilation (Parameter 'syntaxTree')
at StreamJsonRpc.JsonRpc.d__1511.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Threading.Tasks.ValueTask1.get_Result()
at Microsoft.CodeAnalysis.Remote.BrokeredServiceConnection1.<TryInvokeAsync>d__181.MoveNext()
RPC server exception:
System.ArgumentException: SyntaxTree is not part of the compilation (Parameter 'syntaxTree')
at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSemanticModel(SyntaxTree syntaxTree, Boolean ignoreAccessibility)
at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.CommonGetSemanticModel(SyntaxTree syntaxTree, Boolean ignoreAccessibility)
at Microsoft.CodeAnalysis.Diagnostics.SuppressMessageAttributeState.IsDiagnosticSuppressed(Diagnostic diagnostic, SuppressMessageInfo& info)
at Microsoft.CodeAnalysis.Diagnostics.SuppressMessageAttributeState.ApplySourceSuppressions(Diagnostic diagnostic)
at Microsoft.CodeAnalysis.GeneratorDriver.FilterDiagnostics(Compilation compilation, ImmutableArray1 generatorDiagnostics, DiagnosticBag driverDiagnostics, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.GeneratorDriver.RunGeneratorsCore(Compilation compilation, DiagnosticBag diagnosticsBag, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.GeneratorDriver.RunGenerators(Compilation compilation, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.FinalizeCompilationAsync(SolutionState solution, Compilation compilationWithoutGenerators, CompilationTrackerGeneratorInfo generatorInfo, Compilation compilationWithStaleGeneratedTrees, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildCompilationInfoFromScratchAsync(SolutionState solution, CompilationTrackerGeneratorInfo generatorInfo, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildCompilationInfoAsync(SolutionState solution, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetOrBuildCompilationInfoAsync(SolutionState solution, Boolean lockGate, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetCompilationSlowAsync(SolutionState solution, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.GetMetadataReferenceAsync(ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.FinalizeCompilationAsync(SolutionState solution, Compilation compilationWithoutGenerators, CompilationTrackerGeneratorInfo generatorInfo, Compilation compilationWithStaleGeneratedTrees, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildCompilationInfoFromScratchAsync(SolutionState solution, CompilationTrackerGeneratorInfo generatorInfo, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.BuildCompilationInfoAsync(SolutionState solution, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetOrBuildCompilationInfoAsync(SolutionState solution, Boolean lockGate, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.GetCompilationSlowAsync(SolutionState solution, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.Razor.TagHelperResolver.GetTagHelpersAsync(Project workspaceProject, RazorProjectEngine engine, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.Remote.Razor.RemoteTagHelperProviderService.GetTagHelpersCoreAsync(RazorPinnedSolutionInfoWrapper solutionInfo, ProjectSnapshotHandle projectHandle, String factoryTypeName, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.Remote.Razor.RemoteTagHelperProviderService.GetTagHelpersDeltaCoreAsync(RazorPinnedSolutionInfoWrapper solutionInfo, ProjectSnapshotHandle projectHandle, String factoryTypeName, Int32 lastResultId, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.Remote.BrokeredServiceBase.RunServiceImplAsync[T](Func2 implementation, CancellationToken cancellationToken)

SourceGenerator generates an error for IStreamRequest with structs

Hi,
I tried converting one of my MediatR solutions to your source-gen approach and noticed the following:

The srcgen generates the following error for IStreamRequest with structs in 2.0.15-preview

Error	CS0266	Cannot implicitly convert type 'System.Collections.Generic.IAsyncEnumerable<MyStruct>' to 'System.Collections.Generic.IAsyncEnumerable<object>'. An explicit conversion exists (are you missing a cast?)	ConsoleApp7	

Repro:

using Mediator;

public class Program
{
	public static int Main(string[] args)
	{
		return 0;
	}
}

public sealed class MyRequest : IStreamRequest<MyStruct>
{
}

public sealed class MyRequestHandler : IStreamRequestHandler<MyRequest, MyStruct>
{
	public IAsyncEnumerable<MyStruct> Handle(MyRequest request, CancellationToken cancellationToken)
	{
		throw new NotImplementedException();
	}
}

public readonly struct MyStruct
{

}

The generated code snippet is:

        /// <summary>
        /// Create stream.
        /// Throws <see cref="global::System.ArgumentNullException"/> if message is null.
        /// Throws <see cref="global::Mediator.MissingMessageHandlerException"/> if no handler is registered.
        /// </summary>
        /// <param name="message">Incoming message</param>
        /// <param name="cancellationToken">Cancellation token</param>
        /// <returns>Async enumerable</returns>
        public global::System.Collections.Generic.IAsyncEnumerable<object> CreateStream(
            object message,
            global::System.Threading.CancellationToken cancellationToken = default
        )
        {
            switch (message)
            {
                case global::MyRequest m: return CreateStream(m, cancellationToken);
                default:
                {
                    ThrowArgumentNullOrInvalidMessage(message as global::Mediator.IStreamMessage, nameof(message));
                    return default;
                }
            }
            
        }

Workaround: use class instead of struct

[Suggestion] Option to control handler lifetime

What about providing the following interfaces and use those to register handlers accordingly:
ISingletonRequestHandler<in TRequest, Tresponse>, IScopedRequestHandler<...>, ITransientRequestHandler<...>
All of them inherit from IRequestHandler<...>. Default lifetime applies if IRequestHandler<...> is used.
Alternatively, there could maybe be an attribute to override the default registration.
In a larger asp.net core web api type of backend you will typically have a mix of lifetimes for your handlers.
It would actually also be a good idea for notification handlers, in my opinion.

Missing Request Type Exception

Thanks again for a great project.

At the moment in Send in Mediator.g.cs the default handler is to ThrowArgumentNullOrInvalidMessage which first casts the message as global::Mediator.IMessage. Since the request doesn't need to be IMessage it can come through as null at that point, and then we get an ArgumentNullException message instead of a missing request type exception.

Possibly the issue I'm referring to could be fixed by not casting to IMessage? This would do MissingMessageHandlerException which would be good enough for my purposes. (I'm not sure if distinguishing between missing handler and a missing request type is possible or sensible, but they look like they could be different things)

(I am on 2.0.27-rc)

conflict in generated code if imported in multiple projects

im unable to use Mediator.SourceGenerator in project, that is referencing different project which is already using Mediator.SourceGenerator - second project will throw at me this message: (CS0436)

Error CS0436 The type 'MediatorDependencyInjectionExtensions' in 'Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs' conflicts with the imported type 'MediatorDependencyInjectionExtensions' in 'ProjectA'. Using the type defined in 'Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\Mediator.g.cs'.

(technically it is 'only' warning, but i have TreatWarningsAsErrors enabled and therefore my build fails... )

affected types:

Mediator.MediatorOptions
Mediator.MediatorOptionsAttribute
Microsoft.Extensions.DependencyInjection.MediatorDependencyInjectionExtensions

ideas how to fix this:

  • move Mediator.MediatorOptions and Mediator.MediatorOptionsAttribute to Mediator.Abstractions package
    - they looks like they do not need to be generated
  • generate MediatorDependencyInjectionExtensions in namespace defined by MediatorOptions:Namespace property
    - this way i can use multiple generated AddMediator() methods

-or-

make all 3 affected classes internal
- this will force to call AddMediator method from within project, that generated it but is simple fix

[Bug?] Issue with Miscrosoft DI and changing 'Lifetime Scopes'

image

Sorry for the short post, it seems like there is no 'overload' for DI.

This causes issues because Microsoft.Identity registers UserManager and such as 'Scoped', while this library by default registers everything as 'Singleton', this causes an error to be thrown by Microsoft.Identity.

When i went to 'change the default' of this library to 'Scoped', I discovered that the overload method was not available for some reason. I reviewed the code, and I could not solve this myself.

Do you know what is going on?

PS: I have a fairly large project and can test things for you if you want regarding V2.

Thanks

DebuggerNonUserCodeAttribute/DebuggerStepThroughAttribute

I can see DebuggerNonUserCodeAttribute is on the main generated DependencyInjectionExtensions class, but it doesn't seem to be on the Mediator class. I'd like to be able to just step through/passed the generated code into my code (I have "just my code" enabled); is there a way to get the generator to put this attribute on all the classes? Or is there another way to get the debugger to skip all the generated code when "stepping into"

Thanks for the library :-)

Scoped/transient services

Hello,

I'm trying to implement this version of the mediator pattern, but I've found a showstopper while trying to port my services: Since services are registered as singletons, I can't use scoped services inside (think IUnitOfWork in my AppService). I'm getting the following error: "InvalidOperationException: Cannot consume scoped service 'IUnitOfWork' from singleton 'AddFormFileHandler'."

I know right now it is not possible to configure how services are instantiated, but it would be really useful as an option, since for most web apps, dbconnections (for example) would need to be scoped. Maybe have an option for services to be singleton/transient only (I really don't care if they are scoped, as usually handlers execute only once per request, they are never instantiated twice).

CS0118: 'Mediator' is a namespace but is used like a type

for better performance, i chose Mediator instead of IMediator, but i get CS0118: 'Mediator' is a namespace but is used like a type error, so i have to explicitly declare Mediator by "MyNameSpace.Mediator" for every call , do you have better way to solve this ?

example

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleApp1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var service = new ServiceCollection()
                .AddMediator(options => options.Namespace = "MyNamespace")
                .BuildServiceProvider();
        }
    }

    public class Test
    {
        private readonly IServiceProvider _service;

        public Test(IServiceProvider service)
        {
            _service = service;
        }

        public void ResolveMediator()
        {
            Mediator mediator = _service.GetRequiredService<Mediator>(); // here i will get cs0018 error
            MyNamespace.Mediator mediator1 = _service.GetRequiredService<MyNamespace.Mediator>(); // this will work, but i wanna know is there any better way to avoid type "MyNamespace." every time 
        }
    }
}

Creating IRequest with byte[] causes InvalidCastException

image

image

Warning	CS8785	Generator 'MediatorGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'InvalidCastException' with message 'Unable to cast object of type 'Microsoft.CodeAnalysis.CSharp.Symbols.PublicModel.ArrayTypeSymbol' to type 'Microsoft.CodeAnalysis.INamedTypeSymbol'.'	XXXXX	1	Active

Possibility to make remove certain handlers from the ServiceCollection

At the moment the DI registration for INotificationHandlers registers them all via SP.GetRequiredService.
I have the use case that in some cases (tests or certain features are disabled) I'd like to remove certain NotificationHandlers from the global list, as they might have unintended side effects for tests or a simply disabled.
Let's assume I have a notification MyNotification and an implementation handler MyNotificationHandler, which I want to remove.

Conceptionally the code looks like this for a ServiceDescription registration:

 var sd = new ServiceDescriptor(typeof(INotificationHandler<MyNotification>), p => p.GetRequiredService<MyNotificationHandler>(), ServiceLifetime.Singleton);

due to the fact how the registration works, the functor is actually Func<IServiceProvider, object> making it impossible to obtain the real implementation type via reflection without reverting to tricks like IL inspection.

I propose to change the registration to something like:

 var sd = new ServiceDescriptor(typeof(INotificationHandler<MyNotification>), GetRequiredService<MyNotificationHandler>(), ServiceLifetime.Singleton);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Func<IServiceProvider, object> GetRequiredService<T>() where T : notnull
{
    return provider => provider.GetRequiredService<T>();
}

The above code results in a delegate target which looks like this: <>c__2<MyNotificationHandler> and this allows us to identify the correct handler to remove.

Question, when to use message types?

Not really an issue with Mediator, more of me just not understanding how it works. Message types are explained here, but why would you use certain types over others? For example:

IRequest<out TResponse> vs ICommand<out TResponse> Why would I use one over the other?

ICommand vs IRequest vs INotification? Same question here. The descriptions tell me they all do the same thing.

Defensive copies for non-readonly struct queries

At the moment the generator adds the in parameter modifier for read-only structs, it does not add it for non-readonly structs.
But the method signature of the wrapper always containts the in modifier.

public {{ wrapperType.ReturnTypeName }} Handle(in TRequest request, global::System.Threading.CancellationToken cancellationToken) =>
_rootHandler(request, cancellationToken);

This means, that for non-readonly structs a defensive copy is always made, which is wrong from the code-correctness perspective and for performance.
See: https://devblogs.microsoft.com/premier-developer/the-in-modifier-and-the-readonly-structs-in-c/#performance-characteristics-of-the-in-modifier for more details

Abstractions to support other dependency injection providers?

I note that in the documentation you state that:

This library relies on the Microsoft.Extensions.DependencyInjection, so it only works with DI containers that integrate with those abstractions.

Have you considered structuring the library in such a way that use with alternative abstractions would be possible (if not supported currently)? This would allow extensions to be written to support additional DI containers.

The reason I ask is that I am also in the process of writing a mediator library using source generators, but am not nearly as far along in my endeavors. It is my intention to support compile-time DI containers such as StrongInject, Jab and Pure.DI.

Much of the logic would be shared between the implementations (e.g. analyzing the client code to collect information about all the registered handlers and pipeline behaviors), but would differ primarily in the source which is then generated from the collected information.

MediatR pre/post/exception handlers

Currently don't have these. Not sure I want to add these "by default", as they will slow down all pipelines even if not used, but maybe as a separate package? Needs investigation

Source generator do not process assemblies that does not have direct references to Mediator assembly or types

Hello,

First of all, thank you very much for your excellent product, it is a real pleasure to use it! However, I've stepped over the strange bug (or feature) that is related to the multi-project setup:

Let's imagine that we have three projects:

MySolution.Abstractions project references Mediator.Abstractions package and contains a class ApplicationHandlerBase that serves as a base class for the request handlers of the solution. It also contains a class ApplicationController, that provides a method TResponse ProcessMessage<TRequest, TResponse>(TRequest request) that retrieves an IMediator service from the service provider exposed by HttpContext and dispatches a call to IMediator.Send using provided request.

MySolution.Handlers project references MySolution.Abstractions project and implements a handler (SomeHandler), derived from ApplicationHandlerBase and a controller, derived from ApplicationController.

MySolution.WebApp is ASP.NET Core application, that references MySolution.Handlers project and Mediator.SourceGenerator package. The call to AddMediator is also in this project.

Now, to the interesting part: with this setup source generator does not generate code for handlers from MySolution.Handlers. If I, however, will (redundantly) mark SomeHandler as implementing IRequestHandler (because it is implementing this interface through the base class) OR directly reference any type from Mediator.Abstractions in that project, then handler will be properly registered. It seems that source generator's analyzer skips the project, since the compiler(?) does not add a reference to the assembly Mediator.Abstractions. My wild guess is that the problem is here. It looks like analyzer should inspect not only direct, but all references of the compilation. I can come up with a PR if you'll confirm my guess and removing such constraint will not interfere with your vision of the analyzer.

Help in diagnosing MessageHandler not being called

Hi,
I have a solution where I have a business project (where commands, queries and messages exist, Mediator.Abstractions linked), and an api project (where I have Mediator linked).

Commands/queries are working perfect. but not messages. Message handlers are not being called and I don't have any clues about why.

Is there any place where the source generator puts the generated files, so I can check if at least there are any information about my handlers? Or, is there any way I can diagnose this?

Thanks in advance.

Handle published notification concurrently

Hi
I would like to migrate from MediatR to Mediator,
but the current implementation Mediator doesn't support handling pushed notifications all in once

Is it possible to implement such a thing in Mediator?

Support for netstandard2.0

Hi @martinothamar, this is just a question after I came across your package and read that it supports netstandard2.1:

Would it be possible to also support netstandard2.0 (e.g., with a reduced set of features if some features depend on capabilities introduced with netstandard2.1)? We have a frontend that must target .NET Framework and, therefore, can only use libraries up to netstandard2.0.

Issue with automapper in dotnet core webapi argument exception

Hi,

Our project is using the start guide and mostly works as advertised. However, once we add automapper into the mix, we're facing an exception failure:

System.ArgumentException: GenericArguments[0], 'System.Int64', on 'T MaxFloat[T](System.Collections.Generic.IEnumerable`1[T])' violates the constraint of type 'T'.

The simple version of the models and config:

    public class C1 : IRequest<long>
    {
        public string FirstName { get; set; }
    }

    public class C2
    {
        public string FirstName { get; set; }
    }

    var mapperConfig = new MapperConfiguration(cfg => {
        cfg.CreateMap<C1, C2>();
    });
    IMapper mapper = mapperConfig.CreateMapper();
    services.AddSingleton(mapper);

The system throws the exception on cfg.CreateMap<C1, C2>();

If I remove IRequest<long> from the C1 model, all works as expected.

I can't work out why automapper does not like the IRequest interface. As far as I can tell, the IRequest is just a marker.

Have you ever seen this before?

Lifetime still treated as singleton, eventhough i already set the lifetime to scoped

Hello, thanks for the great library, In my current project, I use .Net 7 with EF 7.
okay, so, I already set the options for the mediator like this :
image
But, when I run the code, i got this error from visual studio :
image
So, as mentioned my command handler is still treated as a singleton, not a scoped somehow, do i miss any step?

N.B. If i change my service to singleton, everything worked, but got error later on, because my DbContext need to be defined as scoped.

Thank you very much for your answer

MessagePreProcessor not found

I trying to migrate my old application from MediatR to this Mediator. So I got issue with MessagePreProcessor which I want to use instead of IRequestPreProcessor.
It looks like it doesn't exist. I've included Mediator.Abstractions and Mediator.SourceGenerator both version 2.0.30 and can't use it
image_2023-02-08_14-58-46

System.Threading.Tasks.Extensions could be removed?

Is System.Threading.Tasks.Extensions actually used? By searching on github it is found just in csproj. As currently Mediator bring this dependency to blazor (wasm) project and I am just curies if it could be removed.

DI registration extensions not working in .Net7

The dependency injection registration, of the Mediator in the Services collection is not working in .NET 7.
image

It seems that the extensions can not be found.

Everything works fine for the same project in .NET6.
Just changing the SDK makes it work.

However, I have other dependencies/functionalities from .NET 7 that I require in a specific project.
And I would like to use Mediator there as well.

I tried with the release candidate (2.0.27-rc, 2.0.28-rc) and with the preview versions (2.1.0-preview2, 2.1.0-preview3).
None seems to work.
I also tried to do the configuration with:

using Mediator;
[assembly: MediatorOptions(Namespace = "SimpleConsole.Mediator", DefaultServiceLifetime = ServiceLifetime.Transient)]

But with the same result.

Process to replicate:

  • Create a new API project with .NET 7.
  • Import both Mediator.SourceGenerator and Mediator.Abstractions packages from NuGet.
  • try to call the AddMediator() extension in the builder.Services in Program.cs

Mediator Diagnostics detects other AddMediator call as belonging to itself

This is a weird one and definitely non-standard usage. So I'm using a second mediator package in my codebase right now that also has an AddMediator call on the service collection. The issue is that it has options that are configured. The Mediator Analyzer picks this up and errors out. It should probably check the namespace of the AddMediator extension call before printing an error as it might be a different Mediator framework that's being used.

EntryPointNotFoundException throwns in ValueTask

I'm crossposting this issue on aspnetcore, because the error is thrown on the ValueTask returned by Mediator.

I'm not sure where the error is. Not sure if Mediator is generating something wrong between the Send and the handler, or if this is a framework issue.

Maybe you can take a look too?

Generate Mediator class that allow for the internal access level on Requests and Handlers / better support for large solutions

I have a project where I would like to keep many concepts marked as internal. It also applies to the mediator 'requests' and 'handlers'. At the moment, I can't use the internal keyword because I'm getting an error:

Inconsistent accessibility: parameter type 'Request' is less accessible than method' Mediator.Send(Request, CancellationToken)'

Is there w way to generate the mediator class as internal? Then, I could completely hide that I'm using the Mediator (it is just an implementation detail) and not expose it from the assembly.

Warning message: "...conflicts with the imported type 'MediatorOptions'..."

Hi,
When I compile, I get the above warning.
Why is this message displayed?

Thanx

  The type 'MediatorOptions' in 'Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\MediatorOptions.g.cs' 
  conflicts with the imported type 'MediatorOptions' in 'Transportation.Logic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. 
  Using the type defined in 'Mediator.SourceGenerator.Roslyn40\Mediator.SourceGenerator.IncrementalMediatorGenerator\MediatorOptions.g.cs'.

Error in generated code with generic parameter

I just attempted at moving over to this library but encountered some strange stuff in generated code.

I have decorated my NotificationHandlers with IDomainEventHandler, implementing INotificationHandler, taking generic parameters with TDomainEventbeing constrained to DomainEvent (implements INotification)

... Mediator.SourceGenerator.Roslyn40/Mediator.SourceGenerator.IncrementalMediatorGenerator/Mediator.g.cs(1214,74,1214,86): 
error CS0246: The type or namespace name 'TDomainEvent' could not be found (are you missing a using directive or an assembly reference?)
public interface IDomainEventHandler<TDomainEvent>
    : INotificationHandler<TDomainEvent>
    where TDomainEvent : DomainEvent
{

}
services.AddMediator(options =>
{
    options.ServiceLifetime = ServiceLifetime.Scoped;
});

Why it's not supporting .net 7?

I'm using this package in .net 7 and the service collection extension is not available I cannot register the mediator service nor change it's lifetime

Improve codegen for large projects

As per discussion from #7 (comment), performance for large projects is pretty bad for cases where we don't have a statically known type as method parameter (i.e. ValueTask<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);). There are some easier wins to be had, but none that preserves the performance advantage that are seen on smaller projects.

I've created the Mediator.Benchmarks.Large project which tracks performance for the above mentioned overload when there are 1000 requests in the assembly.

To not delay 2.0 much further I'm going to complete this release first, then start the work on improving the perf situation:

  • Add overloads for the Send methods that will allow requests without responses to be generic arguments. This is a small improvement for i.e. struct cases where we can avoid boxing. ValueTask<Unit> Send<TRequest>(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest;
  • Generate IDs per request that can be mapped to linear array access so that we have close to zero overhead handler lookup. This will require request types to be partial. Not sure if this will become a breaking change or if we can keep it optional and fallback to slower message handling. This is a bigger change but will drastically improve the performance for larger projects

Mediator only adds classes from one assembly

I'm in the process of migrating from Mediatr to Mediator (2.0.27-rc). My solution is setup like this:

  • WebApi (Controllers that send/publish queries and commands)
  • Application (Defines commands, queries and handlers e.g. GetItemQuery, CreateItemCommand, GetItemQueryHandler)
  • Domain (Domain logic and events e.g. ItemCreated)
  • Infrastructure (storage logic and publishing of Domain Events)

Using Mediatr, I added the relevant classes to the DI containers like this:
services.AddMediatr(applicationAssembly);
services.AddMediatr(infrastructureAssembly);

However, with Mediator it doesn't seem to matter where I add the .AddMediator call - it only ever seems to generate code that includes Domain Events defined in the Domain project (ItemCreated etc). I've also tried adding [assembly: MediatorOptions] to each project to no avail.

What am I doing wrong? How do I define which assemblies Mediator should scan for queries, commands and handlers?

Corrupt state of HashSet in Mediator.SourceGenerator.CompilationAnalyzer causing high CPU in Visual Studio

Description

I am from the Visual Studio performance team and our telemetry has shown that there are high amounts of CPU being consumed by the callstack I pasted below. It appears that either there are multiple threads writing to the HashSet at the same time, or a thread reading from it while another thread is writing to it. HashSet is not inherently thread-safe, so this concurrent access to the HashSet is causing it to enter a corrupted state and the threads essentially get stuck in an infinite loop. This causes multiple threads to get stuck inside of HashSet.AddIfNotPresent and each thread to indefinitely consume a full core of CPU within Visual Studio.

CallStack

+microsoft.codeanalysis!Microsoft.CodeAnalysis.UserFunctionExtensions+<>c__DisplayClass3_0`2[Microsoft.CodeAnalysis.SourceProductionContext,System.ValueTuple`2[System.__Canon,System.Collections.Immutable.ImmutableArray`1[System.__Canon]]].b__0(!0,!1,valueclassSystem.Threading.CancellationToken)
+mediator.sourcegenerator.roslyn40!Mediator.SourceGenerator.IncrementalMediatorGenerator.b__5_3(valueclassMicrosoft.CodeAnalysis.SourceProductionContext,valueclassSystem.ValueTuple`2>)
+mediator.sourcegenerator.roslyn40!IncrementalMediatorGenerator.ExecuteInternal
+mediator.sourcegenerator.implementation!CompilationAnalyzer.Analyze
+mediator.sourcegenerator.implementation!CompilationAnalyzer.PopulateMetadata
+mediator.sourcegenerator.implementation!Mediator.SourceGenerator.CompilationAnalyzer.g__ProcessMember|97_0(classSystem.Collections.Generic.Queue`1,classMicrosoft.CodeAnalysis.INamespaceOrTypeSymbol,classSystem.Collections.Generic.Dictionary`2)
+mediator.sourcegenerator.implementation!Mediator.SourceGenerator.CompilationAnalyzer.g__ProcessInterface|97_1(int32,classMicrosoft.CodeAnalysis.INamedTypeSymbol,classMicrosoft.CodeAnalysis.INamedTypeSymbol,bool,classSystem.Collections.Generic.Dictionary`2)
+system.core.ni!System.Collections.Generic.HashSet`1[System.__Canon].AddIfNotPresent(System.__Canon)

Solution

Ensuring reads/writes to this HashSet are thread-safe will fix this issue. You can likely either use a lock to prevent concurrent access or use a thread-safe data structure such as ConcurrentBag instead of HashSet.

OpenTelemetry pipeline behaviors for metrics/tracing/logging

I think it could be useful to have a package called Mediator.OpenTelemetry or OpenTelemetry.Instrumentation.Mediator or something like that which would contain a pipeline behavior that would instrument all messages flowing through Mediator, similarly to how OpenTelemetry.Instrumentation.AspNetCore instruments all HTTP requests

[Bug] Handlers cannot be marked as 'Internal', and consumed by another Assembly

Hello!

Scenario

  • I am building a CLI app that cleans the .nuspec file (I add some custom settings in my .nuspec files, this is unrelated).
  • I am building a nuget package which utilizes Mediator
  • This nuget package will be consumed by other applications of mine.
  • The other applications will call _doSomethingCool.ExecuteSomeCode(someObjectGoesHere); which will call a method inside my library. The method inside the consumed library will call _mediator.Send(command); (I just wanted to explain how the flow works).
  • I do not want the internal handlers, or ISomeCoolService or SomeCoolCommand exposed to applications which consume this library, so I mark all my handlers and services as internal class or internal record (for the commands and responses)

Bug

When my application consumes the library which references Mediator, I receive errors, see below:
image

Closer view of the errors:
image

This leads me to believe that the generated code in the Mediator library cannot be set to internal.

@martinothamar - is this a bug, or just a fact of life with source-generators?

Again - BIG thank you for the library!! I can live with it just fine if the code must be marked as public, I am thankful for your beautiful code!

HttpContext access from IRequestHandler

Is there a way to access HttpContext from a request handler?
I added a filter to my (minimal API) endpoint to validate the API key, coming from the request headers. Upon successful validation I need the value to Generate JWT for subsequent requests.

Is there a workaround to this? Need a code sample.

Support multiple project

Mediator seems not supporting multiple projects. I Have several projects - WebAPI, User, Company. I have mediator installed and correct setup in User and Company. Running those, only the first project registered in DI works.

serviceCollection.AddCompanyServices();
serviceCollection.AddUserServices();
= Company works correctly, User gets error "No handler registered for message type:"

serviceCollection.AddUserServices();
serviceCollection.AddCompanyServices();
= User works correctly, Company gets error "No handler registered for message type:"

CS8785: Generator 'MediatorGenerator' failed to generate source.

I have a request that implements IRequest<byte[]>.

public class ExportRequest : IRequest<byte[]>
{
}

I have a request handler that implements IRequestHandler<ExportRequest, byte[]>.

public class ExportRequestHandler : IRequestHandler<ExportRequest, byte[]>
{
    // implementation
}

I get a CS8785 warning and no code is generated for the ExportRequestHandler. I have several other request, command and notification handlers, but this is the only one that expected to return a byte array result.

Also, the warning message is complaining: "Exception of type 'FileNotFoundException' with message 'Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0. blah blah blah' or one of its dependencies.

Are there any restrictions on TResponse types?

the project that I am using this on is a .Net6.0 project in VS 2022.

Code generation fails for any array type but succeeds for everything else. Tried string[], int[], Stream[].

using version 1.0.5

Project registration and service lifetime

This library is neat! I quite enjoy it and the concept, and when it works, it works.

However, I've run into a peculiar issue, in a similar vein of #51; I have two projects (at least, for the sake of this issue): Application and Data. The latter of the two houses requests and handlers (I should break this out, but stick with me), and I initially installed the source generator on that project, ran into some issues, followed the steps outlined in the aforementioned issue, and now I'm even worse off.

Rider doesn't see the extension method (and in fact, I don't either), but the project technically builds fine; furthermore, I can't seem to set the service registration correctly (though, this could/may be its own issue entirely).

(Also: I didn't want to necro the issue mentioned, and this is a smidge more complicated than that from what I can gather, hence the new issue.)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.