├── src ├── tests │ └── NimbleMediator.Tests │ │ ├── GlobalUsings.cs │ │ ├── Requests │ │ ├── MyRequestWithoutResponse.cs │ │ └── MyRequestWithResponse.cs │ │ ├── Helpers.cs │ │ ├── SenderTests.cs │ │ ├── NimbleMediator.Tests.csproj │ │ └── ServiceCollectionExtensionsTests.cs └── NimbleMediator │ ├── Contracts │ ├── INotification.cs │ ├── IMediator.cs │ ├── INotificationPublisher.cs │ ├── IRequest.cs │ ├── INotificationHandler.cs │ ├── IPublisher.cs │ ├── IRequestHandler.cs │ └── ISender.cs │ ├── Implementations │ ├── NotificationPublisherType.cs │ ├── Mediator.cs │ ├── Mediator.Publish.cs │ ├── NotificationPublishers │ │ ├── TaskWhenAllPublisher.cs │ │ ├── ForeachAwaitStopOnFirstExceptionPublisher.cs │ │ └── ForeachAwaitRobustPublisher.cs │ └── Mediator.Send.cs │ ├── ServiceExtensions │ ├── ServiceExtensions.cs │ └── NimbleMediatorConfig.cs │ └── NimbleMediator.csproj ├── assets ├── logo.png └── logo_128.png ├── benchmarks ├── send_benchmark.png ├── publish_foreachawait_benchmark.png ├── publish_taskwhenall_benchmark.png └── MediatorsBenchmark │ ├── Program.cs │ ├── MediatorsBenchmark.csproj │ ├── MediatorsBenchmark.sln │ ├── Requests │ ├── MediatR.cs │ └── NimbleMediator.cs │ ├── Notifications │ ├── MediatR.cs │ └── NimbleMediator.cs │ ├── SendBenchmark.cs │ ├── ForeachAwaitBenchmark.cs │ └── TaskWhenAllBenchmark.cs ├── .cz.toml ├── .pre-commit-config.yaml ├── samples ├── NimbleMediator.Samples.ForeachAwaitRobustPublisher │ ├── NimbleMediator.Samples.ForeachAwaitRobustPublisher.csproj │ └── Program.cs └── NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher │ ├── NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher.csproj │ └── Program.cs ├── CHANGELOG.md ├── CONTRIBUTING.md ├── NimbleMediator.sln ├── README.md ├── .gitignore ├── .editorconfig └── LICENSE /src/tests/NimbleMediator.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baranacikgoz/NimbleMediator/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baranacikgoz/NimbleMediator/HEAD/assets/logo_128.png -------------------------------------------------------------------------------- /benchmarks/send_benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baranacikgoz/NimbleMediator/HEAD/benchmarks/send_benchmark.png -------------------------------------------------------------------------------- /benchmarks/publish_foreachawait_benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baranacikgoz/NimbleMediator/HEAD/benchmarks/publish_foreachawait_benchmark.png -------------------------------------------------------------------------------- /benchmarks/publish_taskwhenall_benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baranacikgoz/NimbleMediator/HEAD/benchmarks/publish_taskwhenall_benchmark.png -------------------------------------------------------------------------------- /.cz.toml: -------------------------------------------------------------------------------- 1 | [tool.commitizen] 2 | name = "cz_conventional_commits" 3 | tag_format = "v$version" 4 | version_scheme = "semver" 5 | version = "1.2.0" 6 | update_changelog_on_bump = true 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - hooks: 3 | - id: commitizen 4 | - id: commitizen-branch 5 | stages: 6 | - push 7 | repo: https://github.com/commitizen-tools/commitizen 8 | rev: v3.12.0 9 | -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/INotification.cs: -------------------------------------------------------------------------------- 1 | namespace NimbleMediator.Contracts; 2 | 3 | /// 4 | /// Marker interface for notifications. 5 | /// 6 | public interface INotification 7 | { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using MediatorsBenchmark; 3 | 4 | BenchmarkRunner.Run(); 5 | BenchmarkRunner.Run(); 6 | BenchmarkRunner.Run(); -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/IMediator.cs: -------------------------------------------------------------------------------- 1 | namespace NimbleMediator.Contracts; 2 | 3 | /// 4 | /// Defines a mediator with the ability to send requests and publish notifications. 5 | /// 6 | public interface IMediator : ISender, IPublisher 7 | { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/INotificationPublisher.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace NimbleMediator; 4 | 5 | public interface INotificationPublisher 6 | { 7 | Task PublishAsync(TNotification notification, IEnumerable> handlers, CancellationToken cancellationToken) 8 | where TNotification : INotification; 9 | } -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/IRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NimbleMediator.Contracts; 2 | #pragma warning disable S2326 // Unused type parameters should be removed 3 | 4 | /// 5 | /// Marker interface for requests with response. 6 | /// 7 | /// 8 | 9 | public interface IRequest 10 | { 11 | } 12 | 13 | /// 14 | /// Marker interface for requests without response. 15 | /// 16 | public interface IRequest 17 | { 18 | } 19 | 20 | #pragma warning restore S2326 -------------------------------------------------------------------------------- /src/NimbleMediator/Implementations/NotificationPublisherType.cs: -------------------------------------------------------------------------------- 1 | namespace NimbleMediator.Implementations; 2 | 3 | /// 4 | /// Defines the type of notification publisher. 5 | /// 6 | public enum NotificationPublisherType 7 | { 8 | /// 9 | /// Publishes notifications by sequentially awaiting with a loop. 10 | /// 11 | ForeachAwait, 12 | 13 | /// 14 | /// Publishes notifications by awaiting with Task.WhenAll(). 15 | /// 16 | TaskWhenAll 17 | } 18 | -------------------------------------------------------------------------------- /src/tests/NimbleMediator.Tests/Requests/MyRequestWithoutResponse.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace NimbleMediator.Tests; 4 | 5 | public class MyRequestWithoutResponse : IRequest 6 | { 7 | public string Name { get; set; } = "Baran"; 8 | } 9 | 10 | public class MyRequestWithoutResponseHandler : IRequestHandler 11 | { 12 | public ValueTask HandleAsync(MyRequestWithoutResponse request, CancellationToken cancellationToken) 13 | { 14 | return ValueTask.CompletedTask; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tests/NimbleMediator.Tests/Requests/MyRequestWithResponse.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace NimbleMediator.Tests; 4 | 5 | public class MyRequestWithResponse : IRequest 6 | { 7 | public string Name { get; set; } = "Baran"; 8 | } 9 | 10 | public class MyRequestWithResponseHandler : IRequestHandler 11 | { 12 | public ValueTask HandleAsync(MyRequestWithResponse request, CancellationToken cancellationToken) 13 | { 14 | return ValueTask.FromResult(request.Name); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tests/NimbleMediator.Tests/Helpers.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NimbleMediator.ServiceExtensions; 3 | 4 | namespace NimbleMediator.Tests; 5 | 6 | public static class Helpers 7 | { 8 | public static ServiceProvider SetupServiceProvider() 9 | { 10 | var services = new ServiceCollection(); 11 | 12 | services.AddNimbleMediator(config => 13 | { 14 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 15 | }); 16 | 17 | return services.BuildServiceProvider(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/INotificationHandler.cs: -------------------------------------------------------------------------------- 1 | namespace NimbleMediator.Contracts; 2 | 3 | /// 4 | /// Defines a notification handler. 5 | /// 6 | /// 7 | public interface INotificationHandler 8 | where TNotification : INotification 9 | { 10 | /// 11 | /// Handles the notification. 12 | /// 13 | /// 14 | /// 15 | /// 16 | Task HandleAsync(TNotification notification, CancellationToken cancellationToken); 17 | } -------------------------------------------------------------------------------- /samples/NimbleMediator.Samples.ForeachAwaitRobustPublisher/NimbleMediator.Samples.ForeachAwaitRobustPublisher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/IPublisher.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace NimbleMediator; 4 | 5 | /// 6 | /// Defines an event publisher. 7 | /// 8 | public interface IPublisher 9 | { 10 | /// 11 | /// Publishes the notification. 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | Task PublishAsync(TNotification notification, CancellationToken cancellationToken) 18 | where TNotification : INotification; 19 | } 20 | -------------------------------------------------------------------------------- /src/NimbleMediator/ServiceExtensions/ServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using NimbleMediator.Contracts; 4 | using NimbleMediator.Implementations; 5 | 6 | namespace NimbleMediator.ServiceExtensions; 7 | 8 | public static class ServiceCollectionExtensions 9 | { 10 | public static IServiceCollection AddNimbleMediator(this IServiceCollection services, Action configAction) 11 | { 12 | var config = new NimbleMediatorConfig(services); 13 | 14 | configAction(config); 15 | 16 | config.RegisterServicesInternal(); 17 | 18 | return services; 19 | } 20 | } -------------------------------------------------------------------------------- /samples/NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher/NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/NimbleMediator/Implementations/Mediator.cs: -------------------------------------------------------------------------------- 1 | 2 | using Microsoft.Extensions.DependencyInjection; 3 | using NimbleMediator.Contracts; 4 | 5 | namespace NimbleMediator.Implementations; 6 | 7 | /// 8 | /// Implementation of . 9 | /// 10 | public partial class Mediator : IMediator 11 | { 12 | private readonly IServiceProvider _serviceProvider; 13 | private readonly Dictionary _publisherTypeMappings; 14 | 15 | public Mediator(IServiceProvider serviceProvider, Dictionary publisherTypeMappings) 16 | { 17 | _serviceProvider = serviceProvider; 18 | _publisherTypeMappings = publisherTypeMappings; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/MediatorsBenchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Exe 15 | net7.0 16 | enable 17 | enable 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/tests/NimbleMediator.Tests/SenderTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NimbleMediator.Contracts; 3 | 4 | namespace NimbleMediator.Tests; 5 | 6 | public class SenderTests 7 | { 8 | public SenderTests() 9 | { 10 | _sender = Helpers.SetupServiceProvider().GetRequiredService(); 11 | } 12 | private readonly ISender _sender; 13 | 14 | [Fact] 15 | public async Task SendAsync_Should_Handle_Request() 16 | { 17 | var request = new MyRequestWithoutResponse(); 18 | 19 | await _sender.SendAsync(request, CancellationToken.None); 20 | } 21 | 22 | [Fact] 23 | public async Task SendAsync_Should_Handle_Request_WithResponse() 24 | { 25 | 26 | var request = new MyRequestWithResponse() 27 | { 28 | Name = "Baran" 29 | }; 30 | 31 | var result = await _sender.SendAsync(request, CancellationToken.None); 32 | 33 | Assert.True(result is string str && str.Equals("Baran")); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/IRequestHandler.cs: -------------------------------------------------------------------------------- 1 | namespace NimbleMediator.Contracts; 2 | 3 | /// 4 | /// Defines a request handler. 5 | /// 6 | /// 7 | /// 8 | public interface IRequestHandler 9 | where TRequest : IRequest 10 | where TResponse : notnull 11 | { 12 | /// 13 | /// Handles the request. 14 | /// 15 | /// 16 | /// 17 | /// 18 | ValueTask HandleAsync(TRequest request, CancellationToken cancellationToken); 19 | } 20 | 21 | public interface IRequestHandler 22 | where TRequest : IRequest 23 | { 24 | /// 25 | /// Handles the request. 26 | /// 27 | /// 28 | /// 29 | /// 30 | ValueTask HandleAsync(TRequest request, CancellationToken cancellationToken); 31 | } -------------------------------------------------------------------------------- /src/NimbleMediator/Contracts/ISender.cs: -------------------------------------------------------------------------------- 1 | namespace NimbleMediator.Contracts; 2 | 3 | /// 4 | /// Defines a request sender. 5 | /// 6 | public interface ISender 7 | { 8 | /// 9 | /// Sends the request. 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | ValueTask SendAsync(TRequest request, CancellationToken cancellationToken) 16 | where TRequest : IRequest; 17 | 18 | /// 19 | /// Sends the request. 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | ValueTask SendAsync(TRequest request, CancellationToken cancellationToken) 27 | where TRequest : IRequest 28 | where TResponse : notnull; 29 | } 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.0] & [1.0.1] 2 | 3 | - Initial release. 4 | 5 | ## [1.0.2] 6 | 7 | - Registered ISender and IPublisher also to the DI. This was forgotten in the initial release. 8 | 9 | ## [1.1.0] 10 | 11 | - Removed the type dictionary for SendAsync, now directly depends on the DI container to retrieve the handler. 12 | - Improved performance, now up to 3.5x faster and utilizes up to 16x less memory in certain cases. 13 | - Fixed the method call order dependency in NimbleMediatorConfig; it is now order-independent. 14 | - Overhauled notification handling; now supports notification publisher implementations via INotificationPublisher. Now maintains a Dictionary for notifications. 15 | - Added 3 default notification publishers: ForeachAwaitRobustPublisher, ForeachAwaitStopOnFirstExceptionPublisher, and TaskWhenAllPublisher. 16 | - Made minor changes to method names and signatures in NimbleMediatorConfig. 17 | 18 | ## [1.2.0] 19 | - Mediator implementation was singleton before, with this version it is **scoped** by default. 20 | - Added an api for explicitly setting the desired lifetime of the mediator. -------------------------------------------------------------------------------- /src/tests/NimbleMediator.Tests/NimbleMediator.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/MediatorsBenchmark.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.002.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediatorsBenchmark", "MediatorsBenchmark.csproj", "{68175B34-5100-4B68-AE19-B13F2FFF3910}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {68175B34-5100-4B68-AE19-B13F2FFF3910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {68175B34-5100-4B68-AE19-B13F2FFF3910}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {68175B34-5100-4B68-AE19-B13F2FFF3910}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {68175B34-5100-4B68-AE19-B13F2FFF3910}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {AB989479-AD47-48FD-984E-C3229604DC32} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/NimbleMediator/Implementations/Mediator.Publish.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NimbleMediator.Contracts; 3 | 4 | namespace NimbleMediator.Implementations; 5 | 6 | public partial class Mediator 7 | { 8 | /// 9 | /// Publishes a notification to all registered handlers. 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// Thrown when one or more notification handlers fail. 16 | public Task PublishAsync(TNotification notification, CancellationToken cancellationToken) 17 | where TNotification : INotification 18 | { 19 | if (!_publisherTypeMappings.TryGetValue(typeof(TNotification), out var publisherType)) 20 | { 21 | throw new InvalidOperationException($"No publisher type registered for {typeof(TNotification).Name}"); 22 | } 23 | 24 | var handlers = _serviceProvider.GetServices>(); 25 | 26 | var publisher = (INotificationPublisher)_serviceProvider.GetRequiredService(publisherType); 27 | 28 | return publisher.PublishAsync(notification, handlers, cancellationToken); 29 | } 30 | } -------------------------------------------------------------------------------- /src/NimbleMediator/Implementations/NotificationPublishers/TaskWhenAllPublisher.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace NimbleMediator.NotificationPublishers; 4 | 5 | public class TaskWhenAllPublisher : INotificationPublisher 6 | { 7 | public Task PublishAsync(TNotification notification, IEnumerable> handlers, CancellationToken cancellationToken) 8 | where TNotification : INotification 9 | { 10 | if (handlers is not INotificationHandler[] handlersArray) 11 | { 12 | throw new ArgumentException("The default Microsoft DI container should have returned an array of handlers."); 13 | } 14 | 15 | // If there is only one handler, 16 | // no need to allocate an array and no need to overhead of Task.WhenAll. 17 | if (handlersArray.Length == 1) 18 | { 19 | 20 | return handlersArray[0].HandleAsync(notification, cancellationToken); 21 | } 22 | 23 | // If there are more than one handlers, allocate an array and use Task.WhenAll. 24 | var tasks = new Task[handlersArray.Length]; 25 | 26 | for (int i = 0; i < handlersArray.Length; i++) 27 | { 28 | tasks[i] = handlersArray[i].HandleAsync(notification, cancellationToken); 29 | } 30 | 31 | return Task.WhenAll(tasks); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/NimbleMediator/Implementations/Mediator.Send.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NimbleMediator.Contracts; 3 | 4 | namespace NimbleMediator.Implementations; 5 | 6 | public partial class Mediator 7 | { 8 | /// 9 | /// Sends a request to the registered handler. 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | public ValueTask SendAsync(TRequest request, CancellationToken cancellationToken) 16 | where TRequest : IRequest 17 | { 18 | var handler = _serviceProvider.GetRequiredService>(); 19 | return handler.HandleAsync(request, cancellationToken); 20 | } 21 | 22 | /// 23 | /// Sends a request to the registered handler and returns the response. 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | public ValueTask SendAsync(TRequest request, CancellationToken cancellationToken) 31 | where TRequest : IRequest 32 | where TResponse : notnull 33 | { 34 | var handler = _serviceProvider.GetRequiredService>(); 35 | return handler.HandleAsync(request, cancellationToken); 36 | } 37 | } -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/Requests/MediatR.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MediatorsBenchmark; 4 | 5 | public record MediatRRequestWithoutResponse(string Name) : IRequest; 6 | 7 | public class MediatRRequestWithoutResponseHandler : IRequestHandler 8 | { 9 | public Task Handle(MediatRRequestWithoutResponse request, CancellationToken cancellationToken) 10 | { 11 | return Task.CompletedTask; 12 | } 13 | } 14 | 15 | public record MediatRRequestWithResponse(string Name) : IRequest; 16 | 17 | public class MediatRRequestWithResponseHandler : IRequestHandler 18 | { 19 | public Task Handle(MediatRRequestWithResponse request, CancellationToken cancellationToken) 20 | { 21 | return Task.FromResult(request.Name); 22 | } 23 | } 24 | 25 | public record MediatRRequestWithoutResponseThrowsException(string Name) : IRequest; 26 | 27 | public class MediatRRequestWithoutResponseThrowsExceptionHandler : IRequestHandler 28 | { 29 | public Task Handle(MediatRRequestWithoutResponseThrowsException request, CancellationToken cancellationToken) 30 | { 31 | throw new Exception("Benchmark"); 32 | } 33 | } 34 | 35 | public record MediatRRequestWithResponseThrowsException(string Name) : IRequest; 36 | 37 | public class MediatRRequestWithResponseThrowsExceptionHandler : IRequestHandler 38 | { 39 | public Task Handle(MediatRRequestWithResponseThrowsException request, CancellationToken cancellationToken) 40 | { 41 | throw new Exception("Benchmark"); 42 | } 43 | } -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/Requests/NimbleMediator.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace MediatorsBenchmark; 4 | 5 | public record NimbleMediatorRequestWithoutResponse(string Name) : IRequest; 6 | 7 | public class NimbleMediatorRequestWithoutResponseHandler : IRequestHandler 8 | { 9 | public ValueTask HandleAsync(NimbleMediatorRequestWithoutResponse request, CancellationToken cancellationToken) 10 | { 11 | return ValueTask.CompletedTask; 12 | } 13 | } 14 | 15 | public record NimbleMediatorRequestWithResponse(string Name) : IRequest; 16 | 17 | public class NimbleMediatorRequestWithResponseHandler : IRequestHandler 18 | { 19 | public ValueTask HandleAsync(NimbleMediatorRequestWithResponse request, CancellationToken cancellationToken) 20 | { 21 | return ValueTask.FromResult(request.Name); 22 | } 23 | } 24 | 25 | public record NimbleMediatorRequestWithoutResponseThrowsException(string Name) : IRequest; 26 | 27 | public class NimbleMediatorRequestWithoutResponseThrowsExceptionHandler : IRequestHandler 28 | { 29 | public ValueTask HandleAsync(NimbleMediatorRequestWithoutResponseThrowsException request, CancellationToken cancellationToken) 30 | { 31 | throw new Exception("Benchmark"); 32 | } 33 | } 34 | 35 | public record NimbleMediatorRequestWithResponseThrowsException(string Name) : IRequest; 36 | 37 | public class NimbleMediatorRequestWithResponseThrowsExceptionHandler : IRequestHandler 38 | { 39 | public ValueTask HandleAsync(NimbleMediatorRequestWithResponseThrowsException request, CancellationToken cancellationToken) 40 | { 41 | throw new Exception("Benchmark"); 42 | } 43 | } -------------------------------------------------------------------------------- /samples/NimbleMediator.Samples.ForeachAwaitRobustPublisher/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | using Microsoft.Extensions.DependencyInjection; 3 | using NimbleMediator.Contracts; 4 | using NimbleMediator.NotificationPublishers; 5 | using NimbleMediator.ServiceExtensions; 6 | 7 | Console.WriteLine("Hello, World!"); 8 | 9 | var services = new ServiceCollection(); 10 | 11 | services.AddNimbleMediator(config => 12 | { 13 | config.SetDefaultNotificationPublisher(); 14 | config.RegisterServicesFromAssembly(typeof(MyNotification).Assembly); 15 | }); 16 | 17 | var provider = services.BuildServiceProvider(); 18 | 19 | var mediator = provider.GetRequiredService(); 20 | 21 | try 22 | { 23 | await mediator.PublishAsync(new MyNotification(), CancellationToken.None); 24 | } 25 | catch (AggregateException ae) 26 | { 27 | Console.WriteLine("AggregateException:"); 28 | foreach (var e in ae.InnerExceptions) 29 | { 30 | Console.WriteLine(e.Message); 31 | } 32 | } 33 | catch (Exception e) 34 | { 35 | Console.WriteLine("Exception:"); 36 | Console.WriteLine(e.Message); 37 | } 38 | 39 | public class MyNotification : INotification 40 | { 41 | public string Name { get; set; } 42 | } 43 | 44 | public class MyNotificationHandler1 : INotificationHandler 45 | { 46 | public Task HandleAsync(MyNotification notification, CancellationToken cancellationToken) 47 | { 48 | Console.WriteLine($"Handling notification from {nameof(MyNotificationHandler1)}"); 49 | return Task.CompletedTask; 50 | } 51 | } 52 | 53 | public class MyNotificationHandler2 : INotificationHandler 54 | { 55 | public Task HandleAsync(MyNotification notification, CancellationToken cancellationToken) 56 | { 57 | throw new Exception("Exception from MyNotificationHandler2"); 58 | } 59 | } 60 | 61 | public class MyNotificationHandler3 : INotificationHandler 62 | { 63 | public Task HandleAsync(MyNotification notification, CancellationToken cancellationToken) 64 | { 65 | Console.WriteLine($"Handling notification from {nameof(MyNotificationHandler3)}"); 66 | return Task.CompletedTask; 67 | } 68 | } -------------------------------------------------------------------------------- /samples/NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using NimbleMediator.Contracts; 3 | using NimbleMediator.NotificationPublishers; 4 | using NimbleMediator.ServiceExtensions; 5 | 6 | var services = new ServiceCollection(); 7 | 8 | services.AddNimbleMediator(config => 9 | { 10 | config.SetDefaultNotificationPublisher(); 11 | config.RegisterServicesFromAssembly(typeof(MyNotification).Assembly); 12 | }); 13 | 14 | var provider = services.BuildServiceProvider(); 15 | 16 | var mediator = provider.GetRequiredService(); 17 | 18 | try 19 | { 20 | await mediator.PublishAsync(new MyNotification(), CancellationToken.None); 21 | } 22 | catch (AggregateException ae) 23 | { 24 | // This will not happen because the publisher stops on first exception. 25 | Console.WriteLine("AggregateException:"); 26 | foreach (var e in ae.InnerExceptions) 27 | { 28 | Console.WriteLine(e.Message); 29 | } 30 | } 31 | catch (Exception e) 32 | { 33 | // This is the expected exception. 34 | 35 | Console.WriteLine("Exception:"); 36 | Console.WriteLine(e.Message); 37 | } 38 | 39 | public class MyNotification : INotification 40 | { 41 | public string Name { get; set; } 42 | } 43 | 44 | public class MyNotificationHandler1 : INotificationHandler 45 | { 46 | public Task HandleAsync(MyNotification notification, CancellationToken cancellationToken) 47 | { 48 | Console.WriteLine($"Handling notification from {nameof(MyNotificationHandler1)}"); 49 | return Task.CompletedTask; 50 | } 51 | } 52 | 53 | public class MyNotificationHandler2 : INotificationHandler 54 | { 55 | public Task HandleAsync(MyNotification notification, CancellationToken cancellationToken) 56 | { 57 | throw new Exception("Exception from MyNotificationHandler2"); 58 | } 59 | } 60 | 61 | public class MyNotificationHandler3 : INotificationHandler 62 | { 63 | public Task HandleAsync(MyNotification notification, CancellationToken cancellationToken) 64 | { 65 | Console.WriteLine($"Handling notification from {nameof(MyNotificationHandler3)}"); 66 | return Task.CompletedTask; 67 | } 68 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to NimbleMediator 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | 10 | ## We Develop with Github 11 | 12 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 13 | 14 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html) 15 | 16 | Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests. 17 | 18 | 1. Fork the repo and create your branch from `main`. 19 | 2. If you've added code that should be tested, add tests. 20 | 3. If you've changed APIs, update the documentation. 21 | 4. Ensure the test suite passes. 22 | 5. Make sure your code lints. 23 | 6. Issue that pull request! 24 | 25 | ## Commit Message Format 26 | 27 | We follow a specific commit message format. This leads to more readable messages that are easy to follow when looking through the project history. 28 | 29 | Each commit message consists of a **header**, and a **body** and/or a **footer**. 30 | 31 | **Example: ``[Tag]: Short description (fixes #1234)``** 32 | 33 | The `Tag` is one of the following: 34 | 35 | - **Feat** - for new features 36 | - **Fix** - for bug fixes 37 | - **Refactor** - for code that does not add a feature or fix a bug 38 | - **Perf** - for code that improves performance 39 | - **Style** - for code that does not alter how the code runs, just improves formatting and similar. 40 | - **Docs** - for changes to documentation only 41 | - **Test** - for adding missing tests or correcting existing tests 42 | - **CI** - for changes to CI configuration files and scripts 43 | - **Chore** - for repetitive tasks such as updating dependencies and so on. 44 | 45 | ## Use a Consistent Coding Style 46 | 47 | Let's ensure our codebase is consistent and clean. 48 | 49 | ## Report bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues) 50 | 51 | We use GitHub issues to track public bugs. Report a bug by opening a new issue. 52 | 53 | ## Write bug reports with detail, background, and sample code 54 | 55 | **Great Bug Reports** tend to have: 56 | 57 | - A quick summary and/or background 58 | - Steps to reproduce 59 | - Be specific! 60 | - Give sample code if you can. 61 | - What you expected would happen 62 | - What actually happens 63 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 64 | -------------------------------------------------------------------------------- /src/NimbleMediator/NimbleMediator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | latest 6 | enable 7 | enable 8 | latest 9 | all 10 | all 11 | true 12 | true 13 | true 14 | CA1040 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | NimbleMediator 31 | 1.2.0 32 | Baran Açıkgöz 33 | Significantly faster and memory optimized mediator implementation. 34 | True 35 | NimbleMediator 36 | https://github.com/baranacikgoz/NimbleMediator/blob/main/LICENSE 37 | https://github.com/baranacikgoz/NimbleMediator 38 | README.md 39 | https://github.com/baranacikgoz/NimbleMediator 40 | git 41 | mediator;Mediator;mediatr;MediatR;Nimble;NimbleMediator 42 | LICENSE 43 | True 44 | logo_128.png 45 | 46 | 47 | - Mediator implementation was singleton before, with this version it is scoped by default. 48 | - Added an api for explicitly setting the desired lifetime of the mediator. 49 | 50 | 51 | 52 | 53 | 54 | 55 | runtime; build; native; contentfiles; analyzers; buildtransitive 56 | all 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/NimbleMediator/Implementations/NotificationPublishers/ForeachAwaitStopOnFirstExceptionPublisher.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace NimbleMediator.NotificationPublishers; 4 | 5 | /// 6 | /// The publisher that stops on first exception, not guarantees that all handlers are executed. 7 | /// If you want to ensure that all handlers are executed, use . 8 | /// It is relatively faster and less memory consuming due it's fail-fast nature. 9 | /// 10 | public class ForeachAwaitStopOnFirstExceptionPublisher : INotificationPublisher 11 | { 12 | /// 13 | /// Publishes a notification to all registered handlers. 14 | /// Stops on first exception, not guarantees that all handlers are executed. 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// May throw an exception if an handler throws an exception. 23 | /// Does not wraps the exception with a different type of exception, throws directly. 24 | /// 25 | public Task PublishAsync(TNotification notification, IEnumerable> handlers, CancellationToken cancellationToken) 26 | where TNotification : INotification 27 | { 28 | if (handlers is not INotificationHandler[] handlersArray) 29 | { 30 | throw new ArgumentException("The default Microsoft DI container should have returned an array of handlers."); 31 | } 32 | 33 | // If there is only one handler, no need for a loop's overhead. 34 | if (handlersArray.Length == 1) 35 | { 36 | return handlersArray[0].HandleAsync(notification, cancellationToken); 37 | } 38 | 39 | return PublishAsyncInternal(notification, handlersArray, cancellationToken); 40 | } 41 | 42 | // This method is extracted to avoid call 'await' inside the PublishAsync method, 43 | // thus preventing the creation of an additional state machine and reducing overhead. 44 | // The state machine will only be created when the user calls the PublishAsync method outside of the library. 45 | // Yes, it seems like even beyond micro-optimization, but makes difference in benchmarks and high-throughput scenarios. 46 | private static async Task PublishAsyncInternal(TNotification notification, INotificationHandler[] handlersArray, CancellationToken cancellationToken) 47 | where TNotification : INotification 48 | { 49 | for (int i = 0; i < handlersArray.Length; i++) 50 | { 51 | await handlersArray[i].HandleAsync(notification, cancellationToken).ConfigureAwait(false); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/Notifications/MediatR.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MediatorsBenchmark; 4 | 5 | public record MediatRNotificationWith1Handler(string Name) : INotification; 6 | 7 | public class MediatRNotificationWith1HandlerHandler : INotificationHandler 8 | { 9 | public Task Handle(MediatRNotificationWith1Handler notification, CancellationToken cancellationToken) 10 | { 11 | return Task.CompletedTask; 12 | } 13 | } 14 | 15 | public record MediatRNotificationWith1HandlerThrowsException(string Name) : INotification; 16 | 17 | public class MediatRNotificationWith1HandlerThrowsExceptionHandler : INotificationHandler 18 | { 19 | public Task Handle(MediatRNotificationWith1HandlerThrowsException notification, CancellationToken cancellationToken) 20 | { 21 | throw new Exception("Benchmark"); 22 | } 23 | } 24 | 25 | public record MediatRNotificationWith3Handlers(string Name) : INotification; 26 | 27 | public class MediatRNotificationWith3HandlersHandler1 : INotificationHandler 28 | { 29 | public Task Handle(MediatRNotificationWith3Handlers notification, CancellationToken cancellationToken) 30 | { 31 | return Task.CompletedTask; 32 | } 33 | } 34 | 35 | public class MediatRNotificationWith3HandlersHandler2 : INotificationHandler 36 | { 37 | public Task Handle(MediatRNotificationWith3Handlers notification, CancellationToken cancellationToken) 38 | { 39 | return Task.CompletedTask; 40 | } 41 | } 42 | 43 | public class MediatRNotificationWith3HandlersHandler3 : INotificationHandler 44 | { 45 | public Task Handle(MediatRNotificationWith3Handlers notification, CancellationToken cancellationToken) 46 | { 47 | return Task.CompletedTask; 48 | } 49 | } 50 | 51 | public record MediatRNotificationWith3Handlers1ThrowsException(string Name) : INotification; 52 | 53 | public class MediatRNotificationWith3Handlers1ThrowsExceptionHandler1 : INotificationHandler 54 | { 55 | public Task Handle(MediatRNotificationWith3Handlers1ThrowsException notification, CancellationToken cancellationToken) 56 | { 57 | return Task.CompletedTask; 58 | } 59 | } 60 | 61 | public class MediatRNotificationWith3Handlers1ThrowsExceptionHandler2 : INotificationHandler 62 | { 63 | public Task Handle(MediatRNotificationWith3Handlers1ThrowsException notification, CancellationToken cancellationToken) 64 | { 65 | throw new Exception("Benchmark"); 66 | } 67 | } 68 | 69 | public record MediatRNotificationWith3Handlers1ThrowsExceptionHandler3 : INotificationHandler 70 | { 71 | public Task Handle(MediatRNotificationWith3Handlers1ThrowsException notification, CancellationToken cancellationToken) 72 | { 73 | return Task.CompletedTask; 74 | } 75 | } -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/Notifications/NimbleMediator.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace MediatorsBenchmark; 4 | 5 | public record NimbleMediatorNotificationWith1Handler(string Name) : INotification; 6 | 7 | public class NimbleMediatorNotificationWith1HandlerHandler : INotificationHandler 8 | { 9 | public Task HandleAsync(NimbleMediatorNotificationWith1Handler notification, CancellationToken cancellationToken) 10 | { 11 | return Task.CompletedTask; 12 | } 13 | } 14 | 15 | public record NimbleMediatorNotificationWith1HandlerThrowsException(string Name) : INotification; 16 | 17 | public class NimbleMediatorNotificationWith1HandlerThrowsExceptionHandler : INotificationHandler 18 | { 19 | public Task HandleAsync(NimbleMediatorNotificationWith1HandlerThrowsException notification, CancellationToken cancellationToken) 20 | { 21 | throw new Exception("Benchmark"); 22 | } 23 | } 24 | 25 | public record NimbleMediatorNotificationWith3Handlers(string Name) : INotification; 26 | 27 | public class NimbleMediatorNotificationWith3HandlersHandler1 : INotificationHandler 28 | { 29 | public Task HandleAsync(NimbleMediatorNotificationWith3Handlers notification, CancellationToken cancellationToken) 30 | { 31 | return Task.CompletedTask; 32 | } 33 | } 34 | 35 | public class NimbleMediatorNotificationWith3HandlersHandler2 : INotificationHandler 36 | { 37 | public Task HandleAsync(NimbleMediatorNotificationWith3Handlers notification, CancellationToken cancellationToken) 38 | { 39 | return Task.CompletedTask; 40 | } 41 | } 42 | 43 | public class NimbleMediatorNotificationWith3HandlersHandler3 : INotificationHandler 44 | { 45 | public Task HandleAsync(NimbleMediatorNotificationWith3Handlers notification, CancellationToken cancellationToken) 46 | { 47 | return Task.CompletedTask; 48 | } 49 | } 50 | 51 | public record NimbleMediatorNotificationWith3Handlers1ThrowsException(string Name) : INotification; 52 | 53 | public class NimbleMediatorNotificationWith3Handlers1ThrowsExceptionHandler1 : INotificationHandler 54 | { 55 | public Task HandleAsync(NimbleMediatorNotificationWith3Handlers1ThrowsException notification, CancellationToken cancellationToken) 56 | { 57 | return Task.CompletedTask; 58 | } 59 | } 60 | 61 | public class NimbleMediatorNotificationWith3Handlers1ThrowsExceptionHandler2 : INotificationHandler 62 | { 63 | public Task HandleAsync(NimbleMediatorNotificationWith3Handlers1ThrowsException notification, CancellationToken cancellationToken) 64 | { 65 | throw new Exception("Benchmark"); 66 | } 67 | } 68 | 69 | public class NimbleMediatorNotificationWith3Handlers1ThrowsExceptionHandler3 : INotificationHandler 70 | { 71 | public Task HandleAsync(NimbleMediatorNotificationWith3Handlers1ThrowsException notification, CancellationToken cancellationToken) 72 | { 73 | return Task.CompletedTask; 74 | } 75 | } -------------------------------------------------------------------------------- /src/NimbleMediator/Implementations/NotificationPublishers/ForeachAwaitRobustPublisher.cs: -------------------------------------------------------------------------------- 1 | using NimbleMediator.Contracts; 2 | 3 | namespace NimbleMediator.NotificationPublishers; 4 | 5 | /// 6 | /// The robust publisher that does not stop publishing when one or more handlers fail ensuring that all handlers are executed. 7 | /// It is relatively slower and more memory consuming due it's robust nature. 8 | /// 9 | public class ForeachAwaitRobustPublisher : INotificationPublisher 10 | { 11 | private const string _aggregateExceptionMessage = "One or more notification handlers failed."; 12 | 13 | /// 14 | /// Publishes a notification to all registered handlers. 15 | /// Does not stop publishing when one or more handlers fail ensuring that all handlers are executed. 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// Thrown when more than one handler is registered and more than one notification handlers fail. 23 | /// 24 | /// If only a single handler is registered and it throws an exception, that exception will be thrown directly, not wrapped in an AggregateException. 25 | /// 26 | public Task PublishAsync(TNotification notification, IEnumerable> handlers, CancellationToken cancellationToken) 27 | where TNotification : INotification 28 | { 29 | if (handlers is not INotificationHandler[] handlersArray) 30 | { 31 | throw new ArgumentException("The default Microsoft DI container should have returned an array of handlers."); 32 | } 33 | 34 | // If there is only one handler, 35 | // no need for a loop's overhead. 36 | if (handlersArray.Length == 1) 37 | { 38 | return handlersArray[0].HandleAsync(notification, cancellationToken); 39 | } 40 | 41 | return PublishAsyncInternal(notification, handlersArray, cancellationToken); 42 | } 43 | 44 | // This method is extracted to avoid call 'await' inside the PublishAsync method, 45 | // thus preventing the creation of an additional state machine and reducing overhead. 46 | // The state machine will only be created when the user calls the PublishAsync method outside of the library. 47 | // Yes, it seems like even beyond micro-optimization, but makes difference in benchmarks and high-throughput scenarios. 48 | private static async Task PublishAsyncInternal(TNotification notification, INotificationHandler[] handlersArray, CancellationToken cancellationToken) where TNotification : INotification 49 | { 50 | List? exceptions = null; 51 | 52 | for (int i = 0; i < handlersArray.Length; i++) 53 | { 54 | try 55 | { 56 | await handlersArray[i].HandleAsync(notification, cancellationToken).ConfigureAwait(false); 57 | } 58 | catch (Exception ex) 59 | { 60 | #pragma warning disable CA1508 // Compiler claims that the exceptions will always be null, but it is not. 61 | exceptions ??= new List(handlersArray.Length); 62 | #pragma warning restore CA1508 63 | 64 | exceptions.Add(ex); 65 | } 66 | } 67 | 68 | if (exceptions is not null) 69 | { 70 | throw new AggregateException(_aggregateExceptionMessage, exceptions); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/SendBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using NimbleMediator; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using NimbleMediator.ServiceExtensions; 5 | using NimbleMediator.Implementations; 6 | using MediatR.NotificationPublishers; 7 | using BenchmarkDotNet.Configs; 8 | 9 | namespace MediatorsBenchmark; 10 | 11 | [MemoryDiagnoser] 12 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 13 | [HideColumns(new string[] { "Error", "StdDev", "RatioSD", "Gen0" })] 14 | public class SendBenchmark 15 | { 16 | public SendBenchmark() 17 | { 18 | var services = new ServiceCollection(); 19 | 20 | services.AddNimbleMediator(config => 21 | { 22 | config.RegisterServicesFromAssembly(typeof(NimbleMediatorRequestWithoutResponse).Assembly); 23 | }); 24 | 25 | services.AddMediatR(cfg => 26 | { 27 | cfg.RegisterServicesFromAssembly(typeof(MediatRRequestWithoutResponse).Assembly); 28 | }); 29 | 30 | var provider = services.BuildServiceProvider(); 31 | 32 | _mediatR = provider.GetRequiredService(); 33 | _nimbleMediator = provider.GetRequiredService(); 34 | } 35 | private readonly MediatR.IMediator _mediatR; 36 | private readonly NimbleMediator.Contracts.IMediator _nimbleMediator; 37 | 38 | private readonly NimbleMediatorRequestWithoutResponse _nimbleMediatorRequestWithoutResponse = new("Test"); 39 | private readonly NimbleMediatorRequestWithResponse _nimbleMediatorRequestWithResponse = new("Test"); 40 | private readonly MediatRRequestWithoutResponse _mediatRRequestWithoutResponse = new("Test"); 41 | private readonly MediatRRequestWithResponse _mediatRRequestWithResponse = new("Test"); 42 | private readonly NimbleMediatorRequestWithoutResponseThrowsException _nimbleMediatorRequestWithoutResponseThrowsException = new("Test"); 43 | private readonly MediatRRequestWithoutResponseThrowsException _mediatRRequestWithoutResponseThrowsException = new("Test"); 44 | private readonly NimbleMediatorRequestWithResponseThrowsException _nimbleMediatorRequestWithResponseThrowsException = new("Test"); 45 | private readonly MediatRRequestWithResponseThrowsException _mediatRRequestWithResponseThrowsException = new("Test"); 46 | 47 | 48 | [BenchmarkCategory("1"), Benchmark(Baseline = true)] 49 | public async ValueTask NimbleMediator_Send_WithoutResponse() 50 | { 51 | await _nimbleMediator.SendAsync(_nimbleMediatorRequestWithoutResponse, CancellationToken.None); 52 | } 53 | 54 | [BenchmarkCategory("1"), Benchmark] 55 | public async ValueTask MediatR_Send_WithoutResponse() 56 | { 57 | await _mediatR.Send(_mediatRRequestWithoutResponse, CancellationToken.None); 58 | } 59 | 60 | [BenchmarkCategory("2"), Benchmark(Baseline = true)] 61 | public async ValueTask NimbleMediator_Send_WithResponse() 62 | { 63 | return await _nimbleMediator.SendAsync(_nimbleMediatorRequestWithResponse, CancellationToken.None); 64 | } 65 | 66 | [BenchmarkCategory("2"), Benchmark] 67 | public async Task MediatR_Send_WithResponse() 68 | { 69 | return await _mediatR.Send(_mediatRRequestWithResponse, CancellationToken.None); 70 | } 71 | 72 | [BenchmarkCategory("3"), Benchmark(Baseline = true)] 73 | public async Task NimbleMediator_Send_WithoutResponse_ThrowsException() 74 | { 75 | try 76 | { 77 | await _nimbleMediator.SendAsync(_nimbleMediatorRequestWithoutResponseThrowsException, CancellationToken.None); 78 | } 79 | catch 80 | { 81 | // ignored 82 | } 83 | } 84 | 85 | [BenchmarkCategory("3"), Benchmark] 86 | public async Task MediatR_Send_WithoutResponse_ThrowsException() 87 | { 88 | try 89 | { 90 | await _mediatR.Send(_mediatRRequestWithoutResponseThrowsException, CancellationToken.None); 91 | } 92 | catch 93 | { 94 | // ignored 95 | } 96 | } 97 | 98 | [BenchmarkCategory("4"), Benchmark(Baseline = true)] 99 | public async Task NimbleMediator_Send_WithResponse_ThrowsException() 100 | { 101 | try 102 | { 103 | return await _nimbleMediator.SendAsync(_nimbleMediatorRequestWithResponseThrowsException, CancellationToken.None); 104 | } 105 | catch 106 | { 107 | return "Test"; 108 | } 109 | } 110 | 111 | [BenchmarkCategory("4"), Benchmark] 112 | public async Task MediatR_Send_WithResponse_ThrowsException() 113 | { 114 | try 115 | { 116 | return await _mediatR.Send(_mediatRRequestWithResponseThrowsException, CancellationToken.None); 117 | } 118 | catch 119 | { 120 | return "Test"; 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/ForeachAwaitBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | using MediatorsBenchmark; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using NimbleMediator; 6 | using NimbleMediator.NotificationPublishers; 7 | using NimbleMediator.ServiceExtensions; 8 | 9 | namespace MediatorsBenchmark; 10 | 11 | [MemoryDiagnoser] 12 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 13 | [HideColumns(new string[] { "Error", "StdDev", "RatioSD", "Gen0" })] 14 | public class ForeachAwaitBenchmark 15 | { 16 | public ForeachAwaitBenchmark() 17 | { 18 | var services = new ServiceCollection(); 19 | 20 | services.AddNimbleMediator(config => 21 | { 22 | config.SetDefaultNotificationPublisher(); 23 | config.RegisterServicesFromAssembly(typeof(NimbleMediatorRequestWithoutResponse).Assembly); 24 | }); 25 | 26 | services.AddMediatR(cfg => 27 | { 28 | cfg.RegisterServicesFromAssembly(typeof(MediatRRequestWithoutResponse).Assembly); 29 | cfg.NotificationPublisher = new MediatR.NotificationPublishers.ForeachAwaitPublisher(); 30 | }); 31 | 32 | var provider = services.BuildServiceProvider(); 33 | 34 | _mediatR = provider.GetRequiredService(); 35 | _nimbleMediator = provider.GetRequiredService(); 36 | } 37 | 38 | private readonly MediatR.IMediator _mediatR; 39 | private readonly NimbleMediator.Contracts.IMediator _nimbleMediator; 40 | 41 | private readonly NimbleMediatorNotificationWith1Handler _nimbleMediatorNotificationWith1Handler = new("Test"); 42 | private readonly NimbleMediatorNotificationWith1HandlerThrowsException _nimbleMediatorNotificationWith1HandlerThrowsException = new("Test"); 43 | private readonly NimbleMediatorNotificationWith3Handlers _nimbleMediatorNotificationWith3Handlers = new("Test"); 44 | private readonly NimbleMediatorNotificationWith3Handlers1ThrowsException _nimbleMediatorNotificationWith3Handlers1ThrowsException = new("Test"); 45 | private readonly MediatRNotificationWith1Handler _mediatRNotificationWith1Handler = new("Test"); 46 | private readonly MediatRNotificationWith1HandlerThrowsException _mediatRNotificationWith1HandlerThrowsException = new("Test"); 47 | private readonly MediatRNotificationWith3Handlers _mediatRNotificationWith3Handlers = new("Test"); 48 | private readonly MediatRNotificationWith3Handlers1ThrowsException _mediatRNotificationWith3Handlers1ThrowsException = new("Test"); 49 | 50 | [BenchmarkCategory("1"), Benchmark(Baseline = true)] 51 | public async Task NimbleMediator_Publish_ForeachAwait_notification_has_1_handler() 52 | { 53 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith1Handler, CancellationToken.None); 54 | } 55 | 56 | [BenchmarkCategory("1"), Benchmark] 57 | public async Task MediatR_Publish_ForeachAwait_notification_has_1_handler() 58 | { 59 | await _mediatR.Publish(_mediatRNotificationWith1Handler, CancellationToken.None); 60 | } 61 | 62 | [BenchmarkCategory("2"), Benchmark(Baseline = true)] 63 | public async Task NimbleMediator_Publish_ForeachAwait_notification_has_1_handler_ThrowsException() 64 | { 65 | try 66 | { 67 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith1HandlerThrowsException, CancellationToken.None); 68 | } 69 | catch (Exception) 70 | { 71 | // ignored 72 | } 73 | } 74 | 75 | [BenchmarkCategory("2"), Benchmark] 76 | public async Task MediatR_Publish_ForeachAwait_notification_has_1_handler_ThrowsException() 77 | { 78 | try 79 | { 80 | await _mediatR.Publish(_mediatRNotificationWith1HandlerThrowsException, CancellationToken.None); 81 | } 82 | catch (Exception) 83 | { 84 | // ignored 85 | } 86 | } 87 | 88 | [BenchmarkCategory("3"), Benchmark(Baseline = true)] 89 | public async Task NimbleMediator_Publish_ForeachAwait_notification_has_3_handlers() 90 | { 91 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith3Handlers, CancellationToken.None); 92 | } 93 | 94 | [BenchmarkCategory("3"), Benchmark] 95 | public async Task MediatR_Publish_ForeachAwait_notification_has_3_handlers() 96 | { 97 | await _mediatR.Publish(_mediatRNotificationWith3Handlers, CancellationToken.None); 98 | } 99 | 100 | [BenchmarkCategory("4"), Benchmark(Baseline = true)] 101 | public async Task NimbleMediator_Publish_ForeachAwait_notification_has_3_handlers_1_throws_exception() 102 | { 103 | try 104 | { 105 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith3Handlers1ThrowsException, CancellationToken.None); 106 | } 107 | catch (Exception) 108 | { 109 | // ignored 110 | } 111 | } 112 | 113 | [BenchmarkCategory("4"), Benchmark] 114 | public async Task MediatR_Publish_ForeachAwait_notification_has_3_handlers_1_throws_exception() 115 | { 116 | try 117 | { 118 | await _mediatR.Publish(_mediatRNotificationWith3Handlers1ThrowsException, CancellationToken.None); 119 | } 120 | catch (Exception) 121 | { 122 | // ignored 123 | } 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /benchmarks/MediatorsBenchmark/TaskWhenAllBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using NimbleMediator; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using NimbleMediator.ServiceExtensions; 5 | using NimbleMediator.Implementations; 6 | using MediatR.NotificationPublishers; 7 | using BenchmarkDotNet.Configs; 8 | 9 | namespace MediatorsBenchmark; 10 | 11 | [MemoryDiagnoser] 12 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 13 | [HideColumns(new string[] { "Error", "StdDev", "RatioSD", "Gen0" })] 14 | public class TaskWhenAllBenchmark 15 | { 16 | public TaskWhenAllBenchmark() 17 | { 18 | var services = new ServiceCollection(); 19 | 20 | services.AddNimbleMediator(config => 21 | { 22 | config.SetDefaultNotificationPublisher(); 23 | config.RegisterServicesFromAssembly(typeof(NimbleMediatorRequestWithoutResponse).Assembly); 24 | 25 | }); 26 | 27 | services.AddMediatR(cfg => 28 | { 29 | cfg.RegisterServicesFromAssembly(typeof(MediatRRequestWithoutResponse).Assembly); 30 | cfg.NotificationPublisher = new MediatR.NotificationPublishers.TaskWhenAllPublisher(); 31 | }); 32 | 33 | var provider = services.BuildServiceProvider(); 34 | 35 | _mediatR = provider.GetRequiredService(); 36 | _nimbleMediator = provider.GetRequiredService(); 37 | } 38 | private readonly MediatR.IMediator _mediatR; 39 | private readonly NimbleMediator.Contracts.IMediator _nimbleMediator; 40 | private readonly NimbleMediatorNotificationWith1Handler _nimbleMediatorNotificationWith1Handler = new("Test"); 41 | private readonly NimbleMediatorNotificationWith1HandlerThrowsException _nimbleMediatorNotificationWith1HandlerThrowsException = new("Test"); 42 | private readonly NimbleMediatorNotificationWith3Handlers _nimbleMediatorNotificationWith3Handlers = new("Test"); 43 | private readonly NimbleMediatorNotificationWith3Handlers1ThrowsException _nimbleMediatorNotificationWith3Handlers1ThrowsException = new("Test"); 44 | private readonly MediatRNotificationWith1Handler _mediatRNotificationWithSingleHandler = new("Test"); 45 | private readonly MediatRNotificationWith1HandlerThrowsException _mediatRNotificationWith1HandlerThrowsException = new("Test"); 46 | private readonly MediatRNotificationWith3Handlers _mediatRNotificationWith3Handlers = new("Test"); 47 | private readonly MediatRNotificationWith3Handlers1ThrowsException _mediatRNotificationWith3Handlers1ThrowsException = new("Test"); 48 | 49 | [BenchmarkCategory("1"), Benchmark(Baseline = true)] 50 | public async Task NimbleMediator_Publish_TaskWhenAll_notification_has_1_handler() 51 | { 52 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith1Handler, CancellationToken.None); 53 | } 54 | 55 | [BenchmarkCategory("1"), Benchmark] 56 | public async Task MediatR_Publish_TaskWhenAll_notification_has_1_handler() 57 | { 58 | await _mediatR.Publish(_mediatRNotificationWithSingleHandler, CancellationToken.None); 59 | } 60 | 61 | [BenchmarkCategory("2"), Benchmark(Baseline = true)] 62 | public async Task NimbleMediator_Publish_TaskWhenAll_notification_has_1_handler_ThrowsException() 63 | { 64 | try 65 | { 66 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith1HandlerThrowsException, CancellationToken.None); 67 | } 68 | catch (Exception) 69 | { 70 | // ignored 71 | } 72 | } 73 | 74 | [BenchmarkCategory("2"), Benchmark] 75 | public async Task MediatR_Publish_TaskWhenAll_notification_has_1_handler_ThrowsException() 76 | { 77 | try 78 | { 79 | await _mediatR.Publish(_mediatRNotificationWith1HandlerThrowsException, CancellationToken.None); 80 | } 81 | catch (Exception) 82 | { 83 | // ignored 84 | } 85 | } 86 | 87 | [BenchmarkCategory("3"), Benchmark(Baseline = true)] 88 | public async Task NimbleMediator_Publish_TaskWhenAll_notification_has_3_handlers() 89 | { 90 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith3Handlers, CancellationToken.None); 91 | } 92 | 93 | [BenchmarkCategory("3"), Benchmark] 94 | public async Task MediatR_Publish_TaskWhenAll_notification_has_3_handlers() 95 | { 96 | await _mediatR.Publish(_mediatRNotificationWith3Handlers, CancellationToken.None); 97 | } 98 | 99 | [BenchmarkCategory("4"), Benchmark(Baseline = true)] 100 | public async Task NimbleMediator_Publish_TaskWhenAll_notification_has_3_handlers_1_ThrowsException() 101 | { 102 | try 103 | { 104 | await _nimbleMediator.PublishAsync(_nimbleMediatorNotificationWith3Handlers1ThrowsException, CancellationToken.None); 105 | } 106 | catch (Exception) 107 | { 108 | // ignored 109 | } 110 | } 111 | 112 | [BenchmarkCategory("4"), Benchmark] 113 | public async Task MediatR_Publish_TaskWhenAll_notification_has_3_handlers_1_ThrowsException() 114 | { 115 | try 116 | { 117 | await _mediatR.Publish(_mediatRNotificationWith3Handlers1ThrowsException, CancellationToken.None); 118 | } 119 | catch (Exception) 120 | { 121 | // ignored 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /NimbleMediator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34009.444 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{06B6F282-1C5D-4E62-B583-93D010553BFC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NimbleMediator", "src\NimbleMediator\NimbleMediator.csproj", "{203AEF6B-8DE8-4B2C-A3A4-9522A458F41C}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{3379F0DA-B4C0-447C-B406-AE0F93E5510C}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{A2AD59B6-3D96-441B-88F8-79CD2C9EEBF7}" 13 | ProjectSection(SolutionItems) = preProject 14 | benchmarks\publish_foreachawait_benchmark.png = benchmarks\publish_foreachawait_benchmark.png 15 | benchmarks\publish_taskwhenall_benchmark.png = benchmarks\publish_taskwhenall_benchmark.png 16 | benchmarks\send_benchmark.png = benchmarks\send_benchmark.png 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediatorsBenchmark", "benchmarks\MediatorsBenchmark\MediatorsBenchmark.csproj", "{749D1503-5255-4C90-BFF9-7FAD135C4AE1}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{42714D44-2B41-40C6-8E9C-AD88D1CCEF12}" 22 | ProjectSection(SolutionItems) = preProject 23 | assets\logo.png = assets\logo.png 24 | assets\logo_128.png = assets\logo_128.png 25 | EndProjectSection 26 | EndProject 27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F82D56AF-9485-4A4E-8145-99B761FB110B}" 28 | ProjectSection(SolutionItems) = preProject 29 | .editorconfig = .editorconfig 30 | .gitignore = .gitignore 31 | CONTRIBUTING.md = CONTRIBUTING.md 32 | LICENSE = LICENSE 33 | README.md = README.md 34 | CHANGELOG.md = CHANGELOG.md 35 | EndProjectSection 36 | EndProject 37 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NimbleMediator.Tests", "src\tests\NimbleMediator.Tests\NimbleMediator.Tests.csproj", "{6EE21F4D-7B75-488F-88BC-89FBEEC5E816}" 38 | EndProject 39 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B9C29E78-160E-45E7-AC68-F06B59D4659F}" 40 | EndProject 41 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NimbleMediator.Samples.ForeachAwaitRobustPublisher", "samples\NimbleMediator.Samples.ForeachAwaitRobustPublisher\NimbleMediator.Samples.ForeachAwaitRobustPublisher.csproj", "{8349BD67-E9D0-457E-85CE-4B666654D55D}" 42 | EndProject 43 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher", "samples\NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher\NimbleMediator.Samples.ForechAwaitStopOnFirstExceptionPublisher.csproj", "{EF0E0F07-7884-4EF7-B9AC-C7967DCA43A9}" 44 | EndProject 45 | Global 46 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 47 | Debug|Any CPU = Debug|Any CPU 48 | Release|Any CPU = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 51 | {203AEF6B-8DE8-4B2C-A3A4-9522A458F41C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {203AEF6B-8DE8-4B2C-A3A4-9522A458F41C}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {203AEF6B-8DE8-4B2C-A3A4-9522A458F41C}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {203AEF6B-8DE8-4B2C-A3A4-9522A458F41C}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {749D1503-5255-4C90-BFF9-7FAD135C4AE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {749D1503-5255-4C90-BFF9-7FAD135C4AE1}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {749D1503-5255-4C90-BFF9-7FAD135C4AE1}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {749D1503-5255-4C90-BFF9-7FAD135C4AE1}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {6EE21F4D-7B75-488F-88BC-89FBEEC5E816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {6EE21F4D-7B75-488F-88BC-89FBEEC5E816}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {6EE21F4D-7B75-488F-88BC-89FBEEC5E816}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {6EE21F4D-7B75-488F-88BC-89FBEEC5E816}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {8349BD67-E9D0-457E-85CE-4B666654D55D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {8349BD67-E9D0-457E-85CE-4B666654D55D}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {8349BD67-E9D0-457E-85CE-4B666654D55D}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {8349BD67-E9D0-457E-85CE-4B666654D55D}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {EF0E0F07-7884-4EF7-B9AC-C7967DCA43A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {EF0E0F07-7884-4EF7-B9AC-C7967DCA43A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {EF0E0F07-7884-4EF7-B9AC-C7967DCA43A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {EF0E0F07-7884-4EF7-B9AC-C7967DCA43A9}.Release|Any CPU.Build.0 = Release|Any CPU 71 | EndGlobalSection 72 | GlobalSection(SolutionProperties) = preSolution 73 | HideSolutionNode = FALSE 74 | EndGlobalSection 75 | GlobalSection(NestedProjects) = preSolution 76 | {203AEF6B-8DE8-4B2C-A3A4-9522A458F41C} = {06B6F282-1C5D-4E62-B583-93D010553BFC} 77 | {3379F0DA-B4C0-447C-B406-AE0F93E5510C} = {06B6F282-1C5D-4E62-B583-93D010553BFC} 78 | {749D1503-5255-4C90-BFF9-7FAD135C4AE1} = {A2AD59B6-3D96-441B-88F8-79CD2C9EEBF7} 79 | {6EE21F4D-7B75-488F-88BC-89FBEEC5E816} = {3379F0DA-B4C0-447C-B406-AE0F93E5510C} 80 | {8349BD67-E9D0-457E-85CE-4B666654D55D} = {B9C29E78-160E-45E7-AC68-F06B59D4659F} 81 | {EF0E0F07-7884-4EF7-B9AC-C7967DCA43A9} = {B9C29E78-160E-45E7-AC68-F06B59D4659F} 82 | EndGlobalSection 83 | GlobalSection(ExtensibilityGlobals) = postSolution 84 | SolutionGuid = {475E1217-4E58-4412-B9AE-66AB47E6C6F2} 85 | EndGlobalSection 86 | EndGlobal 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NimbleMediator 2 | 3 | NimbleMediator is a significantly faster, lightweight and memory-efficient mediator pattern implementation for .NET, designed to be an alternative to popular mediator libraries. 4 | 5 | ## Features 6 | 7 | - **Faster Performance**: Designed to offer a speed advantage over similar packages, providing quicker request and notification processing times. (***~3,5*** times faster than MediatR) 8 | - **Less Memory Usage**: optimized to minimize memory allocations, helping to reduce the overall memory footprint. (uses ***~16x*** less memory than MediatR) 9 | - **Easy to Integrate**: Can be easily integrated into existing .NET projects, offering a simple and similar IMediator, ISender, IPublisher interfacses. 10 | - **Individualized Notification Publishers**: Allows for different publisher implementations for each notification, enabling developers to have the flexibility to choose the best publisher implementation for each notification. Also supports custom publisher implementations via INotificationPublisher interface. 11 | 12 | ## Getting Started 13 | 14 | ### Install 15 | Install the NimbleMediator with ```dotnet add package NimbleMediator``` or via NuGet package manager. 16 | 17 | ### Configure 18 | Set up NimbleMediator in your ``Startup.cs`` or ``Program.cs`` by utilizing the ``services.AddNimbleMediator()`` method and configuring your handlers and publishers as necessary. 19 | 20 | #### Register Handlers 21 | Register your handlers from the assembly: 22 | 23 | ```csharp 24 | 25 | services.AddNimbleMediator(cfg => { 26 | cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly); 27 | }); 28 | 29 | ``` 30 | 31 | or from assemblies: 32 | 33 | ```csharp 34 | 35 | services.AddNimbleMediator(cfg => { 36 | cfg.RegisterServicesFromAssemblies(typeof(X).Assembly, typeof(Y).Assembly, typeof(Z).Assembly); 37 | }); 38 | 39 | ``` 40 | 41 | #### Set default publisher (optional) 42 | Set the default publisher implementation for notifications. 43 | It is ``ForeachAwaitRobustPublisher`` by default if you don't set it. 44 | 45 | ```csharp 46 | 47 | cfg.SetDefaultNotificationPublisher(); 48 | or 49 | cfg.SetDefaultNotificationPublisher(); 50 | or your custom publisher. 51 | ``` 52 | 53 | #### Set default publisher's lifetime (optional) 54 | Set the default publisher's lifetime. It is ``Singleton`` by default if you don't set it. 55 | 56 | ```csharp 57 | 58 | cfg.SetDefaultNotificationPublisherLifetime(ServiceLifetime.Transient); 59 | 60 | ``` 61 | 62 | or 63 | 64 | ```csharp 65 | 66 | cfg.SetDefaultNotificationPublisher(ServiceLifetime.Transient); 67 | 68 | ``` 69 | 70 | #### Set a different publisher for a particular notification (optional) 71 | 72 | ```csharp 73 | 74 | cfg.SetNotificationPublisher(); 75 | 76 | ``` 77 | 78 | you can even provide lifetime here: 79 | 80 | ```csharp 81 | 82 | cfg.SetNotificationPublisher(ServiceLifetime.Singleton); 83 | 84 | ``` 85 | 86 | ##### Default publishers 87 | 1. ``ForeachAwaitRobustPublisher``: Executes all handlers in a sequential manner. If any of the handlers throws an exception, it will be caught and will thrown after all handlers are executed. Ensures that all handlers are executed even if one of them throws an exception. 88 | 89 | 2. ``ForeachAwaitStopOnFirstExceptionPublisher``: Executes all handlers in a sequential manner. If any of the handlers throws an exception, will be caught and be thrown immediately. Stops executing handlers if one of them throws an exception. 90 | 91 | 3. ``TaskWhenAllPublisher``: Executes all handlers in a concurrent manner. If any of the handlers throws an exception, it will be caught and will thrown after all handlers are executed. Ensures that all handlers are executed even if one of them throws an exception. 92 | 93 | ##### Custom publishers 94 | You can implement your own publisher by implementing ``INotificationPublisher`` interface. 95 | 96 | ```csharp 97 | 98 | public class MyOwnCustomPublisher : INotificationPublisher 99 | { 100 | public async Task PublishAsync(TNotification notification, IEnumerable> handlers, CancellationToken cancellationToken) 101 | where TNotification : INotification 102 | { 103 | // Implement 104 | } 105 | } 106 | 107 | ``` 108 | 109 | And set it as default publisher: 110 | 111 | ```csharp 112 | 113 | cfg.SetDefaultNotificationPublisher(); 114 | 115 | ``` 116 | 117 | Or set it just for a particular notification: 118 | 119 | ```csharp 120 | 121 | cfg.SetNotificationPublisher(); 122 | 123 | ``` 124 | 125 | #### Define Requests and Handlers 126 | Define your requests and handlers as you would with any other mediator library. 127 | Notice that ``ValueTask`` is used instead of ``Task`` to reduce memory allocations in case of synchronus execution based on some condition. 128 | 129 | 130 | ```csharp 131 | 132 | public class MyRequest : IRequest 133 | { 134 | public string Name { get; set; } 135 | } 136 | 137 | public class MyRequestHandler : IRequestHandler 138 | { 139 | public ValueTask Handle(Request1 request, CancellationToken cancellationToken) 140 | { 141 | // Do some work. 142 | 143 | if(someCondition) 144 | { 145 | return "NimbleMediator"; 146 | } 147 | 148 | var result = await SomeAsyncTask(); 149 | 150 | return result; 151 | } 152 | } 153 | 154 | ``` 155 | 156 | #### Define Notifications and Handlers 157 | Define your notifications and handlers as you would with any other mediator library. 158 | ``ValueTask``'s are not used for Notifications due asynchronous nature of the notifications. 159 | 160 | ```csharp 161 | 162 | public class MyNotification : INotification 163 | { 164 | public string Name { get; set; } 165 | } 166 | 167 | public class MyNotificationHandler : INotificationHandler 168 | { 169 | public Task Handle(MyNotification notification, CancellationToken cancellationToken) 170 | { 171 | 172 | await SomeAsyncTask(); 173 | } 174 | } 175 | 176 | ``` 177 | 178 | ### Performance & Benchmarks 179 | 180 | NimbleMediator is directly depends DI container to get handlers instead of relying on runtime reflection. This approach provides a significant performance advantage over other mediator libraries. 181 | 182 | NimbleMediator is currently **3,5** times faster and uses **16x** less memory than MediatR in some cases. 183 | 184 | ![send_benchmark](benchmarks/send_benchmark.png) 185 | 186 | ![publish_foreachawait_benchmark](benchmarks/publish_foreachawait_benchmark.png) 187 | 188 | ![publish_taskwhenall_benchmark](benchmarks/publish_taskwhenall_benchmark.png) 189 | 190 | 191 | ### Contributing 192 | 193 | Contributions are welcome! Please feel free to submit a Pull Request. See the [Contributing Guidelines](CONTRIBUTING.md) for more information. 194 | 195 | ### License 196 | All contents of this package are licensed under the [Apache License 2.0](LICENSE). 197 | -------------------------------------------------------------------------------- /src/NimbleMediator/ServiceExtensions/NimbleMediatorConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using NimbleMediator.Contracts; 5 | using NimbleMediator.Implementations; 6 | using NimbleMediator.NotificationPublishers; 7 | 8 | namespace NimbleMediator.ServiceExtensions; 9 | 10 | /// 11 | /// Configuration for . 12 | /// 13 | public class NimbleMediatorConfig 14 | { 15 | private readonly IServiceCollection _services; 16 | private readonly Dictionary _publisherTypeMappings = new(); 17 | private Type _defaultPublisherType = typeof(ForeachAwaitRobustPublisher); 18 | private ServiceLifetime _defaultPublisherLifetime = ServiceLifetime.Singleton; 19 | private ServiceLifetime _mediatorLifetime = ServiceLifetime.Scoped; 20 | private readonly HashSet _assemblies = new(); 21 | 22 | public NimbleMediatorConfig(IServiceCollection services) => _services = services; 23 | 24 | /// 25 | /// Registers all requests, notifications and respective handlers from the given assembly. 26 | /// 27 | /// 28 | public void RegisterServicesFromAssembly(Assembly assembly) => _assemblies.Add(assembly); 29 | 30 | /// 31 | /// Registers all requests, notifications and respective handlers from the given assemblies. 32 | /// 33 | /// 34 | public void RegisterServicesFromAssemblies(params Assembly[] assemblies) 35 | { 36 | foreach (var assembly in assemblies) 37 | { 38 | RegisterServicesFromAssembly(assembly); 39 | } 40 | } 41 | 42 | /// 43 | /// Sets the lifetime of the mediator implementation and IMediator, ISender, IPublisher interfaces. 44 | /// 45 | /// 46 | public void SetMediatorLifetime(ServiceLifetime lifetime) => _mediatorLifetime = lifetime; 47 | 48 | /// 49 | /// Sets the default publisher type for notifications. 50 | /// 51 | /// 52 | public void SetDefaultNotificationPublisherLifetime(ServiceLifetime lifetime) => _defaultPublisherLifetime = lifetime; 53 | 54 | /// 55 | /// Sets the default publisher type for notifications. 56 | /// 57 | /// 58 | public void SetDefaultNotificationPublisher(ServiceLifetime? lifetime = null) 59 | where TNotificationPublisher : INotificationPublisher 60 | { 61 | var publisherType = typeof(TNotificationPublisher); 62 | _defaultPublisherType = publisherType; 63 | 64 | if (lifetime is not null) 65 | { 66 | SetDefaultNotificationPublisherLifetime(lifetime.Value); 67 | } 68 | } 69 | 70 | /// 71 | /// Sets the publisher type for the given notification type. 72 | /// 73 | /// 74 | /// 75 | /// 76 | public void SetNotificationPublisher(ServiceLifetime? lifetime = null) 77 | where TNotification : INotification 78 | where TNotificationPublisher : INotificationPublisher 79 | { 80 | var notificationType = typeof(TNotification); 81 | var publisherType = typeof(TNotificationPublisher); 82 | 83 | _publisherTypeMappings[notificationType] = publisherType; 84 | 85 | TryAdd(_services, publisherType, lifetime ?? _defaultPublisherLifetime); 86 | } 87 | 88 | /// 89 | /// The public Register methods are actually just adds assemblies to a hashset, not registering them immediately. 90 | /// This is needed for the config to not to be dependent on the order of the calls. 91 | /// This method is called internally to register all marked assemblies. 92 | /// 93 | internal void RegisterServicesInternal() 94 | { 95 | RegisterMediatorImplementationAndInterfaces(_services, _mediatorLifetime, _publisherTypeMappings); 96 | 97 | foreach (var assembly in _assemblies) 98 | { 99 | RegisterRequestsFromAssembly(assembly); 100 | RegisterNotificationsFromAssembly(assembly); 101 | } 102 | 103 | // Register default publisher type if not provided by user. 104 | TryAdd(_services, _defaultPublisherType, _defaultPublisherLifetime); 105 | } 106 | 107 | private static void RegisterMediatorImplementationAndInterfaces(IServiceCollection services, ServiceLifetime mediatorLifetime, Dictionary publisherTypeMappings) 108 | { 109 | services.Add( 110 | new ServiceDescriptor(typeof(Mediator), 111 | sp => new Mediator(sp, publisherTypeMappings), 112 | mediatorLifetime)); 113 | 114 | services.Add( 115 | new ServiceDescriptor(typeof(IMediator), 116 | sp => sp.GetRequiredService(), 117 | mediatorLifetime)); 118 | 119 | services.Add( 120 | new ServiceDescriptor(typeof(ISender), 121 | sp => sp.GetRequiredService(), 122 | mediatorLifetime)); 123 | 124 | services.Add( 125 | new ServiceDescriptor(typeof(IPublisher), 126 | sp => sp.GetRequiredService(), 127 | mediatorLifetime)); 128 | } 129 | 130 | private void RegisterRequestsFromAssembly(Assembly assembly) 131 | { 132 | foreach (var type in assembly.GetTypes()) 133 | { 134 | foreach (var @interface in type.GetInterfaces()) 135 | { 136 | if (@interface.IsGenericType 137 | && (@interface.GetGenericTypeDefinition() == typeof(IRequestHandler<,>) 138 | || @interface.GetGenericTypeDefinition() == typeof(IRequestHandler<>))) 139 | { 140 | _services.AddTransient(@interface, type); 141 | } 142 | } 143 | } 144 | } 145 | 146 | private void RegisterNotificationsFromAssembly(Assembly assembly) 147 | { 148 | foreach (var type in assembly.GetTypes()) 149 | { 150 | foreach (var @interface in type.GetInterfaces()) 151 | { 152 | if (@interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(INotificationHandler<>)) 153 | { 154 | var notificationType = @interface.GetGenericArguments()[0]; 155 | var handlerType = type; 156 | 157 | _services.AddTransient(@interface, handlerType); 158 | 159 | _publisherTypeMappings.TryAdd(notificationType, _defaultPublisherType); 160 | 161 | } 162 | } 163 | } 164 | } 165 | 166 | private static void TryAdd(IServiceCollection services, Type type, ServiceLifetime lifetime) 167 | { 168 | switch (lifetime) 169 | { 170 | case ServiceLifetime.Singleton: 171 | services.TryAddSingleton(type); 172 | break; 173 | 174 | case ServiceLifetime.Scoped: 175 | services.TryAddScoped(type); 176 | break; 177 | 178 | case ServiceLifetime.Transient: 179 | services.TryAddTransient(type); 180 | break; 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /src/tests/NimbleMediator.Tests/ServiceCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using NimbleMediator.Contracts; 5 | using NimbleMediator.NotificationPublishers; 6 | using NimbleMediator.ServiceExtensions; 7 | namespace NimbleMediator.Tests; 8 | 9 | public class ServiceCollectionExtensionsTests 10 | { 11 | 12 | public ServiceCollectionExtensionsTests() 13 | { 14 | var services = new ServiceCollection(); 15 | 16 | services.AddNimbleMediator(config => config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly)); 17 | 18 | _serviceProvider = services.BuildServiceProvider(); 19 | } 20 | 21 | private readonly IServiceProvider _serviceProvider; 22 | 23 | [Fact] 24 | public void DI_Should_Resolve_ISender() 25 | { 26 | var sender = _serviceProvider.GetService(); 27 | 28 | Assert.NotNull(sender); 29 | Assert.True(sender is ISender s); 30 | } 31 | 32 | [Fact] 33 | public void ISender_Should_Be_Scoped_By_Default() 34 | { 35 | 36 | var serviceCollection = new ServiceCollection(); 37 | serviceCollection.AddNimbleMediator(config => 38 | { 39 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 40 | }); 41 | 42 | var sender = serviceCollection.FirstOrDefault(x => x.ServiceType == typeof(ISender)); 43 | 44 | Assert.NotNull(sender); 45 | Assert.True(sender.Lifetime == ServiceLifetime.Scoped); 46 | } 47 | 48 | [Fact] 49 | public void DI_Should_Resolve_IPublisher() 50 | { 51 | var publisher = _serviceProvider.GetService(); 52 | 53 | Assert.NotNull(publisher); 54 | Assert.True(publisher is IPublisher p); 55 | } 56 | 57 | [Fact] 58 | public void IPublisher_Should_Be_Scoped_By_Default() 59 | { 60 | 61 | var serviceCollection = new ServiceCollection(); 62 | serviceCollection.AddNimbleMediator(config => 63 | { 64 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 65 | }); 66 | 67 | var publisher = serviceCollection.FirstOrDefault(x => x.ServiceType == typeof(IPublisher)); 68 | 69 | Assert.NotNull(publisher); 70 | Assert.True(publisher.Lifetime == ServiceLifetime.Scoped); 71 | } 72 | 73 | [Fact] 74 | public void DI_Should_Resolve_IMediator() 75 | { 76 | var mediator = _serviceProvider.GetService(); 77 | 78 | Assert.NotNull(mediator); 79 | Assert.True(mediator is IMediator m); 80 | } 81 | 82 | [Fact] 83 | public void IMediator_Should_Be_Scoped_By_Default() 84 | { 85 | 86 | var serviceCollection = new ServiceCollection(); 87 | serviceCollection.AddNimbleMediator(config => 88 | { 89 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 90 | }); 91 | 92 | var mediator = serviceCollection.FirstOrDefault(x => x.ServiceType == typeof(IMediator)); 93 | 94 | Assert.NotNull(mediator); 95 | Assert.True(mediator.Lifetime == ServiceLifetime.Scoped); 96 | } 97 | 98 | [Fact] 99 | public void DI_Should_Resolve_RequestHandler_TResponse() 100 | { 101 | var requestHandler = _serviceProvider.GetService>(); 102 | 103 | Assert.NotNull(requestHandler); 104 | } 105 | 106 | [Fact] 107 | public void DI_Should_Resolve_RequestHandler() 108 | { 109 | var requestHandler = _serviceProvider.GetService>(); 110 | 111 | Assert.NotNull(requestHandler); 112 | } 113 | 114 | [Fact] 115 | public void DI_Should_Resolve_ForeachAwaitRobustPublisher() 116 | { 117 | 118 | var services = new ServiceCollection(); 119 | 120 | services.AddNimbleMediator(config => 121 | { 122 | config.SetDefaultNotificationPublisher(); 123 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 124 | }); 125 | 126 | var serviceProvider = services.BuildServiceProvider(); 127 | var notificationHandler = serviceProvider.GetService(); 128 | 129 | Assert.NotNull(notificationHandler); 130 | Assert.True(notificationHandler is ForeachAwaitRobustPublisher publisher); 131 | } 132 | 133 | [Fact] 134 | public void DI_Should_Resolve_ForeachAwaitStopOnFirstExceptionPublisher() 135 | { 136 | var services = new ServiceCollection(); 137 | 138 | services.AddNimbleMediator(config => 139 | { 140 | config.SetDefaultNotificationPublisher(); 141 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 142 | }); 143 | 144 | var serviceProvider = services.BuildServiceProvider(); 145 | var notificationHandler = serviceProvider.GetService(); 146 | 147 | Assert.NotNull(notificationHandler); 148 | Assert.True(notificationHandler is ForeachAwaitStopOnFirstExceptionPublisher publisher); 149 | } 150 | 151 | [Fact] 152 | public void DI_Should_Resolve_TaskWhenAllPublisher() 153 | { 154 | var services = new ServiceCollection(); 155 | 156 | services.AddNimbleMediator(config => 157 | { 158 | config.SetDefaultNotificationPublisher(); 159 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 160 | }); 161 | 162 | var serviceProvider = services.BuildServiceProvider(); 163 | var notificationHandler = serviceProvider.GetService(); 164 | 165 | Assert.NotNull(notificationHandler); 166 | Assert.True(notificationHandler is TaskWhenAllPublisher publisher); 167 | } 168 | 169 | [Fact] 170 | public void Mediator_Should_Have_Singleton_If_Set_Explicitly() 171 | { 172 | var services = new ServiceCollection(); 173 | 174 | services.AddNimbleMediator(config => 175 | { 176 | config.SetMediatorLifetime(ServiceLifetime.Singleton); 177 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 178 | }); 179 | 180 | var mediator = services.FirstOrDefault(x => x.ServiceType == typeof(IMediator)); 181 | 182 | Assert.NotNull(mediator); 183 | Assert.True(mediator.Lifetime == ServiceLifetime.Singleton); 184 | } 185 | 186 | [Fact] 187 | public void Mediator_Should_Have_Scoped_If_Set_Explicitly() 188 | { 189 | var services = new ServiceCollection(); 190 | 191 | services.AddNimbleMediator(config => 192 | { 193 | config.SetMediatorLifetime(ServiceLifetime.Scoped); 194 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 195 | }); 196 | 197 | var mediator = services.FirstOrDefault(x => x.ServiceType == typeof(IMediator)); 198 | 199 | Assert.NotNull(mediator); 200 | Assert.True(mediator.Lifetime == ServiceLifetime.Scoped); 201 | } 202 | 203 | [Fact] 204 | public void Mediator_Should_Have_Transient_If_Set_Explicitly() 205 | { 206 | var services = new ServiceCollection(); 207 | 208 | services.AddNimbleMediator(config => 209 | { 210 | config.SetMediatorLifetime(ServiceLifetime.Transient); 211 | config.RegisterServicesFromAssembly(typeof(MyRequestWithResponse).Assembly); 212 | }); 213 | 214 | var mediator = services.FirstOrDefault(x => x.ServiceType == typeof(IMediator)); 215 | 216 | Assert.NotNull(mediator); 217 | Assert.True(mediator.Lifetime == ServiceLifetime.Transient); 218 | } 219 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from `dotnet new gitignore` 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # Tye 66 | .tye/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.tlog 97 | *.vspscc 98 | *.vssscc 99 | .builds 100 | *.pidb 101 | *.svclog 102 | *.scc 103 | 104 | # Chutzpah Test files 105 | _Chutzpah* 106 | 107 | # Visual C++ cache files 108 | ipch/ 109 | *.aps 110 | *.ncb 111 | *.opendb 112 | *.opensdf 113 | *.sdf 114 | *.cachefile 115 | *.VC.db 116 | *.VC.VC.opendb 117 | 118 | # Visual Studio profiler 119 | *.psess 120 | *.vsp 121 | *.vspx 122 | *.sap 123 | 124 | # Visual Studio Trace Files 125 | *.e2e 126 | 127 | # TFS 2012 Local Workspace 128 | $tf/ 129 | 130 | # Guidance Automation Toolkit 131 | *.gpState 132 | 133 | # ReSharper is a .NET coding add-in 134 | _ReSharper*/ 135 | *.[Rr]e[Ss]harper 136 | *.DotSettings.user 137 | 138 | # TeamCity is a build add-in 139 | _TeamCity* 140 | 141 | # DotCover is a Code Coverage Tool 142 | *.dotCover 143 | 144 | # AxoCover is a Code Coverage Tool 145 | .axoCover/* 146 | !.axoCover/settings.json 147 | 148 | # Coverlet is a free, cross platform Code Coverage Tool 149 | coverage*.json 150 | coverage*.xml 151 | coverage*.info 152 | 153 | # Visual Studio code coverage results 154 | *.coverage 155 | *.coveragexml 156 | 157 | # NCrunch 158 | _NCrunch_* 159 | .*crunch*.local.xml 160 | nCrunchTemp_* 161 | 162 | # MightyMoose 163 | *.mm.* 164 | AutoTest.Net/ 165 | 166 | # Web workbench (sass) 167 | .sass-cache/ 168 | 169 | # Installshield output folder 170 | [Ee]xpress/ 171 | 172 | # DocProject is a documentation generator add-in 173 | DocProject/buildhelp/ 174 | DocProject/Help/*.HxT 175 | DocProject/Help/*.HxC 176 | DocProject/Help/*.hhc 177 | DocProject/Help/*.hhk 178 | DocProject/Help/*.hhp 179 | DocProject/Help/Html2 180 | DocProject/Help/html 181 | 182 | # Click-Once directory 183 | publish/ 184 | 185 | # Publish Web Output 186 | *.[Pp]ublish.xml 187 | *.azurePubxml 188 | # Note: Comment the next line if you want to checkin your web deploy settings, 189 | # but database connection strings (with potential passwords) will be unencrypted 190 | *.pubxml 191 | *.publishproj 192 | 193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 194 | # checkin your Azure Web App publish settings, but sensitive information contained 195 | # in these scripts will be unencrypted 196 | PublishScripts/ 197 | 198 | # NuGet Packages 199 | *.nupkg 200 | # NuGet Symbol Packages 201 | *.snupkg 202 | # The packages folder can be ignored because of Package Restore 203 | **/[Pp]ackages/* 204 | # except build/, which is used as an MSBuild target. 205 | !**/[Pp]ackages/build/ 206 | # Uncomment if necessary however generally it will be regenerated when needed 207 | #!**/[Pp]ackages/repositories.config 208 | # NuGet v3's project.json files produces more ignorable files 209 | *.nuget.props 210 | *.nuget.targets 211 | 212 | # Microsoft Azure Build Output 213 | csx/ 214 | *.build.csdef 215 | 216 | # Microsoft Azure Emulator 217 | ecf/ 218 | rcf/ 219 | 220 | # Windows Store app package directories and files 221 | AppPackages/ 222 | BundleArtifacts/ 223 | Package.StoreAssociation.xml 224 | _pkginfo.txt 225 | *.appx 226 | *.appxbundle 227 | *.appxupload 228 | 229 | # Visual Studio cache files 230 | # files ending in .cache can be ignored 231 | *.[Cc]ache 232 | # but keep track of directories ending in .cache 233 | !?*.[Cc]ache/ 234 | 235 | # Others 236 | ClientBin/ 237 | ~$* 238 | *~ 239 | *.dbmdl 240 | *.dbproj.schemaview 241 | *.jfm 242 | *.pfx 243 | *.publishsettings 244 | orleans.codegen.cs 245 | 246 | # Including strong name files can present a security risk 247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 248 | #*.snk 249 | 250 | # Since there are multiple workflows, uncomment next line to ignore bower_components 251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 252 | #bower_components/ 253 | 254 | # RIA/Silverlight projects 255 | Generated_Code/ 256 | 257 | # Backup & report files from converting an old project file 258 | # to a newer Visual Studio version. Backup files are not needed, 259 | # because we have git ;-) 260 | _UpgradeReport_Files/ 261 | Backup*/ 262 | UpgradeLog*.XML 263 | UpgradeLog*.htm 264 | ServiceFabricBackup/ 265 | *.rptproj.bak 266 | 267 | # SQL Server files 268 | *.mdf 269 | *.ldf 270 | *.ndf 271 | 272 | # Business Intelligence projects 273 | *.rdl.data 274 | *.bim.layout 275 | *.bim_*.settings 276 | *.rptproj.rsuser 277 | *- [Bb]ackup.rdl 278 | *- [Bb]ackup ([0-9]).rdl 279 | *- [Bb]ackup ([0-9][0-9]).rdl 280 | 281 | # Microsoft Fakes 282 | FakesAssemblies/ 283 | 284 | # GhostDoc plugin setting file 285 | *.GhostDoc.xml 286 | 287 | # Node.js Tools for Visual Studio 288 | .ntvs_analysis.dat 289 | node_modules/ 290 | 291 | # Visual Studio 6 build log 292 | *.plg 293 | 294 | # Visual Studio 6 workspace options file 295 | *.opt 296 | 297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 298 | *.vbw 299 | 300 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 301 | *.vbp 302 | 303 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 304 | *.dsw 305 | *.dsp 306 | 307 | # Visual Studio 6 technical files 308 | *.ncb 309 | *.aps 310 | 311 | # Visual Studio LightSwitch build output 312 | **/*.HTMLClient/GeneratedArtifacts 313 | **/*.DesktopClient/GeneratedArtifacts 314 | **/*.DesktopClient/ModelManifest.xml 315 | **/*.Server/GeneratedArtifacts 316 | **/*.Server/ModelManifest.xml 317 | _Pvt_Extensions 318 | 319 | # Paket dependency manager 320 | .paket/paket.exe 321 | paket-files/ 322 | 323 | # FAKE - F# Make 324 | .fake/ 325 | 326 | # CodeRush personal settings 327 | .cr/personal 328 | 329 | # Python Tools for Visual Studio (PTVS) 330 | __pycache__/ 331 | *.pyc 332 | 333 | # Cake - Uncomment if you are using it 334 | # tools/** 335 | # !tools/packages.config 336 | 337 | # Tabs Studio 338 | *.tss 339 | 340 | # Telerik's JustMock configuration file 341 | *.jmconfig 342 | 343 | # BizTalk build output 344 | *.btp.cs 345 | *.btm.cs 346 | *.odx.cs 347 | *.xsd.cs 348 | 349 | # OpenCover UI analysis results 350 | OpenCover/ 351 | 352 | # Azure Stream Analytics local run output 353 | ASALocalRun/ 354 | 355 | # MSBuild Binary and Structured Log 356 | *.binlog 357 | 358 | # NVidia Nsight GPU debugger configuration file 359 | *.nvuser 360 | 361 | # MFractors (Xamarin productivity tool) working folder 362 | .mfractor/ 363 | 364 | # Local History for Visual Studio 365 | .localhistory/ 366 | 367 | # Visual Studio History (VSHistory) files 368 | .vshistory/ 369 | 370 | # BeatPulse healthcheck temp database 371 | healthchecksdb 372 | 373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 374 | MigrationBackup/ 375 | 376 | # Ionide (cross platform F# VS Code tools) working folder 377 | .ionide/ 378 | 379 | # Fody - auto-generated XML schema 380 | FodyWeavers.xsd 381 | 382 | # VS Code files for those working on multiple tools 383 | .vscode/* 384 | !.vscode/settings.json 385 | !.vscode/tasks.json 386 | !.vscode/launch.json 387 | !.vscode/extensions.json 388 | *.code-workspace 389 | 390 | # Local History for Visual Studio Code 391 | .history/ 392 | 393 | # Windows Installer files from build outputs 394 | *.cab 395 | *.msi 396 | *.msix 397 | *.msm 398 | *.msp 399 | 400 | # JetBrains Rider 401 | *.sln.iml 402 | 403 | ## 404 | ## Visual studio for Mac 405 | ## 406 | 407 | 408 | # globs 409 | Makefile.in 410 | *.userprefs 411 | *.usertasks 412 | config.make 413 | config.status 414 | aclocal.m4 415 | install-sh 416 | autom4te.cache/ 417 | *.tar.gz 418 | tarballs/ 419 | test-results/ 420 | 421 | # Mac bundle stuff 422 | *.dmg 423 | *.app 424 | 425 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 426 | # General 427 | .DS_Store 428 | .AppleDouble 429 | .LSOverride 430 | 431 | # Icon must end with two \r 432 | Icon 433 | 434 | 435 | # Thumbnails 436 | ._* 437 | 438 | # Files that might appear in the root of a volume 439 | .DocumentRevisions-V100 440 | .fseventsd 441 | .Spotlight-V100 442 | .TemporaryItems 443 | .Trashes 444 | .VolumeIcon.icns 445 | .com.apple.timemachine.donotpresent 446 | 447 | # Directories potentially created on remote AFP share 448 | .AppleDB 449 | .AppleDesktop 450 | Network Trash Folder 451 | Temporary Items 452 | .apdisk 453 | 454 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 455 | # Windows thumbnail cache files 456 | Thumbs.db 457 | ehthumbs.db 458 | ehthumbs_vista.db 459 | 460 | # Dump file 461 | *.stackdump 462 | 463 | # Folder config file 464 | [Dd]esktop.ini 465 | 466 | # Recycle Bin used on file shares 467 | $RECYCLE.BIN/ 468 | 469 | # Windows Installer files 470 | *.cab 471 | *.msi 472 | *.msix 473 | *.msm 474 | *.msp 475 | 476 | # Windows shortcuts 477 | *.lnk 478 | 479 | # Vim temporary swap files 480 | *.swp 481 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*.cs] 3 | #### Naming styles #### 4 | 5 | # Naming rules 6 | 7 | dotnet_naming_rule.private_or_internal_field_should_be_put___before_private.severity = error 8 | dotnet_naming_rule.private_or_internal_field_should_be_put___before_private.symbols = private_or_internal_field 9 | dotnet_naming_rule.private_or_internal_field_should_be_put___before_private.style = put___before_private 10 | 11 | dotnet_naming_rule.private_or_internal_static_field_should_be_put___before_private.severity = error 12 | dotnet_naming_rule.private_or_internal_static_field_should_be_put___before_private.symbols = private_or_internal_static_field 13 | dotnet_naming_rule.private_or_internal_static_field_should_be_put___before_private.style = put___before_private 14 | 15 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = error 16 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 17 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 18 | 19 | dotnet_naming_rule.types_should_be_pascal_case.severity = warning 20 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 21 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 22 | 23 | # Symbol specifications 24 | 25 | dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field 26 | dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected 27 | dotnet_naming_symbols.private_or_internal_field.required_modifiers = 28 | 29 | dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field 30 | dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected 31 | dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static 32 | 33 | dotnet_naming_symbols.interface.applicable_kinds = interface 34 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 35 | dotnet_naming_symbols.interface.required_modifiers = 36 | 37 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 38 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 39 | dotnet_naming_symbols.types.required_modifiers = 40 | 41 | # Naming styles 42 | 43 | dotnet_naming_style.put___before_private.required_prefix = _ 44 | dotnet_naming_style.put___before_private.required_suffix = 45 | dotnet_naming_style.put___before_private.word_separator = 46 | dotnet_naming_style.put___before_private.capitalization = camel_case 47 | 48 | dotnet_naming_style.put___before_private.required_prefix = _ 49 | dotnet_naming_style.put___before_private.required_suffix = 50 | dotnet_naming_style.put___before_private.word_separator = 51 | dotnet_naming_style.put___before_private.capitalization = camel_case 52 | 53 | dotnet_naming_style.begins_with_i.required_prefix = I 54 | dotnet_naming_style.begins_with_i.required_suffix = 55 | dotnet_naming_style.begins_with_i.word_separator = 56 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 57 | 58 | dotnet_naming_style.pascal_case.required_prefix = 59 | dotnet_naming_style.pascal_case.required_suffix = 60 | dotnet_naming_style.pascal_case.word_separator = 61 | dotnet_naming_style.pascal_case.capitalization = pascal_case 62 | csharp_indent_labels = one_less_than_current 63 | csharp_using_directive_placement = outside_namespace:error 64 | csharp_prefer_simple_using_statement = true:suggestion 65 | csharp_prefer_braces = true:warning 66 | csharp_style_namespace_declarations = file_scoped:warning 67 | csharp_style_prefer_method_group_conversion = true:silent 68 | csharp_style_prefer_top_level_statements = true:silent 69 | csharp_style_expression_bodied_methods = when_on_single_line:suggestion 70 | csharp_style_expression_bodied_constructors = when_on_single_line:suggestion 71 | csharp_style_expression_bodied_operators = when_on_single_line:silent 72 | csharp_style_expression_bodied_properties = when_on_single_line:warning 73 | csharp_style_expression_bodied_indexers = when_on_single_line:warning 74 | csharp_style_expression_bodied_accessors = true:warning 75 | csharp_style_expression_bodied_lambdas = true:suggestion 76 | csharp_style_expression_bodied_local_functions = true:suggestion 77 | csharp_space_around_binary_operators = before_and_after 78 | csharp_style_throw_expression = true:warning 79 | csharp_style_prefer_null_check_over_type_check = true:warning 80 | csharp_prefer_simple_default_expression = true:warning 81 | csharp_style_prefer_local_over_anonymous_function = true:warning 82 | csharp_style_prefer_index_operator = true:warning 83 | csharp_style_prefer_range_operator = true:warning 84 | csharp_style_implicit_object_creation_when_type_is_apparent = true:warning 85 | csharp_style_prefer_tuple_swap = true:suggestion 86 | csharp_style_prefer_utf8_string_literals = true:suggestion 87 | csharp_style_inlined_variable_declaration = false:warning 88 | csharp_style_deconstructed_variable_declaration = true:warning 89 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 90 | csharp_style_unused_value_expression_statement_preference = discard_variable:none 91 | csharp_prefer_static_local_function = true:warning 92 | csharp_style_prefer_readonly_struct = true:warning 93 | csharp_style_prefer_readonly_struct_member = true:suggestion 94 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:suggestion 95 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 96 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 97 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent 98 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent 99 | csharp_style_conditional_delegate_call = true:warning 100 | csharp_style_prefer_switch_expression = true:warning 101 | csharp_style_prefer_pattern_matching = true:warning 102 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 103 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 104 | csharp_style_prefer_not_pattern = true:warning 105 | csharp_style_prefer_extended_property_pattern = true:suggestion 106 | csharp_style_var_for_built_in_types = false:silent 107 | csharp_style_var_when_type_is_apparent = true:silent 108 | csharp_style_var_elsewhere = false:silent 109 | dotnet_diagnostic.CA1001.severity = warning 110 | dotnet_diagnostic.CA1032.severity = suggestion 111 | dotnet_diagnostic.CA1507.severity = warning 112 | dotnet_diagnostic.CA2016.severity = warning 113 | 114 | # IDE0058: Expression value is never used 115 | dotnet_diagnostic.IDE0058.severity = none 116 | csharp_style_prefer_primary_constructors = true:suggestion 117 | dotnet_diagnostic.S2437.severity = warning 118 | dotnet_diagnostic.IDE0051.severity = warning 119 | dotnet_diagnostic.IDE0052.severity = warning 120 | 121 | [*.vb] 122 | #### Naming styles #### 123 | 124 | # Naming rules 125 | 126 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 127 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 128 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 129 | 130 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 131 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 132 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 133 | 134 | # Symbol specifications 135 | 136 | dotnet_naming_symbols.interface.applicable_kinds = interface 137 | dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 138 | dotnet_naming_symbols.interface.required_modifiers = 139 | 140 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 141 | dotnet_naming_symbols.types.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 142 | dotnet_naming_symbols.types.required_modifiers = 143 | 144 | # Naming styles 145 | 146 | dotnet_naming_style.begins_with_i.required_prefix = I 147 | dotnet_naming_style.begins_with_i.required_suffix = 148 | dotnet_naming_style.begins_with_i.word_separator = 149 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 150 | 151 | dotnet_naming_style.pascal_case.required_prefix = 152 | dotnet_naming_style.pascal_case.required_suffix = 153 | dotnet_naming_style.pascal_case.word_separator = 154 | dotnet_naming_style.pascal_case.capitalization = pascal_case 155 | 156 | [*.{cs,vb}] 157 | #### Naming styles #### 158 | 159 | # Naming rules 160 | 161 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning 162 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 163 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 164 | 165 | # Symbol specifications 166 | 167 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 168 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 169 | dotnet_naming_symbols.non_field_members.required_modifiers = 170 | 171 | # Naming styles 172 | 173 | dotnet_naming_style.pascal_case.required_prefix = 174 | dotnet_naming_style.pascal_case.required_suffix = 175 | dotnet_naming_style.pascal_case.word_separator = 176 | dotnet_naming_style.pascal_case.capitalization = pascal_case 177 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 178 | tab_width = 4 179 | indent_size = 4 180 | end_of_line = crlf 181 | dotnet_style_coalesce_expression = true:warning 182 | dotnet_style_null_propagation = true:warning 183 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 184 | indent_style = space 185 | dotnet_style_prefer_auto_properties = true:silent 186 | dotnet_style_object_initializer = true:warning 187 | dotnet_style_collection_initializer = true:warning 188 | dotnet_style_prefer_simplified_boolean_expressions = true:warning 189 | dotnet_style_prefer_conditional_expression_over_assignment = true:warning 190 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion 191 | dotnet_style_explicit_tuple_names = true:suggestion 192 | dotnet_style_prefer_inferred_tuple_names = false:suggestion 193 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 194 | dotnet_style_prefer_compound_assignment = true:warning 195 | dotnet_style_prefer_simplified_interpolation = true:suggestion 196 | dotnet_style_namespace_match_folder = true:warning 197 | dotnet_style_readonly_field = true:warning 198 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 199 | dotnet_style_predefined_type_for_member_access = true:silent 200 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 201 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 202 | dotnet_style_allow_statement_immediately_after_block_experimental = false:silent 203 | dotnet_code_quality_unused_parameters = all:warning 204 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 205 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 206 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 207 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 208 | dotnet_style_qualification_for_field = false:warning 209 | dotnet_style_qualification_for_property = false:warning 210 | dotnet_style_qualification_for_method = false:warning 211 | dotnet_style_qualification_for_event = false:warning 212 | dotnet_diagnostic.CA1031.severity = suggestion 213 | dotnet_diagnostic.CA1062.severity = suggestion 214 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------