From 761fb0b1b420f5a8c2cb4a751617dce7ab9c3fe3 Mon Sep 17 00:00:00 2001 From: Jimmy Bogard Date: Thu, 9 Feb 2023 09:20:21 -0600 Subject: [PATCH] Changing IRequest to just be a normal Task instead of Task --- samples/MediatR.Examples/JingHandler.cs | 4 +- src/MediatR.Contracts/IRequest.cs | 2 +- .../MediatR.Contracts.csproj | 2 +- src/MediatR/IRequestHandler.cs | 65 +-------- src/MediatR/ISender.cs | 9 ++ src/MediatR/MediatR.csproj | 3 +- src/MediatR/Mediator.cs | 41 +++++- .../ServiceCollectionExtensions.cs | 2 - src/MediatR/Registration/ServiceRegistrar.cs | 1 + src/MediatR/Wrappers/RequestHandlerWrapper.cs | 48 +++++-- test/MediatR.Tests/ExceptionTests.cs | 10 +- .../GenericTypeConstraintsTests.cs | 15 +- .../AssemblyResolutionTests.cs | 2 +- .../MicrosoftExtensionsDI/Handlers.cs | 9 +- .../TypeResolutionTests.cs | 2 +- test/MediatR.Tests/PipelineTests.cs | 129 +++++++++++++++++- test/MediatR.Tests/RequestHandlerTests.cs | 36 ----- test/MediatR.Tests/RequestHandlerUnitTests.cs | 44 ------ test/MediatR.Tests/SendTests.cs | 78 +++++++++++ test/MediatR.Tests/SendVoidInterfaceTests.cs | 5 +- 20 files changed, 331 insertions(+), 176 deletions(-) delete mode 100644 test/MediatR.Tests/RequestHandlerTests.cs delete mode 100644 test/MediatR.Tests/RequestHandlerUnitTests.cs diff --git a/samples/MediatR.Examples/JingHandler.cs b/samples/MediatR.Examples/JingHandler.cs index a5010273..4dafc4d4 100644 --- a/samples/MediatR.Examples/JingHandler.cs +++ b/samples/MediatR.Examples/JingHandler.cs @@ -4,7 +4,7 @@ namespace MediatR.Examples; -public class JingHandler : AsyncRequestHandler +public class JingHandler : IRequestHandler { private readonly TextWriter _writer; @@ -13,7 +13,7 @@ public JingHandler(TextWriter writer) _writer = writer; } - protected override Task Handle(Jing request, CancellationToken cancellationToken) + public Task Handle(Jing request, CancellationToken cancellationToken) { return _writer.WriteLineAsync($"--- Handled Jing: {request.Message}, no Jong"); } diff --git a/src/MediatR.Contracts/IRequest.cs b/src/MediatR.Contracts/IRequest.cs index efbfda01..8a004779 100644 --- a/src/MediatR.Contracts/IRequest.cs +++ b/src/MediatR.Contracts/IRequest.cs @@ -3,7 +3,7 @@ namespace MediatR; /// /// Marker interface to represent a request with a void response /// -public interface IRequest : IRequest { } +public interface IRequest : IBaseRequest { } /// /// Marker interface to represent a request with a response diff --git a/src/MediatR.Contracts/MediatR.Contracts.csproj b/src/MediatR.Contracts/MediatR.Contracts.csproj index d2832f9c..af0bf68f 100644 --- a/src/MediatR.Contracts/MediatR.Contracts.csproj +++ b/src/MediatR.Contracts/MediatR.Contracts.csproj @@ -19,7 +19,7 @@ snupkg true true - 1.0.1 + 2.0.0 MediatR diff --git a/src/MediatR/IRequestHandler.cs b/src/MediatR/IRequestHandler.cs index 30c31410..5aa399b2 100644 --- a/src/MediatR/IRequestHandler.cs +++ b/src/MediatR/IRequestHandler.cs @@ -21,68 +21,17 @@ public interface IRequestHandler } /// -/// Defines a handler for a request with a void () response. -/// You do not need to register this interface explicitly with a container as it inherits from the base interface. +/// Defines a handler for a request with a void response. /// /// The type of request being handled -public interface IRequestHandler : IRequestHandler - where TRequest : IRequest -{ -} - -/// -/// Wrapper class for a handler that asynchronously handles a request and does not return a response -/// -/// The type of request being handled -public abstract class AsyncRequestHandler : IRequestHandler +public interface IRequestHandler where TRequest : IRequest { - async Task IRequestHandler.Handle(TRequest request, CancellationToken cancellationToken) - { - await Handle(request, cancellationToken).ConfigureAwait(false); - return Unit.Value; - } - - /// - /// Override in a derived class for the handler logic - /// - /// Request - /// - /// Response - protected abstract Task Handle(TRequest request, CancellationToken cancellationToken); -} - -/// -/// Wrapper class for a handler that synchronously handles a request and returns a response -/// -/// The type of request being handled -/// The type of response from the handler -public abstract class RequestHandler : IRequestHandler - where TRequest : IRequest -{ - Task IRequestHandler.Handle(TRequest request, CancellationToken cancellationToken) - => Task.FromResult(Handle(request)); - /// - /// Override in a derived class for the handler logic + /// Handles a request /// - /// Request - /// Response - protected abstract TResponse Handle(TRequest request); -} - -/// -/// Wrapper class for a handler that synchronously handles a request and does not return a response -/// -/// The type of request being handled -public abstract class RequestHandler : IRequestHandler - where TRequest : IRequest -{ - Task IRequestHandler.Handle(TRequest request, CancellationToken cancellationToken) - { - Handle(request); - return Unit.Task; - } - - protected abstract void Handle(TRequest request); + /// The request + /// Cancellation token + /// Response from the request + Task Handle(TRequest request, CancellationToken cancellationToken); } diff --git a/src/MediatR/ISender.cs b/src/MediatR/ISender.cs index 2c476293..5f9b46a1 100644 --- a/src/MediatR/ISender.cs +++ b/src/MediatR/ISender.cs @@ -18,6 +18,15 @@ public interface ISender /// A task that represents the send operation. The task result contains the handler response Task Send(IRequest request, CancellationToken cancellationToken = default); + /// + /// Asynchronously send a request to a single handler with no response + /// + /// Request object + /// Optional cancellation token + /// A task that represents the send operation. + Task Send(TRequest request, CancellationToken cancellationToken = default) + where TRequest : IRequest; + /// /// Asynchronously send an object request to a single handler via dynamic dispatch /// diff --git a/src/MediatR/MediatR.csproj b/src/MediatR/MediatR.csproj index 6cf37d78..90447ebb 100644 --- a/src/MediatR/MediatR.csproj +++ b/src/MediatR/MediatR.csproj @@ -27,10 +27,11 @@ - + + diff --git a/src/MediatR/Mediator.cs b/src/MediatR/Mediator.cs index 79a66077..6745842f 100644 --- a/src/MediatR/Mediator.cs +++ b/src/MediatR/Mediator.cs @@ -41,6 +41,23 @@ public Task Send(IRequest request, Cancellation return handler.Handle(request, _serviceProvider, cancellationToken); } + public Task Send(TRequest request, CancellationToken cancellationToken = default) + where TRequest : IRequest + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + var requestType = typeof(TRequest); + + var handler = (RequestHandlerWrapper)_requestHandlers.GetOrAdd(requestType, + static t => (RequestHandlerBase)(Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<>).MakeGenericType(t)) + ?? throw new InvalidOperationException($"Could not create wrapper type for {t}"))); + + return handler.Handle(request, _serviceProvider, cancellationToken); + } + public Task Send(object request, CancellationToken cancellationToken = default) { if (request == null) @@ -55,13 +72,29 @@ public Task Send(IRequest request, Cancellation .GetInterfaces() .FirstOrDefault(static i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>)); + Type wrapperType; + if (requestInterfaceType is null) { - throw new ArgumentException($"{requestTypeKey.Name} does not implement {nameof(IRequest)}", nameof(request)); + requestInterfaceType = requestTypeKey + .GetInterfaces() + .FirstOrDefault(static i => i == typeof(IRequest)); + + if (requestInterfaceType is null) + { + throw new ArgumentException($"{requestTypeKey.Name} does not implement {nameof(IRequest)}", + nameof(request)); + } + + wrapperType = + typeof(RequestHandlerWrapperImpl<>).MakeGenericType(requestTypeKey); + } + else + { + var responseType = requestInterfaceType.GetGenericArguments()[0]; + wrapperType = + typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestTypeKey, responseType); } - - var responseType = requestInterfaceType.GetGenericArguments()[0]; - var wrapperType = typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestTypeKey, responseType); return (RequestHandlerBase)(Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper for type {wrapperType}")); diff --git a/src/MediatR/MicrosoftExtensionsDI/ServiceCollectionExtensions.cs b/src/MediatR/MicrosoftExtensionsDI/ServiceCollectionExtensions.cs index 75218800..c95b5a8f 100644 --- a/src/MediatR/MicrosoftExtensionsDI/ServiceCollectionExtensions.cs +++ b/src/MediatR/MicrosoftExtensionsDI/ServiceCollectionExtensions.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Reflection; using MediatR; using MediatR.Pipeline; using MediatR.Registration; diff --git a/src/MediatR/Registration/ServiceRegistrar.cs b/src/MediatR/Registration/ServiceRegistrar.cs index 07551533..e56f7ee0 100644 --- a/src/MediatR/Registration/ServiceRegistrar.cs +++ b/src/MediatR/Registration/ServiceRegistrar.cs @@ -15,6 +15,7 @@ public static void AddMediatRClasses(IServiceCollection services, MediatRService var assembliesToScan = configuration.AssembliesToRegister.Distinct().ToArray(); ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>), services, assembliesToScan, false, configuration); + ConnectImplementationsToTypesClosing(typeof(IRequestHandler<>), services, assembliesToScan, false, configuration); ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>), services, assembliesToScan, true, configuration); ConnectImplementationsToTypesClosing(typeof(IStreamRequestHandler<,>), services, assembliesToScan, false, configuration); ConnectImplementationsToTypesClosing(typeof(IRequestPreProcessor<>), services, assembliesToScan, true, configuration); diff --git a/src/MediatR/Wrappers/RequestHandlerWrapper.cs b/src/MediatR/Wrappers/RequestHandlerWrapper.cs index fb5e00a0..1550ecf8 100644 --- a/src/MediatR/Wrappers/RequestHandlerWrapper.cs +++ b/src/MediatR/Wrappers/RequestHandlerWrapper.cs @@ -1,17 +1,15 @@ using System; -using Microsoft.Extensions.DependencyInjection; - -namespace MediatR.Wrappers; - using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace MediatR.Wrappers; public abstract class RequestHandlerBase { public abstract Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken); - } public abstract class RequestHandlerWrapper : RequestHandlerBase @@ -20,21 +18,55 @@ public abstract Task Handle(IRequest request, IServiceProv CancellationToken cancellationToken); } +public abstract class RequestHandlerWrapper : RequestHandlerBase +{ + public abstract Task Handle(IRequest request, IServiceProvider serviceProvider, + CancellationToken cancellationToken); +} + public class RequestHandlerWrapperImpl : RequestHandlerWrapper where TRequest : IRequest { public override async Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken) => - await Handle((IRequest)request, serviceProvider, cancellationToken).ConfigureAwait(false); + await Handle((IRequest) request, serviceProvider, cancellationToken).ConfigureAwait(false); public override Task Handle(IRequest request, IServiceProvider serviceProvider, CancellationToken cancellationToken) { - Task Handler() => serviceProvider.GetRequiredService>().Handle((TRequest) request, cancellationToken); + Task Handler() => serviceProvider.GetRequiredService>() + .Handle((TRequest) request, cancellationToken); return serviceProvider .GetServices>() .Reverse() - .Aggregate((RequestHandlerDelegate) Handler, (next, pipeline) => () => pipeline.Handle((TRequest)request, next, cancellationToken))(); + .Aggregate((RequestHandlerDelegate) Handler, + (next, pipeline) => () => pipeline.Handle((TRequest) request, next, cancellationToken))(); + } +} + +public class RequestHandlerWrapperImpl : RequestHandlerWrapper + where TRequest : IRequest +{ + public override async Task Handle(object request, IServiceProvider serviceProvider, + CancellationToken cancellationToken) => + await Handle((IRequest) request, serviceProvider, cancellationToken).ConfigureAwait(false); + + public override Task Handle(IRequest request, IServiceProvider serviceProvider, + CancellationToken cancellationToken) + { + async Task Handler() + { + await serviceProvider.GetRequiredService>() + .Handle((TRequest) request, cancellationToken); + + return Unit.Value; + } + + return serviceProvider + .GetServices>() + .Reverse() + .Aggregate((RequestHandlerDelegate) Handler, + (next, pipeline) => () => pipeline.Handle((TRequest) request, next, cancellationToken))(); } } \ No newline at end of file diff --git a/test/MediatR.Tests/ExceptionTests.cs b/test/MediatR.Tests/ExceptionTests.cs index 30878713..9b8e67a2 100644 --- a/test/MediatR.Tests/ExceptionTests.cs +++ b/test/MediatR.Tests/ExceptionTests.cs @@ -61,11 +61,11 @@ public Task Handle(NullPing request, CancellationToken cancellationToken) } } - public class VoidNullPingHandler : IRequestHandler + public class VoidNullPingHandler : IRequestHandler { - public Task Handle(VoidNullPing request, CancellationToken cancellationToken) + public Task Handle(VoidNullPing request, CancellationToken cancellationToken) { - return Unit.Task; + return Task.CompletedTask; } } @@ -244,7 +244,7 @@ public class PingException : IRequest public class PingExceptionHandler : IRequestHandler { - public Task Handle(PingException request, CancellationToken cancellationToken) + public Task Handle(PingException request, CancellationToken cancellationToken) { throw new NotImplementedException(); } @@ -261,6 +261,7 @@ public async Task Should_throw_exception_for_non_generic_send_when_exception_occ scanner.IncludeNamespaceContainingType(); scanner.WithDefaultConventions(); scanner.AddAllTypesOf(typeof(IRequestHandler<,>)); + scanner.AddAllTypesOf(typeof(IRequestHandler<>)); }); cfg.For().Use(); }); @@ -309,6 +310,7 @@ public async Task Should_throw_exception_for_generic_send_when_exception_occurs( scanner.IncludeNamespaceContainingType(); scanner.WithDefaultConventions(); scanner.AddAllTypesOf(typeof(IRequestHandler<,>)); + scanner.AddAllTypesOf(typeof(IRequestHandler<>)); }); cfg.For().Use(); }); diff --git a/test/MediatR.Tests/GenericTypeConstraintsTests.cs b/test/MediatR.Tests/GenericTypeConstraintsTests.cs index 254b4756..5a84f126 100644 --- a/test/MediatR.Tests/GenericTypeConstraintsTests.cs +++ b/test/MediatR.Tests/GenericTypeConstraintsTests.cs @@ -58,12 +58,12 @@ public class Jing : IRequest public string? Message { get; set; } } - public class JingHandler : IRequestHandler + public class JingHandler : IRequestHandler { - public Task Handle(Jing request, CancellationToken cancellationToken) + public Task Handle(Jing request, CancellationToken cancellationToken) { // empty handle - return Unit.Task; + return Task.CompletedTask; } } @@ -98,6 +98,7 @@ public GenericTypeConstraintsTests() scanner.IncludeNamespaceContainingType(); scanner.WithDefaultConventions(); scanner.AddAllTypesOf(typeof(IRequestHandler<,>)); + scanner.AddAllTypesOf(typeof(IRequestHandler<>)); }); cfg.For().Use(); }); @@ -119,15 +120,15 @@ public async Task Should_Resolve_Void_Return_Request() // Assert it is of type IRequest and IRequest Assert.True(genericTypeConstraintsVoidReturn.IsIRequest); - Assert.True(genericTypeConstraintsVoidReturn.IsIRequestT); + Assert.False(genericTypeConstraintsVoidReturn.IsIRequestT); Assert.True(genericTypeConstraintsVoidReturn.IsIBaseRequest); - // Verify it is of IRequest and IBaseRequest and IRequest + // Verify it is of IRequest and IBaseRequest var results = genericTypeConstraintsVoidReturn.Handle(jing); - Assert.Equal(3, results.Length); + Assert.Equal(2, results.Length); - results.ShouldContain(typeof(IRequest)); + results.ShouldNotContain(typeof(IRequest)); results.ShouldContain(typeof(IBaseRequest)); results.ShouldContain(typeof(IRequest)); } diff --git a/test/MediatR.Tests/MicrosoftExtensionsDI/AssemblyResolutionTests.cs b/test/MediatR.Tests/MicrosoftExtensionsDI/AssemblyResolutionTests.cs index 8c3ee483..83ed7998 100644 --- a/test/MediatR.Tests/MicrosoftExtensionsDI/AssemblyResolutionTests.cs +++ b/test/MediatR.Tests/MicrosoftExtensionsDI/AssemblyResolutionTests.cs @@ -35,7 +35,7 @@ public void ShouldResolveRequestHandler() [Fact] public void ShouldResolveInternalHandler() { - _provider.GetService>().ShouldNotBeNull(); + _provider.GetService>().ShouldNotBeNull(); } [Fact] diff --git a/test/MediatR.Tests/MicrosoftExtensionsDI/Handlers.cs b/test/MediatR.Tests/MicrosoftExtensionsDI/Handlers.cs index 4e241846..62768991 100644 --- a/test/MediatR.Tests/MicrosoftExtensionsDI/Handlers.cs +++ b/test/MediatR.Tests/MicrosoftExtensionsDI/Handlers.cs @@ -59,7 +59,7 @@ public Task Handle(INotification notification, CancellationToken cancellationTok public class DingAsyncHandler : IRequestHandler { - public Task Handle(Ding message, CancellationToken cancellationToken) => Unit.Task; + public Task Handle(Ding message, CancellationToken cancellationToken) => Task.CompletedTask; } public class PingedHandler : INotificationHandler @@ -166,7 +166,7 @@ public Task Handle(DuplicateTest message, CancellationToken cancellation class InternalPingHandler : IRequestHandler { - public Task Handle(InternalPing request, CancellationToken cancellationToken) => Unit.Task; + public Task Handle(InternalPing request, CancellationToken cancellationToken) => Task.CompletedTask; } class MyCustomMediator : IMediator @@ -201,6 +201,11 @@ public Task Send(IRequest request, Cancellation { throw new System.NotImplementedException(); } + public Task Send(TRequest request, CancellationToken cancellationToken = default) + where TRequest : IRequest + { + throw new System.NotImplementedException(); + } } } diff --git a/test/MediatR.Tests/MicrosoftExtensionsDI/TypeResolutionTests.cs b/test/MediatR.Tests/MicrosoftExtensionsDI/TypeResolutionTests.cs index 7761d135..ab47fe22 100644 --- a/test/MediatR.Tests/MicrosoftExtensionsDI/TypeResolutionTests.cs +++ b/test/MediatR.Tests/MicrosoftExtensionsDI/TypeResolutionTests.cs @@ -47,7 +47,7 @@ public void ShouldResolveRequestHandler() [Fact] public void ShouldResolveVoidRequestHandler() { - _provider.GetService>().ShouldNotBeNull(); + _provider.GetService>().ShouldNotBeNull(); } [Fact] diff --git a/test/MediatR.Tests/PipelineTests.cs b/test/MediatR.Tests/PipelineTests.cs index 6c3af50c..31241e5b 100644 --- a/test/MediatR.Tests/PipelineTests.cs +++ b/test/MediatR.Tests/PipelineTests.cs @@ -20,6 +20,11 @@ public class Pong public string? Message { get; set; } } + public class VoidPing : IRequest + { + public string? Message { get; set; } + } + public class Zing : IRequest { public string? Message { get; set; } @@ -45,6 +50,21 @@ public Task Handle(Ping request, CancellationToken cancellationToken) } } + public class VoidPingHandler : IRequestHandler + { + private readonly Logger _output; + + public VoidPingHandler(Logger output) + { + _output = output; + } + public Task Handle(VoidPing request, CancellationToken cancellationToken) + { + _output.Messages.Add("Handler"); + return Task.CompletedTask; + } + } + public class ZingHandler : IRequestHandler { private readonly Logger _output; @@ -79,6 +99,25 @@ public async Task Handle(Ping request, RequestHandlerDelegate next, } } + public class OuterVoidBehavior : IPipelineBehavior + { + private readonly Logger _output; + + public OuterVoidBehavior(Logger output) + { + _output = output; + } + + public async Task Handle(VoidPing request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + _output.Messages.Add("Outer before"); + var response = await next(); + _output.Messages.Add("Outer after"); + + return response; + } + } + public class InnerBehavior : IPipelineBehavior { private readonly Logger _output; @@ -98,6 +137,25 @@ public async Task Handle(Ping request, RequestHandlerDelegate next, } } + public class InnerVoidBehavior : IPipelineBehavior + { + private readonly Logger _output; + + public InnerVoidBehavior(Logger output) + { + _output = output; + } + + public async Task Handle(VoidPing request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + _output.Messages.Add("Inner before"); + var response = await next(); + _output.Messages.Add("Inner after"); + + return response; + } + } + public class InnerBehavior : IPipelineBehavior where TRequest : notnull { @@ -217,6 +275,39 @@ public async Task Should_wrap_with_behavior() }); } + [Fact] + public async Task Should_wrap_void_with_behavior() + { + var output = new Logger(); + var container = new Container(cfg => + { + cfg.Scan(scanner => + { + scanner.AssemblyContainingType(typeof(PublishTests)); + scanner.IncludeNamespaceContainingType(); + scanner.WithDefaultConventions(); + scanner.AddAllTypesOf(typeof(IRequestHandler<>)); + }); + cfg.For().Use(output); + cfg.For>().Add(); + cfg.For>().Add(); + cfg.For().Use(); + }); + + var mediator = container.GetInstance(); + + await mediator.Send(new VoidPing { Message = "Ping" }); + + output.Messages.ShouldBe(new [] + { + "Outer before", + "Inner before", + "Handler", + "Inner after", + "Outer after" + }); + } + [Fact] public async Task Should_wrap_generics_with_behavior() { @@ -238,8 +329,6 @@ public async Task Should_wrap_generics_with_behavior() cfg.For().Use(); }); - container.GetAllInstances>(); - var mediator = container.GetInstance(); var response = await mediator.Send(new Ping { Message = "Ping" }); @@ -256,6 +345,42 @@ public async Task Should_wrap_generics_with_behavior() }); } + [Fact] + public async Task Should_wrap_void_generics_with_behavior() + { + var output = new Logger(); + var container = new Container(cfg => + { + cfg.Scan(scanner => + { + scanner.AssemblyContainingType(typeof(PublishTests)); + scanner.IncludeNamespaceContainingType(); + scanner.WithDefaultConventions(); + scanner.AddAllTypesOf(typeof(IRequestHandler<,>)); + scanner.AddAllTypesOf(typeof(IRequestHandler<>)); + }); + cfg.For().Use(output); + + cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(OuterBehavior<,>)); + cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(InnerBehavior<,>)); + + cfg.For().Use(); + }); + + var mediator = container.GetInstance(); + + await mediator.Send(new VoidPing { Message = "Ping" }); + + output.Messages.ShouldBe(new[] + { + "Outer generic before", + "Inner generic before", + "Handler", + "Inner generic after", + "Outer generic after", + }); + } + [Fact] public async Task Should_handle_constrained_generics() { diff --git a/test/MediatR.Tests/RequestHandlerTests.cs b/test/MediatR.Tests/RequestHandlerTests.cs deleted file mode 100644 index b7474c51..00000000 --- a/test/MediatR.Tests/RequestHandlerTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading.Tasks; -using Shouldly; -using Xunit; - -namespace MediatR.Tests; - -public class RequestHandlerTests -{ - public class Ping : IRequest - { - public string? Message { get; set; } - } - - public class Pong - { - public string? Message { get; set; } - } - - public class PingHandler : RequestHandler - { - protected override Pong Handle(Ping request) - { - return new Pong { Message = request.Message + " Pong" }; - } - } - - [Fact] - public async Task Should_call_abstract_handler() - { - IRequestHandler handler = new PingHandler(); - - var response = await handler.Handle(new Ping() { Message = "Ping" }, default); - - response.Message.ShouldBe("Ping Pong"); - } -} \ No newline at end of file diff --git a/test/MediatR.Tests/RequestHandlerUnitTests.cs b/test/MediatR.Tests/RequestHandlerUnitTests.cs deleted file mode 100644 index b9428399..00000000 --- a/test/MediatR.Tests/RequestHandlerUnitTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Shouldly; -using Xunit; - -namespace MediatR.Tests; - -public class RequestHandlerUnitTests -{ - public class Ping : IRequest - { - public string? Message { get; set; } - } - - public class PingHandler : RequestHandler - { - private readonly TextWriter _writer; - - public PingHandler(TextWriter writer) - { - _writer = writer; - } - - protected override void Handle(Ping request) - { - _writer.WriteLine(request.Message + " Pong"); - } - } - - [Fact] - public async Task Should_call_abstract_unit_handler() - { - var builder = new StringBuilder(); - var writer = new StringWriter(builder); - - IRequestHandler handler = new PingHandler(writer); - - await handler.Handle(new Ping() { Message = "Ping" }, default); - - var result = builder.ToString(); - result.ShouldContain("Ping Pong"); - } -} \ No newline at end of file diff --git a/test/MediatR.Tests/SendTests.cs b/test/MediatR.Tests/SendTests.cs index 3e2af0d5..466ba72b 100644 --- a/test/MediatR.Tests/SendTests.cs +++ b/test/MediatR.Tests/SendTests.cs @@ -16,6 +16,10 @@ public class Ping : IRequest public string? Message { get; set; } } + public class VoidPing : IRequest + { + } + public class Pong { public string? Message { get; set; } @@ -29,6 +33,25 @@ public Task Handle(Ping request, CancellationToken cancellationToken) } } + public class Dependency + { + public bool Called { get; set; } + } + + public class VoidPingHandler : IRequestHandler + { + private readonly Dependency _dependency; + + public VoidPingHandler(Dependency dependency) => _dependency = dependency; + + public Task Handle(VoidPing request, CancellationToken cancellationToken) + { + _dependency.Called = true; + + return Task.CompletedTask; + } + } + [Fact] public async Task Should_resolve_main_handler() { @@ -51,6 +74,32 @@ public async Task Should_resolve_main_handler() response.Message.ShouldBe("Ping Pong"); } + [Fact] + public async Task Should_resolve_main_void_handler() + { + var dependency = new Dependency(); + + var container = new Container(cfg => + { + cfg.Scan(scanner => + { + scanner.AssemblyContainingType(typeof(PublishTests)); + scanner.IncludeNamespaceContainingType(); + scanner.WithDefaultConventions(); + scanner.AddAllTypesOf(typeof(IRequestHandler<>)); + scanner.AddAllTypesOf(typeof(IRequestHandler<,>)); + }); + cfg.ForSingletonOf().Use(dependency); + cfg.For().Use(); + }); + + var mediator = container.GetInstance(); + + await mediator.Send(new VoidPing()); + + dependency.Called.ShouldBeTrue(); + } + [Fact] public async Task Should_resolve_main_handler_via_dynamic_dispatch() { @@ -75,6 +124,35 @@ public async Task Should_resolve_main_handler_via_dynamic_dispatch() pong.Message.ShouldBe("Ping Pong"); } + [Fact] + public async Task Should_resolve_main_void_handler_via_dynamic_dispatch() + { + var dependency = new Dependency(); + + var container = new Container(cfg => + { + cfg.Scan(scanner => + { + scanner.AssemblyContainingType(typeof(PublishTests)); + scanner.IncludeNamespaceContainingType(); + scanner.WithDefaultConventions(); + scanner.AddAllTypesOf(typeof(IRequestHandler<>)); + scanner.AddAllTypesOf(typeof(IRequestHandler<,>)); + }); + cfg.ForSingletonOf().Use(dependency); + cfg.For().Use(); + }); + + var mediator = container.GetInstance(); + + object request = new VoidPing(); + var response = await mediator.Send(request); + + response.ShouldBeOfType(); + + dependency.Called.ShouldBeTrue(); + } + [Fact] public async Task Should_resolve_main_handler_by_specific_interface() { diff --git a/test/MediatR.Tests/SendVoidInterfaceTests.cs b/test/MediatR.Tests/SendVoidInterfaceTests.cs index 2f7f9056..52bfb83d 100644 --- a/test/MediatR.Tests/SendVoidInterfaceTests.cs +++ b/test/MediatR.Tests/SendVoidInterfaceTests.cs @@ -16,13 +16,13 @@ public class Ping : IRequest public string? Message { get; set; } } - public class PingHandler : AsyncRequestHandler + public class PingHandler : IRequestHandler { private readonly TextWriter _writer; public PingHandler(TextWriter writer) => _writer = writer; - protected override Task Handle(Ping request, CancellationToken cancellationToken) + public Task Handle(Ping request, CancellationToken cancellationToken) => _writer.WriteAsync(request.Message + " Pong"); } @@ -40,6 +40,7 @@ public async Task Should_resolve_main_void_handler() scanner.IncludeNamespaceContainingType(); scanner.WithDefaultConventions(); scanner.AddAllTypesOf(typeof (IRequestHandler<,>)); + scanner.AddAllTypesOf(typeof (IRequestHandler<>)); }); cfg.For().Use(writer); cfg.For().Use();