├── NuGet.config ├── MediatR.snk ├── src ├── TestApp │ ├── Pong.cs │ ├── Ponged.cs │ ├── Pinged.cs │ ├── Ping.cs │ ├── Jing.cs │ ├── TestApp.csproj │ ├── GenericHandler.cs │ ├── JingHandler.cs │ ├── GenericRequestPreProcessor.cs │ ├── ConstrainedPingedHandler.cs │ ├── GenericRequestPostProcessor.cs │ ├── ConstrainedRequestPostProcessor.cs │ ├── PingHandler.cs │ ├── GenericPipelineBehavior.cs │ ├── PingPongExceptionHandlers.cs │ ├── Program.cs │ ├── PingedHandler.cs │ └── Runner.cs └── MediatR.Extensions.Microsoft.DependencyInjection │ ├── RequestExceptionActionProcessorStrategy.cs │ ├── MediatrServiceConfiguration.cs │ ├── MediatR.Extensions.Microsoft.DependencyInjection.csproj │ ├── ServiceCollectionExtensions.cs │ └── Registration │ └── ServiceRegistrar.cs ├── README.md ├── assets └── logo │ ├── flat_128x128.png │ ├── flat_32x32.png │ ├── flat_64x64.png │ ├── gradient_32x32.png │ ├── gradient_64x64.png │ └── gradient_128x128.png ├── test └── MediatR.Extensions.Microsoft.DependencyInjection.Tests │ ├── Properties │ └── AssemblyInfo.cs │ ├── DuplicateAssemblyResolutionTests.cs │ ├── DerivingRequestsTests.cs │ ├── TypeEvaluatorTests.cs │ ├── MediatR.Extensions.Microsoft.DependencyInjection.Tests.csproj │ ├── AssemblyResolutionTests.cs │ ├── CustomMediatorTests.cs │ ├── TypeResolutionTests.cs │ ├── StreamPipelineTests.cs │ ├── PipeLineMultiCallToConstructorTest.cs │ ├── Handlers.cs │ └── PipelineTests.cs ├── Directory.Build.props ├── .gitattributes ├── Push.ps1 ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── LICENSE ├── .vscode ├── launch.json └── tasks.json ├── Build.ps1 ├── MediatR.DI.sln └── .gitignore /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /MediatR.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/HEAD/MediatR.snk -------------------------------------------------------------------------------- /src/TestApp/Pong.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp; 2 | 3 | public class Pong 4 | { 5 | public string? Message { get; set; } 6 | } -------------------------------------------------------------------------------- /src/TestApp/Ponged.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace TestApp; 4 | 5 | public class Ponged : INotification 6 | { 7 | 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo is archived and the functionality is moved into the main [MediatR repository](https://github.com/jbogard/MediatR). 2 | -------------------------------------------------------------------------------- /src/TestApp/Pinged.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace TestApp; 4 | 5 | public class Pinged : INotification 6 | { 7 | 8 | } -------------------------------------------------------------------------------- /assets/logo/flat_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/HEAD/assets/logo/flat_128x128.png -------------------------------------------------------------------------------- /assets/logo/flat_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/HEAD/assets/logo/flat_32x32.png -------------------------------------------------------------------------------- /assets/logo/flat_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/HEAD/assets/logo/flat_64x64.png -------------------------------------------------------------------------------- /assets/logo/gradient_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/HEAD/assets/logo/gradient_32x32.png -------------------------------------------------------------------------------- /assets/logo/gradient_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/HEAD/assets/logo/gradient_64x64.png -------------------------------------------------------------------------------- /assets/logo/gradient_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/HEAD/assets/logo/gradient_128x128.png -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] 2 | -------------------------------------------------------------------------------- /src/TestApp/Ping.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace TestApp; 4 | 5 | public class Ping : IRequest 6 | { 7 | public string? Message { get; set; } 8 | public bool Throw { get; set; } 9 | } -------------------------------------------------------------------------------- /src/MediatR.Extensions.Microsoft.DependencyInjection/RequestExceptionActionProcessorStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace MediatR; 2 | 3 | public enum RequestExceptionActionProcessorStrategy 4 | { 5 | ApplyForUnhandledExceptions, 6 | ApplyForAllExceptions 7 | } -------------------------------------------------------------------------------- /src/TestApp/Jing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MediatR; 7 | 8 | namespace TestApp; 9 | 10 | public class Jing : IRequest 11 | { 12 | public string? Message { get; set; } 13 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Jimmy Bogard 4 | 10.0 5 | enable 6 | $(NoWarn);1701;1702;1591 7 | true 8 | Apache-2.0 9 | v 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | core.eol crlf 17 | 18 | *.cs diff=csharp 19 | 20 | *.csproj merge=union 21 | *.vbproj merge=union 22 | *.fsproj merge=union 23 | *.dbproj merge=union 24 | *.sln merge=union 25 | -------------------------------------------------------------------------------- /src/TestApp/TestApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/TestApp/GenericHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | 5 | namespace TestApp; 6 | 7 | using System.IO; 8 | 9 | public class GenericHandler : INotificationHandler 10 | { 11 | private readonly TextWriter _writer; 12 | 13 | public GenericHandler(TextWriter writer) 14 | { 15 | _writer = writer; 16 | } 17 | 18 | public Task Handle(INotification notification, CancellationToken cancellationToken) 19 | { 20 | return _writer.WriteLineAsync("Got notified."); 21 | } 22 | } -------------------------------------------------------------------------------- /src/TestApp/JingHandler.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | 6 | namespace TestApp; 7 | 8 | public class JingHandler : AsyncRequestHandler 9 | { 10 | private readonly TextWriter _writer; 11 | 12 | public JingHandler(TextWriter writer) 13 | { 14 | _writer = writer; 15 | } 16 | 17 | protected override Task Handle(Jing request, CancellationToken cancellationToken) 18 | { 19 | return _writer.WriteLineAsync($"--- Handled Jing: {request.Message}, no Jong"); 20 | } 21 | } -------------------------------------------------------------------------------- /Push.ps1: -------------------------------------------------------------------------------- 1 | $scriptName = $MyInvocation.MyCommand.Name 2 | $artifacts = "./artifacts" 3 | 4 | if ([string]::IsNullOrEmpty($Env:NUGET_API_KEY)) { 5 | Write-Host "${scriptName}: NUGET_API_KEY is empty or not set. Skipped pushing package(s)." 6 | } else { 7 | Get-ChildItem $artifacts -Filter "*.nupkg" | ForEach-Object { 8 | Write-Host "$($scriptName): Pushing $($_.Name)" 9 | dotnet nuget push $_ --source $Env:NUGET_URL --api-key $Env:NUGET_API_KEY 10 | if ($lastexitcode -ne 0) { 11 | throw ("Exec: " + $errorMessage) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/TestApp/GenericRequestPreProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR.Pipeline; 5 | 6 | namespace TestApp; 7 | 8 | public class GenericRequestPreProcessor : IRequestPreProcessor where TRequest : notnull 9 | { 10 | private readonly TextWriter _writer; 11 | 12 | public GenericRequestPreProcessor(TextWriter writer) 13 | { 14 | _writer = writer; 15 | } 16 | 17 | public Task Process(TRequest request, CancellationToken cancellationToken) 18 | { 19 | return _writer.WriteLineAsync("- Starting Up"); 20 | } 21 | } -------------------------------------------------------------------------------- /src/TestApp/ConstrainedPingedHandler.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | 6 | namespace TestApp; 7 | 8 | public class ConstrainedPingedHandler : INotificationHandler 9 | where TNotification : Pinged 10 | { 11 | private readonly TextWriter _writer; 12 | 13 | public ConstrainedPingedHandler(TextWriter writer) 14 | { 15 | _writer = writer; 16 | } 17 | 18 | public Task Handle(TNotification notification, CancellationToken cancellationToken) 19 | { 20 | return _writer.WriteLineAsync("Got pinged constrained async."); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/TestApp/GenericRequestPostProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | using MediatR.Pipeline; 6 | 7 | namespace TestApp; 8 | 9 | public class GenericRequestPostProcessor : IRequestPostProcessor 10 | where TRequest : IRequest 11 | 12 | { 13 | private readonly TextWriter _writer; 14 | 15 | public GenericRequestPostProcessor(TextWriter writer) 16 | { 17 | _writer = writer; 18 | } 19 | 20 | public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) 21 | { 22 | return _writer.WriteLineAsync("- All Done"); 23 | } 24 | } -------------------------------------------------------------------------------- /src/TestApp/ConstrainedRequestPostProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | using MediatR.Pipeline; 6 | 7 | namespace TestApp; 8 | 9 | public class ConstrainedRequestPostProcessor 10 | : IRequestPostProcessor 11 | where TRequest : Ping, IRequest 12 | { 13 | private readonly TextWriter _writer; 14 | 15 | public ConstrainedRequestPostProcessor(TextWriter writer) 16 | { 17 | _writer = writer; 18 | } 19 | 20 | public Task Process(TRequest request, TResponse response, CancellationToken token) 21 | { 22 | return _writer.WriteLineAsync("- All Done with Ping"); 23 | } 24 | } -------------------------------------------------------------------------------- /src/TestApp/PingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using MediatR; 5 | 6 | namespace TestApp; 7 | 8 | using System.Threading.Tasks; 9 | 10 | public class PingHandler : IRequestHandler 11 | { 12 | private readonly TextWriter _writer; 13 | 14 | public PingHandler(TextWriter writer) 15 | { 16 | _writer = writer; 17 | } 18 | 19 | public async Task Handle(Ping request, CancellationToken cancellationToken) 20 | { 21 | await _writer.WriteLineAsync($"--- Handled Ping: {request.Message}"); 22 | 23 | if (request.Throw) 24 | { 25 | throw new ApplicationException("Requested to throw"); 26 | } 27 | 28 | return new Pong { Message = request.Message + " Pong" }; 29 | } 30 | } -------------------------------------------------------------------------------- /src/TestApp/GenericPipelineBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | 6 | namespace TestApp; 7 | 8 | public class GenericPipelineBehavior : IPipelineBehavior 9 | where TRequest: IRequest 10 | { 11 | private readonly TextWriter _writer; 12 | 13 | public GenericPipelineBehavior(TextWriter writer) 14 | { 15 | _writer = writer; 16 | } 17 | 18 | public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) 19 | { 20 | await _writer.WriteLineAsync("-- Handling Request"); 21 | var response = await next(); 22 | await _writer.WriteLineAsync("-- Finished Request"); 23 | return response; 24 | } 25 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DuplicateAssemblyResolutionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 4 | 5 | using System; 6 | using System.Linq; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | public class DuplicateAssemblyResolutionTests 11 | { 12 | private readonly IServiceProvider _provider; 13 | 14 | public DuplicateAssemblyResolutionTests() 15 | { 16 | IServiceCollection services = new ServiceCollection(); 17 | services.AddSingleton(new Logger()); 18 | services.AddMediatR(typeof(Ping), typeof(Ping)); 19 | _provider = services.BuildServiceProvider(); 20 | } 21 | 22 | [Fact] 23 | public void ShouldResolveNotificationHandlersOnlyOnce() 24 | { 25 | _provider.GetServices>().Count().ShouldBe(3); 26 | } 27 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | os: [windows-latest] 15 | fail-fast: false 16 | runs-on: windows-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - name: Setup dotnet 6.0 23 | uses: actions/setup-dotnet@v1 24 | with: 25 | dotnet-version: '6.0.x' 26 | - name: Build and Test 27 | run: ./Build.ps1 28 | shell: pwsh 29 | - name: Push to MyGet 30 | env: 31 | NUGET_URL: https://www.myget.org/F/mediatr-ci/api/v3/index.json 32 | NUGET_API_KEY: ${{ secrets.MYGET_MEDIATR_CI_API_KEY }} 33 | run: ./Push.ps1 34 | shell: pwsh 35 | - name: Artifacts 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: artifacts 39 | path: artifacts/**/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jimmy Bogard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | jobs: 8 | build: 9 | strategy: 10 | matrix: 11 | os: [windows-latest] 12 | fail-fast: false 13 | runs-on: windows-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | - name: Setup dotnet 6.0 20 | uses: actions/setup-dotnet@v1 21 | with: 22 | dotnet-version: '6.0.x' 23 | - name: Build and Test 24 | run: ./Build.ps1 25 | shell: pwsh 26 | - name: Push to MyGet 27 | env: 28 | NUGET_URL: https://www.myget.org/F/mediatr-ci/api/v3/index.json 29 | NUGET_API_KEY: ${{ secrets.MYGET_MEDIATR_CI_API_KEY }} 30 | run: ./Push.ps1 31 | shell: pwsh 32 | - name: Push to NuGet 33 | env: 34 | NUGET_URL: https://api.nuget.org/v3/index.json 35 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 36 | run: ./Push.ps1 37 | shell: pwsh 38 | - name: Artifacts 39 | uses: actions/upload-artifact@v2 40 | with: 41 | name: artifacts 42 | path: artifacts/**/* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/src/TestApp/bin/Debug/netcoreapp2.1/TestApp.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/src/TestApp", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/DerivingRequestsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 8 | 9 | public class DerivingRequestsTests 10 | { 11 | private readonly IServiceProvider _provider; 12 | private readonly IMediator _mediator; 13 | 14 | public DerivingRequestsTests() 15 | { 16 | IServiceCollection services = new ServiceCollection(); 17 | services.AddSingleton(new Logger()); 18 | services.AddMediatR(typeof(Ping)); 19 | _provider = services.BuildServiceProvider(); 20 | _mediator = _provider.GetRequiredService(); 21 | } 22 | 23 | [Fact] 24 | public async Task ShouldReturnPingPong() 25 | { 26 | Pong pong = await _mediator.Send(new Ping() { Message = "Ping" }); 27 | pong.Message.ShouldBe("Ping Pong"); 28 | } 29 | 30 | [Fact] 31 | public async Task ShouldReturnDerivedPingPong() 32 | { 33 | Pong pong = await _mediator.Send(new DerivedPing() { Message = "Ping" }); 34 | pong.Message.ShouldBe("DerivedPing Pong"); 35 | } 36 | } -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | # Taken from psake https://github.com/psake/psake 2 | 3 | <# 4 | .SYNOPSIS 5 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode 6 | to see if an error occcured. If an error is detected then an exception is thrown. 7 | This function allows you to run command-line programs without having to 8 | explicitly check the $lastexitcode variable. 9 | .EXAMPLE 10 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" 11 | #> 12 | function Exec 13 | { 14 | [CmdletBinding()] 15 | param( 16 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, 17 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd) 18 | ) 19 | & $cmd 20 | if ($lastexitcode -ne 0) { 21 | throw ("Exec: " + $errorMessage) 22 | } 23 | } 24 | 25 | $artifacts = ".\artifacts" 26 | 27 | if(Test-Path $artifacts) { Remove-Item $artifacts -Force -Recurse } 28 | 29 | exec { & dotnet clean -c Release } 30 | 31 | exec { & dotnet build -c Release } 32 | 33 | exec { & dotnet test -c Release -r $artifacts --no-build -l trx --verbosity=normal } 34 | 35 | exec { dotnet pack .\src\MediatR.Extensions.Microsoft.DependencyInjection -c Release -o $artifacts --no-build } 36 | -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeEvaluatorTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 4 | 5 | using MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included; 6 | using Shouldly; 7 | using System; 8 | using System.Reflection; 9 | using Xunit; 10 | 11 | public class TypeEvaluatorTests 12 | { 13 | private readonly IServiceProvider _provider; 14 | 15 | public TypeEvaluatorTests() 16 | { 17 | IServiceCollection services = new ServiceCollection(); 18 | services.AddSingleton(new Logger()); 19 | services.AddMediatR(new[] { typeof(Ping).GetTypeInfo().Assembly }, cfg => 20 | { 21 | cfg.WithEvaluator(t => t.Namespace == "MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included"); 22 | }); 23 | _provider = services.BuildServiceProvider(); 24 | } 25 | 26 | [Fact] 27 | public void ShouldResolveMediator() 28 | { 29 | _provider.GetService().ShouldNotBeNull(); 30 | } 31 | 32 | [Fact] 33 | public void ShouldOnlyResolveIncludedRequestHandlers() 34 | { 35 | _provider.GetService>().ShouldNotBeNull(); 36 | _provider.GetService>().ShouldBeNull(); 37 | } 38 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/TestApp/TestApp.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/src/TestApp/TestApp.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/src/TestApp/TestApp.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /src/TestApp/PingPongExceptionHandlers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using MediatR.Pipeline; 6 | 7 | namespace TestApp; 8 | 9 | public class PingPongExceptionHandlerForType : IRequestExceptionHandler 10 | { 11 | public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) 12 | { 13 | state.SetHandled(new Pong { Message = exception.Message + " Handled by Type" }); 14 | 15 | return Task.CompletedTask; 16 | } 17 | } 18 | 19 | public class PingPongExceptionActionForType1 : IRequestExceptionAction 20 | { 21 | private readonly TextWriter _output; 22 | 23 | public PingPongExceptionActionForType1(TextWriter output) => _output = output; 24 | 25 | public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) 26 | => _output.WriteLineAsync("Logging exception 1"); 27 | } 28 | 29 | public class PingPongExceptionActionForType2 : IRequestExceptionAction 30 | { 31 | private readonly TextWriter _output; 32 | 33 | public PingPongExceptionActionForType2(TextWriter output) => _output = output; 34 | 35 | public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) 36 | => _output.WriteLineAsync("Logging exception 2"); 37 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/MediatR.Extensions.Microsoft.DependencyInjection.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | true 6 | MediatR.Extensions.Microsoft.DependencyInjection.Tests 7 | MediatR.Extensions.Microsoft.DependencyInjection.Tests 8 | true 9 | false 10 | false 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/MediatR.Extensions.Microsoft.DependencyInjection/MediatrServiceConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MediatR; 4 | 5 | using System; 6 | 7 | public class MediatRServiceConfiguration 8 | { 9 | public Func TypeEvaluator { get; private set; } = t => true; 10 | public Type MediatorImplementationType { get; private set; } 11 | public ServiceLifetime Lifetime { get; private set; } 12 | public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; } 13 | 14 | public MediatRServiceConfiguration() 15 | { 16 | MediatorImplementationType = typeof(Mediator); 17 | Lifetime = ServiceLifetime.Transient; 18 | } 19 | 20 | public MediatRServiceConfiguration Using() where TMediator : IMediator 21 | { 22 | MediatorImplementationType = typeof(TMediator); 23 | return this; 24 | } 25 | 26 | public MediatRServiceConfiguration AsSingleton() 27 | { 28 | Lifetime = ServiceLifetime.Singleton; 29 | return this; 30 | } 31 | 32 | public MediatRServiceConfiguration AsScoped() 33 | { 34 | Lifetime = ServiceLifetime.Scoped; 35 | return this; 36 | } 37 | 38 | public MediatRServiceConfiguration AsTransient() 39 | { 40 | Lifetime = ServiceLifetime.Transient; 41 | return this; 42 | } 43 | 44 | public MediatRServiceConfiguration WithEvaluator(Func evaluator) 45 | { 46 | TypeEvaluator = evaluator; 47 | return this; 48 | } 49 | } -------------------------------------------------------------------------------- /src/TestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using MediatR.Pipeline; 8 | 9 | namespace TestApp; 10 | 11 | public class Program 12 | { 13 | public static Task Main(string[] args) 14 | { 15 | var writer = new WrappingWriter(Console.Out); 16 | var mediator = BuildMediator(writer); 17 | return Runner.Run(mediator, writer, "ASP.NET Core DI"); 18 | } 19 | 20 | private static IMediator BuildMediator(WrappingWriter writer) 21 | { 22 | var services = new ServiceCollection(); 23 | 24 | services.AddScoped(p => p.GetRequiredService); 25 | 26 | services.AddSingleton(writer); 27 | 28 | //Pipeline 29 | 30 | //This causes a type load exception. https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/issues/12 31 | services.AddScoped(typeof(IRequestPostProcessor<,>), typeof(ConstrainedRequestPostProcessor<,>)); 32 | services.AddScoped(typeof(INotificationHandler<>), typeof(ConstrainedPingedHandler<>)); 33 | 34 | services.AddMediatR(typeof(Ping)); 35 | 36 | services.AddScoped(typeof(IPipelineBehavior<,>), typeof(GenericPipelineBehavior<,>)); 37 | 38 | foreach (var service in services) 39 | { 40 | Console.WriteLine(service.ServiceType + " - " + service.ImplementationType); 41 | } 42 | 43 | var provider = services.BuildServiceProvider(); 44 | 45 | return provider.GetRequiredService(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/MediatR.Extensions.Microsoft.DependencyInjection/MediatR.Extensions.Microsoft.DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MediatR extensions for ASP.NET Core 5 | Copyright Jimmy Bogard 6 | netstandard2.1 7 | MediatR.Extensions.Microsoft.DependencyInjection 8 | MediatR 9 | MediatR.Extensions.Microsoft.DependencyInjection 10 | mediator;request;response;queries;commands;notifications 11 | true 12 | ..\..\MediatR.snk 13 | gradient_128x128.png 14 | v 15 | Apache-2.0 16 | true 17 | true 18 | snupkg 19 | true 20 | true 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/AssemblyResolutionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Reflection; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | public class AssemblyResolutionTests 12 | { 13 | private readonly IServiceProvider _provider; 14 | 15 | public AssemblyResolutionTests() 16 | { 17 | IServiceCollection services = new ServiceCollection(); 18 | services.AddSingleton(new Logger()); 19 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 20 | _provider = services.BuildServiceProvider(); 21 | } 22 | 23 | [Fact] 24 | public void ShouldResolveMediator() 25 | { 26 | _provider.GetService().ShouldNotBeNull(); 27 | } 28 | 29 | [Fact] 30 | public void ShouldResolveRequestHandler() 31 | { 32 | _provider.GetService>().ShouldNotBeNull(); 33 | } 34 | 35 | [Fact] 36 | public void ShouldResolveInternalHandler() 37 | { 38 | _provider.GetService>().ShouldNotBeNull(); 39 | } 40 | 41 | [Fact] 42 | public void ShouldResolveNotificationHandlers() 43 | { 44 | _provider.GetServices>().Count().ShouldBe(3); 45 | } 46 | 47 | [Fact] 48 | public void ShouldResolveStreamHandlers() 49 | { 50 | _provider.GetService>().ShouldNotBeNull(); 51 | } 52 | 53 | [Fact] 54 | public void ShouldRequireAtLeastOneAssembly() 55 | { 56 | var services = new ServiceCollection(); 57 | 58 | Action registration = () => services.AddMediatR(new Type[0]); 59 | 60 | registration.ShouldThrow(); 61 | } 62 | } -------------------------------------------------------------------------------- /src/TestApp/PingedHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using MediatR; 3 | 4 | namespace TestApp; 5 | 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | 9 | public class PingedHandler : INotificationHandler 10 | { 11 | private readonly TextWriter _writer; 12 | 13 | public PingedHandler(TextWriter writer) 14 | { 15 | _writer = writer; 16 | } 17 | 18 | public Task Handle(Pinged notification, CancellationToken cancellationToken) 19 | { 20 | return _writer.WriteLineAsync("Got pinged async."); 21 | } 22 | } 23 | 24 | public class PongedHandler : INotificationHandler 25 | { 26 | private readonly TextWriter _writer; 27 | 28 | public PongedHandler(TextWriter writer) 29 | { 30 | _writer = writer; 31 | } 32 | 33 | public Task Handle(Ponged notification, CancellationToken cancellationToken) 34 | { 35 | return _writer.WriteLineAsync("Got ponged async."); 36 | } 37 | } 38 | 39 | //public class ConstrainedPingedHandler : INotificationHandler 40 | // where TNotification : Pinged 41 | //{ 42 | // private readonly TextWriter _writer; 43 | 44 | // public ConstrainedPingedHandler(TextWriter writer) 45 | // { 46 | // _writer = writer; 47 | // } 48 | 49 | // public Task Handle(TNotification notification, CancellationToken cancellationToken) 50 | // { 51 | // return _writer.WriteLineAsync("Got pinged constrained async."); 52 | // } 53 | //} 54 | 55 | public class PingedAlsoHandler : INotificationHandler 56 | { 57 | private readonly TextWriter _writer; 58 | 59 | public PingedAlsoHandler(TextWriter writer) 60 | { 61 | _writer = writer; 62 | } 63 | 64 | public Task Handle(Pinged notification, CancellationToken cancellationToken) 65 | { 66 | return _writer.WriteLineAsync("Got pinged also async."); 67 | } 68 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/CustomMediatorTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 4 | 5 | using System; 6 | using System.Linq; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | public class CustomMediatorTests 11 | { 12 | private readonly IServiceProvider _provider; 13 | 14 | public CustomMediatorTests() 15 | { 16 | IServiceCollection services = new ServiceCollection(); 17 | services.AddSingleton(new Logger()); 18 | services.AddMediatR(cfg => cfg.Using(), typeof(CustomMediatorTests)); 19 | _provider = services.BuildServiceProvider(); 20 | } 21 | 22 | [Fact] 23 | public void ShouldResolveMediator() 24 | { 25 | _provider.GetService().ShouldNotBeNull(); 26 | _provider.GetRequiredService().GetType().ShouldBe(typeof(MyCustomMediator)); 27 | } 28 | 29 | [Fact] 30 | public void ShouldResolveRequestHandler() 31 | { 32 | _provider.GetService>().ShouldNotBeNull(); 33 | } 34 | 35 | [Fact] 36 | public void ShouldResolveNotificationHandlers() 37 | { 38 | _provider.GetServices>().Count().ShouldBe(3); 39 | } 40 | 41 | [Fact] 42 | public void Can_Call_AddMediatr_multiple_times() 43 | { 44 | IServiceCollection services = new ServiceCollection(); 45 | services.AddSingleton(new Logger()); 46 | services.AddMediatR(cfg => cfg.Using(), typeof(CustomMediatorTests)); 47 | 48 | // Call AddMediatr again, this should NOT override our custom mediatr (With MS DI, last registration wins) 49 | services.AddMediatR(typeof(CustomMediatorTests)); 50 | 51 | var provider = services.BuildServiceProvider(); 52 | var mediator = provider.GetRequiredService(); 53 | mediator.GetType().ShouldBe(typeof(MyCustomMediator)); 54 | } 55 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/TypeResolutionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Reflection; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | public class TypeResolutionTests 12 | { 13 | private readonly IServiceProvider _provider; 14 | 15 | public TypeResolutionTests() 16 | { 17 | IServiceCollection services = new ServiceCollection(); 18 | services.AddSingleton(new Logger()); 19 | services.AddMediatR(typeof(Ping)); 20 | _provider = services.BuildServiceProvider(); 21 | } 22 | 23 | [Fact] 24 | public void ShouldResolveMediator() 25 | { 26 | _provider.GetService().ShouldNotBeNull(); 27 | } 28 | 29 | [Fact] 30 | public void ShouldResolveSender() 31 | { 32 | _provider.GetService().ShouldNotBeNull(); 33 | } 34 | 35 | [Fact] 36 | public void ShouldResolvePublisher() 37 | { 38 | _provider.GetService().ShouldNotBeNull(); 39 | } 40 | 41 | [Fact] 42 | public void ShouldResolveRequestHandler() 43 | { 44 | _provider.GetService>().ShouldNotBeNull(); 45 | } 46 | 47 | [Fact] 48 | public void ShouldResolveVoidRequestHandler() 49 | { 50 | _provider.GetService>().ShouldNotBeNull(); 51 | } 52 | 53 | [Fact] 54 | public void ShouldResolveNotificationHandlers() 55 | { 56 | _provider.GetServices>().Count().ShouldBe(3); 57 | } 58 | 59 | [Fact] 60 | public void ShouldResolveFirstDuplicateHandler() 61 | { 62 | _provider.GetService>().ShouldNotBeNull(); 63 | _provider.GetService>() 64 | .ShouldBeAssignableTo(); 65 | } 66 | 67 | [Fact] 68 | public void ShouldResolveIgnoreSecondDuplicateHandler() 69 | { 70 | _provider.GetServices>().Count().ShouldBe(1); 71 | } 72 | } -------------------------------------------------------------------------------- /MediatR.DI.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1082ED20-13D1-444F-BAFD-17EFB9152981}" 7 | ProjectSection(SolutionItems) = preProject 8 | Build.ps1 = Build.ps1 9 | .github\workflows\ci.yml = .github\workflows\ci.yml 10 | Directory.Build.props = Directory.Build.props 11 | Push.ps1 = Push.ps1 12 | README.md = README.md 13 | .github\workflows\release.yml = .github\workflows\release.yml 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediatR.Extensions.Microsoft.DependencyInjection", "src\MediatR.Extensions.Microsoft.DependencyInjection\MediatR.Extensions.Microsoft.DependencyInjection.csproj", "{CD3BCC44-44C0-4A5A-9041-34B558A9FBF2}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediatR.Extensions.Microsoft.DependencyInjection.Tests", "test\MediatR.Extensions.Microsoft.DependencyInjection.Tests\MediatR.Extensions.Microsoft.DependencyInjection.Tests.csproj", "{A93A7F85-292A-4130-891D-4307D3F60C30}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp", "src\TestApp\TestApp.csproj", "{DE95F633-80B5-4248-A594-7FB357C8DAC9}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {CD3BCC44-44C0-4A5A-9041-34B558A9FBF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {CD3BCC44-44C0-4A5A-9041-34B558A9FBF2}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {CD3BCC44-44C0-4A5A-9041-34B558A9FBF2}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {CD3BCC44-44C0-4A5A-9041-34B558A9FBF2}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {A93A7F85-292A-4130-891D-4307D3F60C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {A93A7F85-292A-4130-891D-4307D3F60C30}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {A93A7F85-292A-4130-891D-4307D3F60C30}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {A93A7F85-292A-4130-891D-4307D3F60C30}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {DE95F633-80B5-4248-A594-7FB357C8DAC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {DE95F633-80B5-4248-A594-7FB357C8DAC9}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {DE95F633-80B5-4248-A594-7FB357C8DAC9}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {DE95F633-80B5-4248-A594-7FB357C8DAC9}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(ExtensibilityGlobals) = postSolution 45 | SolutionGuid = {531488EE-5812-470F-A050-73EB27DFF03B} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/StreamPipelineTests.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using Pipeline; 13 | using Shouldly; 14 | using Xunit; 15 | 16 | public class StreamPipelineTests 17 | { 18 | public class OuterBehavior : IStreamPipelineBehavior 19 | { 20 | private readonly Logger _output; 21 | 22 | public OuterBehavior(Logger output) 23 | { 24 | _output = output; 25 | } 26 | 27 | public async IAsyncEnumerable Handle(StreamPing request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) 28 | { 29 | _output.Messages.Add("Outer before"); 30 | await foreach (var response in next().WithCancellation(cancellationToken)) 31 | { 32 | yield return response; 33 | } 34 | _output.Messages.Add("Outer after"); 35 | } 36 | } 37 | 38 | public class InnerBehavior : IStreamPipelineBehavior 39 | { 40 | private readonly Logger _output; 41 | 42 | public InnerBehavior(Logger output) 43 | { 44 | _output = output; 45 | } 46 | 47 | public async IAsyncEnumerable Handle(StreamPing request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) 48 | { 49 | _output.Messages.Add("Inner before"); 50 | await foreach (var response in next().WithCancellation(cancellationToken)) 51 | { 52 | yield return response; 53 | } 54 | _output.Messages.Add("Inner after"); 55 | } 56 | } 57 | 58 | [Fact] 59 | public async Task Should_wrap_with_behavior() 60 | { 61 | var output = new Logger(); 62 | IServiceCollection services = new ServiceCollection(); 63 | services.AddSingleton(output); 64 | services.AddTransient, OuterBehavior>(); 65 | services.AddTransient, InnerBehavior>(); 66 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 67 | var provider = services.BuildServiceProvider(); 68 | 69 | var mediator = provider.GetRequiredService(); 70 | 71 | var stream = mediator.CreateStream(new StreamPing { Message = "Ping" }); 72 | 73 | await foreach (var response in stream) 74 | { 75 | response.Message.ShouldBe("Ping Pang"); 76 | } 77 | 78 | output.Messages.ShouldBe(new[] 79 | { 80 | "Outer before", 81 | "Inner before", 82 | "Handler", 83 | "Inner after", 84 | "Outer after" 85 | }); 86 | } 87 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipeLineMultiCallToConstructorTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 5 | 6 | using System.Reflection; 7 | using System.Threading.Tasks; 8 | using Shouldly; 9 | using Xunit; 10 | 11 | public class PipelineMultiCallToConstructorTests 12 | { 13 | public class ConstructorTestBehavior : IPipelineBehavior 14 | where TRequest : IRequest 15 | { 16 | private readonly Logger _output; 17 | 18 | public ConstructorTestBehavior(Logger output) => _output = output; 19 | 20 | public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) 21 | { 22 | _output.Messages.Add("ConstructorTestBehavior before"); 23 | var response = await next(); 24 | _output.Messages.Add("ConstructorTestBehavior after"); 25 | 26 | return response; 27 | } 28 | } 29 | 30 | public class ConstructorTestRequest : IRequest 31 | { 32 | public string? Message { get; set; } 33 | } 34 | 35 | public class ConstructorTestResponse 36 | { 37 | public string? Message { get; set; } 38 | } 39 | 40 | public class ConstructorTestHandler : IRequestHandler 41 | { 42 | 43 | private static volatile object _lockObject = new(); 44 | private readonly Logger _logger; 45 | private static int _constructorCallCount; 46 | 47 | public static int ConstructorCallCount => _constructorCallCount; 48 | 49 | public static void ResetCallCount() 50 | { 51 | lock (_lockObject) 52 | { 53 | _constructorCallCount = 0; 54 | } 55 | } 56 | 57 | public ConstructorTestHandler(Logger logger) 58 | { 59 | _logger = logger; 60 | lock (_lockObject) 61 | { 62 | _constructorCallCount++; 63 | } 64 | } 65 | 66 | public Task Handle(ConstructorTestRequest request, CancellationToken cancellationToken) 67 | { 68 | _logger.Messages.Add("Handler"); 69 | return Task.FromResult(new ConstructorTestResponse { Message = request.Message + " ConstructorPong" }); 70 | } 71 | } 72 | 73 | [Fact] 74 | public async Task Should_not_call_constructor_multiple_times_when_using_a_pipeline() 75 | { 76 | ConstructorTestHandler.ResetCallCount(); 77 | ConstructorTestHandler.ConstructorCallCount.ShouldBe(0); 78 | 79 | var output = new Logger(); 80 | IServiceCollection services = new ServiceCollection(); 81 | 82 | services.AddSingleton(output); 83 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstructorTestBehavior<,>)); 84 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 85 | var provider = services.BuildServiceProvider(); 86 | 87 | var mediator = provider.GetRequiredService(); 88 | 89 | var response = await mediator.Send(new ConstructorTestRequest { Message = "ConstructorPing" }); 90 | 91 | response.Message.ShouldBe("ConstructorPing ConstructorPong"); 92 | 93 | output.Messages.ShouldBe(new[] 94 | { 95 | "ConstructorTestBehavior before", 96 | "First pre processor", 97 | "Next pre processor", 98 | "Handler", 99 | "First post processor", 100 | "Next post processor", 101 | "ConstructorTestBehavior after" 102 | }); 103 | ConstructorTestHandler.ConstructorCallCount.ShouldBe(1); 104 | } 105 | } -------------------------------------------------------------------------------- /src/MediatR.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MediatR.Pipeline; 6 | using MediatR.Registration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace MediatR; 10 | 11 | /// 12 | /// Extensions to scan for MediatR handlers and registers them. 13 | /// - Scans for any handler interface implementations and registers them as 14 | /// - Scans for any and implementations and registers them as transient instances 15 | /// Registers and as transient instances 16 | /// After calling AddMediatR you can use the container to resolve an instance. 17 | /// This does not scan for any instances including and . 18 | /// To register behaviors, use the with the open generic or closed generic types. 19 | /// 20 | public static class ServiceCollectionExtensions 21 | { 22 | /// 23 | /// Registers handlers and mediator types from the specified assemblies 24 | /// 25 | /// Service collection 26 | /// Assemblies to scan 27 | /// Service collection 28 | public static IServiceCollection AddMediatR(this IServiceCollection services, params Assembly[] assemblies) 29 | => services.AddMediatR(assemblies, configuration: null); 30 | 31 | /// 32 | /// Registers handlers and mediator types from the specified assemblies 33 | /// 34 | /// Service collection 35 | /// Assemblies to scan 36 | /// The action used to configure the options 37 | /// Service collection 38 | public static IServiceCollection AddMediatR(this IServiceCollection services, Action? configuration, params Assembly[] assemblies) 39 | => services.AddMediatR(assemblies, configuration); 40 | 41 | /// 42 | /// Registers handlers and mediator types from the specified assemblies 43 | /// 44 | /// Service collection 45 | /// Assemblies to scan 46 | /// The action used to configure the options 47 | /// Service collection 48 | public static IServiceCollection AddMediatR(this IServiceCollection services, IEnumerable assemblies, Action? configuration) 49 | { 50 | if (!assemblies.Any()) 51 | { 52 | throw new ArgumentException("No assemblies found to scan. Supply at least one assembly to scan for handlers."); 53 | } 54 | var serviceConfig = new MediatRServiceConfiguration(); 55 | 56 | configuration?.Invoke(serviceConfig); 57 | 58 | ServiceRegistrar.AddRequiredServices(services, serviceConfig); 59 | 60 | ServiceRegistrar.AddMediatRClasses(services, assemblies, serviceConfig); 61 | 62 | return services; 63 | } 64 | 65 | /// 66 | /// Registers handlers and mediator types from the assemblies that contain the specified types 67 | /// 68 | /// 69 | /// 70 | /// Service collection 71 | public static IServiceCollection AddMediatR(this IServiceCollection services, params Type[] handlerAssemblyMarkerTypes) 72 | => services.AddMediatR(handlerAssemblyMarkerTypes, configuration: null); 73 | 74 | /// 75 | /// Registers handlers and mediator types from the assemblies that contain the specified types 76 | /// 77 | /// 78 | /// 79 | /// The action used to configure the options 80 | /// Service collection 81 | public static IServiceCollection AddMediatR(this IServiceCollection services, Action? configuration, params Type[] handlerAssemblyMarkerTypes) 82 | => services.AddMediatR(handlerAssemblyMarkerTypes, configuration); 83 | 84 | /// 85 | /// Registers handlers and mediator types from the assemblies that contain the specified types 86 | /// 87 | /// 88 | /// 89 | /// The action used to configure the options 90 | /// Service collection 91 | public static IServiceCollection AddMediatR(this IServiceCollection services, IEnumerable handlerAssemblyMarkerTypes, Action? configuration) 92 | => services.AddMediatR(handlerAssemblyMarkerTypes.Select(t => t.GetTypeInfo().Assembly), configuration); 93 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /src/TestApp/Runner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using MediatR; 5 | 6 | namespace TestApp; 7 | 8 | using System.IO; 9 | using System.Threading.Tasks; 10 | 11 | public static class Runner 12 | { 13 | public static async Task Run(IMediator mediator, WrappingWriter writer, string projectName) 14 | { 15 | await writer.WriteLineAsync("==============="); 16 | await writer.WriteLineAsync(projectName); 17 | await writer.WriteLineAsync("==============="); 18 | 19 | await writer.WriteLineAsync("Sending Ping..."); 20 | var pong = await mediator.Send(new Ping { Message = "Ping" }); 21 | await writer.WriteLineAsync("Received: " + pong.Message); 22 | 23 | await writer.WriteLineAsync("Publishing Pinged..."); 24 | await mediator.Publish(new Pinged()); 25 | 26 | await writer.WriteLineAsync("Publishing Ponged..."); 27 | var failedPong = false; 28 | try 29 | { 30 | await mediator.Publish(new Ponged()); 31 | } 32 | catch (Exception e) 33 | { 34 | failedPong = true; 35 | await writer.WriteLineAsync(e.ToString()); 36 | } 37 | 38 | bool failedJing = false; 39 | await writer.WriteLineAsync("Sending Jing..."); 40 | try 41 | { 42 | await mediator.Send(new Jing { Message = "Jing" }); 43 | } 44 | catch (Exception e) 45 | { 46 | failedJing = true; 47 | await writer.WriteLineAsync(e.ToString()); 48 | } 49 | 50 | await writer.WriteLineAsync("---------------"); 51 | var contents = writer.Contents; 52 | var order = new[] { 53 | contents.IndexOf("- Starting Up", StringComparison.OrdinalIgnoreCase), 54 | contents.IndexOf("-- Handling Request", StringComparison.OrdinalIgnoreCase), 55 | contents.IndexOf("--- Handled Ping", StringComparison.OrdinalIgnoreCase), 56 | contents.IndexOf("-- Finished Request", StringComparison.OrdinalIgnoreCase), 57 | contents.IndexOf("- All Done", StringComparison.OrdinalIgnoreCase), 58 | contents.IndexOf("- All Done with Ping", StringComparison.OrdinalIgnoreCase), 59 | }; 60 | 61 | var results = new RunResults 62 | { 63 | RequestHandlers = contents.Contains("--- Handled Ping:"), 64 | VoidRequestsHandlers = contents.Contains("--- Handled Jing:"), 65 | PipelineBehaviors = contents.Contains("-- Handling Request"), 66 | RequestPreProcessors = contents.Contains("- Starting Up"), 67 | RequestPostProcessors = contents.Contains("- All Done"), 68 | ConstrainedGenericBehaviors = contents.Contains("- All Done with Ping") && !failedJing, 69 | OrderedPipelineBehaviors = order.SequenceEqual(order.OrderBy(i => i)), 70 | NotificationHandler = contents.Contains("Got pinged async"), 71 | MultipleNotificationHandlers = contents.Contains("Got pinged async") && contents.Contains("Got pinged also async"), 72 | ConstrainedGenericNotificationHandler = contents.Contains("Got pinged constrained async") && !failedPong, 73 | CovariantNotificationHandler = contents.Contains("Got notified") 74 | }; 75 | 76 | await writer.WriteLineAsync($"Request Handler...................{(results.RequestHandlers ? "Y" : "N")}"); 77 | await writer.WriteLineAsync($"Void Request Handler..............{(results.VoidRequestsHandlers ? "Y" : "N")}"); 78 | await writer.WriteLineAsync($"Pipeline Behavior.................{(results.PipelineBehaviors ? "Y" : "N")}"); 79 | await writer.WriteLineAsync($"Pre-Processor.....................{(results.RequestPreProcessors ? "Y" : "N")}"); 80 | await writer.WriteLineAsync($"Post-Processor....................{(results.RequestPostProcessors ? "Y" : "N")}"); 81 | await writer.WriteLineAsync($"Constrained Post-Processor........{(results.ConstrainedGenericBehaviors ? "Y" : "N")}"); 82 | await writer.WriteLineAsync($"Ordered Behaviors.................{(results.OrderedPipelineBehaviors ? "Y" : "N")}"); 83 | await writer.WriteLineAsync($"Notification Handler..............{(results.NotificationHandler ? "Y" : "N")}"); 84 | await writer.WriteLineAsync($"Notification Handlers.............{(results.MultipleNotificationHandlers ? "Y" : "N")}"); 85 | await writer.WriteLineAsync($"Constrained Notification Handler..{(results.ConstrainedGenericNotificationHandler ? "Y" : "N")}"); 86 | await writer.WriteLineAsync($"Covariant Notification Handler....{(results.CovariantNotificationHandler ? "Y" : "N")}"); 87 | } 88 | } 89 | 90 | public class RunResults 91 | { 92 | public bool RequestHandlers { get; init; } 93 | public bool VoidRequestsHandlers { get; init; } 94 | public bool PipelineBehaviors { get; init; } 95 | public bool RequestPreProcessors { get; init; } 96 | public bool RequestPostProcessors { get; init; } 97 | public bool OrderedPipelineBehaviors { get; init; } 98 | public bool ConstrainedGenericBehaviors { get; init; } 99 | public bool NotificationHandler { get; init; } 100 | public bool MultipleNotificationHandlers { get; init; } 101 | public bool CovariantNotificationHandler { get; init; } 102 | public bool ConstrainedGenericNotificationHandler { get; init; } 103 | } 104 | 105 | public class WrappingWriter : TextWriter 106 | { 107 | private readonly TextWriter _innerWriter; 108 | private readonly StringBuilder _stringWriter = new(); 109 | 110 | public WrappingWriter(TextWriter innerWriter) 111 | { 112 | _innerWriter = innerWriter; 113 | } 114 | 115 | public override void Write(char value) 116 | { 117 | _stringWriter.Append(value); 118 | _innerWriter.Write(value); 119 | } 120 | 121 | public override Task WriteLineAsync(string? value) 122 | { 123 | _stringWriter.AppendLine(value); 124 | return _innerWriter.WriteLineAsync(value); 125 | } 126 | 127 | public override Encoding Encoding => _innerWriter.Encoding; 128 | 129 | public string Contents => _stringWriter.ToString(); 130 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/Handlers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests 5 | { 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | public class Ping : IRequest 11 | { 12 | public string? Message { get; init; } 13 | public Action? ThrowAction { get; init; } 14 | } 15 | 16 | public class DerivedPing : Ping 17 | { 18 | } 19 | 20 | public class Pong 21 | { 22 | public string? Message { get; init; } 23 | } 24 | 25 | public class Zing : IRequest 26 | { 27 | public string? Message { get; init; } 28 | } 29 | 30 | public class Zong 31 | { 32 | public string? Message { get; init; } 33 | } 34 | 35 | public class Ding : IRequest 36 | { 37 | public string? Message { get; init; } 38 | } 39 | 40 | public class Pinged : INotification 41 | { 42 | 43 | } 44 | 45 | class InternalPing : IRequest { } 46 | 47 | public class StreamPing : IStreamRequest 48 | { 49 | public string? Message { get; init; } 50 | } 51 | 52 | public class GenericHandler : INotificationHandler 53 | { 54 | public Task Handle(INotification notification, CancellationToken cancellationToken) 55 | { 56 | return Task.FromResult(0); 57 | } 58 | } 59 | 60 | public class DingAsyncHandler : IRequestHandler 61 | { 62 | public Task Handle(Ding message, CancellationToken cancellationToken) => Unit.Task; 63 | } 64 | 65 | public class PingedHandler : INotificationHandler 66 | { 67 | public Task Handle(Pinged notification, CancellationToken cancellationToken) 68 | { 69 | return Task.CompletedTask; 70 | } 71 | } 72 | 73 | public class PingedAlsoHandler : INotificationHandler 74 | { 75 | public Task Handle(Pinged notification, CancellationToken cancellationToken) 76 | { 77 | return Task.CompletedTask; 78 | } 79 | } 80 | 81 | public class Logger 82 | { 83 | public IList Messages { get; } = new List(); 84 | } 85 | 86 | public class PingHandler : IRequestHandler 87 | { 88 | private readonly Logger _logger; 89 | 90 | public PingHandler(Logger logger) 91 | { 92 | _logger = logger; 93 | } 94 | public Task Handle(Ping message, CancellationToken cancellationToken) 95 | { 96 | _logger.Messages.Add("Handler"); 97 | 98 | message.ThrowAction?.Invoke(message); 99 | 100 | return Task.FromResult(new Pong { Message = message.Message + " Pong" }); 101 | } 102 | } 103 | 104 | public class DerivedPingHandler : IRequestHandler 105 | { 106 | private readonly Logger _logger; 107 | 108 | public DerivedPingHandler(Logger logger) 109 | { 110 | _logger = logger; 111 | } 112 | public Task Handle(DerivedPing message, CancellationToken cancellationToken) 113 | { 114 | _logger.Messages.Add("Handler"); 115 | return Task.FromResult(new Pong { Message = $"Derived{message.Message} Pong" }); 116 | } 117 | } 118 | 119 | public class ZingHandler : IRequestHandler 120 | { 121 | private readonly Logger _output; 122 | 123 | public ZingHandler(Logger output) 124 | { 125 | _output = output; 126 | } 127 | public Task Handle(Zing message, CancellationToken cancellationToken) 128 | { 129 | _output.Messages.Add("Handler"); 130 | return Task.FromResult(new Zong { Message = message.Message + " Zong" }); 131 | } 132 | } 133 | 134 | public class PingStreamHandler : IStreamRequestHandler 135 | { 136 | private readonly Logger _output; 137 | 138 | public PingStreamHandler(Logger output) 139 | { 140 | _output = output; 141 | } 142 | public async IAsyncEnumerable Handle(StreamPing request, [EnumeratorCancellation] CancellationToken cancellationToken) 143 | { 144 | _output.Messages.Add("Handler"); 145 | yield return await Task.Run(() => new Pong { Message = request.Message + " Pang" }, cancellationToken); 146 | } 147 | } 148 | 149 | 150 | public class DuplicateTest : IRequest { } 151 | public class DuplicateHandler1 : IRequestHandler 152 | { 153 | public Task Handle(DuplicateTest message, CancellationToken cancellationToken) 154 | { 155 | return Task.FromResult(nameof(DuplicateHandler1)); 156 | } 157 | } 158 | 159 | public class DuplicateHandler2 : IRequestHandler 160 | { 161 | public Task Handle(DuplicateTest message, CancellationToken cancellationToken) 162 | { 163 | return Task.FromResult(nameof(DuplicateHandler2)); 164 | } 165 | } 166 | 167 | class InternalPingHandler : IRequestHandler 168 | { 169 | public Task Handle(InternalPing request, CancellationToken cancellationToken) => Unit.Task; 170 | } 171 | 172 | class MyCustomMediator : IMediator 173 | { 174 | public Task Send(object request, CancellationToken cancellationToken = new()) 175 | { 176 | throw new System.NotImplementedException(); 177 | } 178 | 179 | public IAsyncEnumerable CreateStream(IStreamRequest request, 180 | CancellationToken cancellationToken = new()) 181 | { 182 | throw new NotImplementedException(); 183 | } 184 | 185 | public IAsyncEnumerable CreateStream(object request, CancellationToken cancellationToken = new()) 186 | { 187 | throw new NotImplementedException(); 188 | } 189 | 190 | public Task Publish(object notification, CancellationToken cancellationToken = new()) 191 | { 192 | throw new System.NotImplementedException(); 193 | } 194 | 195 | public Task Publish(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification 196 | { 197 | throw new System.NotImplementedException(); 198 | } 199 | 200 | public Task Send(IRequest request, CancellationToken cancellationToken = default) 201 | { 202 | throw new System.NotImplementedException(); 203 | } 204 | } 205 | } 206 | 207 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included 208 | { 209 | using System.Threading; 210 | using System.Threading.Tasks; 211 | 212 | public class Foo : IRequest 213 | { 214 | public string? Message { get; init; } 215 | public Action? ThrowAction { get; init; } 216 | } 217 | 218 | public class Bar 219 | { 220 | public string? Message { get; init; } 221 | } 222 | 223 | public class FooHandler : IRequestHandler 224 | { 225 | private readonly Logger _logger; 226 | 227 | public FooHandler(Logger logger) 228 | { 229 | _logger = logger; 230 | } 231 | public Task Handle(Foo message, CancellationToken cancellationToken) 232 | { 233 | _logger.Messages.Add("Handler"); 234 | 235 | message.ThrowAction?.Invoke(message); 236 | 237 | return Task.FromResult(new Bar { Message = message.Message + " Bar" }); 238 | } 239 | } 240 | } -------------------------------------------------------------------------------- /src/MediatR.Extensions.Microsoft.DependencyInjection/Registration/ServiceRegistrar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MediatR.Pipeline; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | 9 | namespace MediatR.Registration; 10 | 11 | public static class ServiceRegistrar 12 | { 13 | public static void AddMediatRClasses(IServiceCollection services, IEnumerable assembliesToScan, MediatRServiceConfiguration configuration) 14 | { 15 | assembliesToScan = assembliesToScan.Distinct().ToArray(); 16 | 17 | ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>), services, assembliesToScan, false, configuration); 18 | ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>), services, assembliesToScan, true, configuration); 19 | ConnectImplementationsToTypesClosing(typeof(IStreamRequestHandler<,>), services, assembliesToScan, false, configuration); 20 | ConnectImplementationsToTypesClosing(typeof(IRequestPreProcessor<>), services, assembliesToScan, true, configuration); 21 | ConnectImplementationsToTypesClosing(typeof(IRequestPostProcessor<,>), services, assembliesToScan, true, configuration); 22 | ConnectImplementationsToTypesClosing(typeof(IRequestExceptionHandler<,,>), services, assembliesToScan, true, configuration); 23 | ConnectImplementationsToTypesClosing(typeof(IRequestExceptionAction<,>), services, assembliesToScan, true, configuration); 24 | 25 | var multiOpenInterfaces = new[] 26 | { 27 | typeof(INotificationHandler<>), 28 | typeof(IRequestPreProcessor<>), 29 | typeof(IRequestPostProcessor<,>), 30 | typeof(IRequestExceptionHandler<,,>), 31 | typeof(IRequestExceptionAction<,>) 32 | }; 33 | 34 | foreach (var multiOpenInterface in multiOpenInterfaces) 35 | { 36 | var arity = multiOpenInterface.GetGenericArguments().Length; 37 | 38 | var concretions = assembliesToScan 39 | .SelectMany(a => a.DefinedTypes) 40 | .Where(type => type.FindInterfacesThatClose(multiOpenInterface).Any()) 41 | .Where(type => type.IsConcrete() && type.IsOpenGeneric()) 42 | .Where(type => type.GetGenericArguments().Length == arity) 43 | .Where(configuration.TypeEvaluator) 44 | .ToList(); 45 | 46 | foreach (var type in concretions) 47 | { 48 | services.AddTransient(multiOpenInterface, type); 49 | } 50 | } 51 | } 52 | 53 | private static void ConnectImplementationsToTypesClosing(Type openRequestInterface, 54 | IServiceCollection services, 55 | IEnumerable assembliesToScan, 56 | bool addIfAlreadyExists, 57 | MediatRServiceConfiguration configuration) 58 | { 59 | var concretions = new List(); 60 | var interfaces = new List(); 61 | foreach (var type in assembliesToScan.SelectMany(a => a.DefinedTypes).Where(t => !t.IsOpenGeneric()).Where(configuration.TypeEvaluator)) 62 | { 63 | var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray(); 64 | if (!interfaceTypes.Any()) continue; 65 | 66 | if (type.IsConcrete()) 67 | { 68 | concretions.Add(type); 69 | } 70 | 71 | foreach (var interfaceType in interfaceTypes) 72 | { 73 | interfaces.Fill(interfaceType); 74 | } 75 | } 76 | 77 | foreach (var @interface in interfaces) 78 | { 79 | var exactMatches = concretions.Where(x => x.CanBeCastTo(@interface)).ToList(); 80 | if (addIfAlreadyExists) 81 | { 82 | foreach (var type in exactMatches) 83 | { 84 | services.AddTransient(@interface, type); 85 | } 86 | } 87 | else 88 | { 89 | if (exactMatches.Count > 1) 90 | { 91 | exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface)); 92 | } 93 | 94 | foreach (var type in exactMatches) 95 | { 96 | services.TryAddTransient(@interface, type); 97 | } 98 | } 99 | 100 | if (!@interface.IsOpenGeneric()) 101 | { 102 | AddConcretionsThatCouldBeClosed(@interface, concretions, services); 103 | } 104 | } 105 | } 106 | 107 | private static bool IsMatchingWithInterface(Type handlerType, Type handlerInterface) 108 | { 109 | if (handlerType == null || handlerInterface == null) 110 | { 111 | return false; 112 | } 113 | 114 | if (handlerType.IsInterface) 115 | { 116 | if (handlerType.GenericTypeArguments.SequenceEqual(handlerInterface.GenericTypeArguments)) 117 | { 118 | return true; 119 | } 120 | } 121 | else 122 | { 123 | return IsMatchingWithInterface(handlerType.GetInterface(handlerInterface.Name), handlerInterface); 124 | } 125 | 126 | return false; 127 | } 128 | 129 | private static void AddConcretionsThatCouldBeClosed(Type @interface, List concretions, IServiceCollection services) 130 | { 131 | foreach (var type in concretions 132 | .Where(x => x.IsOpenGeneric() && x.CouldCloseTo(@interface))) 133 | { 134 | try 135 | { 136 | services.TryAddTransient(@interface, type.MakeGenericType(@interface.GenericTypeArguments)); 137 | } 138 | catch (Exception) 139 | { 140 | } 141 | } 142 | } 143 | 144 | private static bool CouldCloseTo(this Type openConcretion, Type closedInterface) 145 | { 146 | var openInterface = closedInterface.GetGenericTypeDefinition(); 147 | var arguments = closedInterface.GenericTypeArguments; 148 | 149 | var concreteArguments = openConcretion.GenericTypeArguments; 150 | return arguments.Length == concreteArguments.Length && openConcretion.CanBeCastTo(openInterface); 151 | } 152 | 153 | private static bool CanBeCastTo(this Type pluggedType, Type pluginType) 154 | { 155 | if (pluggedType == null) return false; 156 | 157 | if (pluggedType == pluginType) return true; 158 | 159 | return pluginType.GetTypeInfo().IsAssignableFrom(pluggedType.GetTypeInfo()); 160 | } 161 | 162 | private static bool IsOpenGeneric(this Type type) 163 | { 164 | return type.GetTypeInfo().IsGenericTypeDefinition || type.GetTypeInfo().ContainsGenericParameters; 165 | } 166 | 167 | private static IEnumerable FindInterfacesThatClose(this Type pluggedType, Type templateType) 168 | { 169 | return FindInterfacesThatClosesCore(pluggedType, templateType).Distinct(); 170 | } 171 | 172 | private static IEnumerable FindInterfacesThatClosesCore(Type pluggedType, Type templateType) 173 | { 174 | if (pluggedType == null) yield break; 175 | 176 | if (!pluggedType.IsConcrete()) yield break; 177 | 178 | if (templateType.GetTypeInfo().IsInterface) 179 | { 180 | foreach ( 181 | var interfaceType in 182 | pluggedType.GetInterfaces() 183 | .Where(type => type.GetTypeInfo().IsGenericType && (type.GetGenericTypeDefinition() == templateType))) 184 | { 185 | yield return interfaceType; 186 | } 187 | } 188 | else if (pluggedType.GetTypeInfo().BaseType.GetTypeInfo().IsGenericType && 189 | (pluggedType.GetTypeInfo().BaseType.GetGenericTypeDefinition() == templateType)) 190 | { 191 | yield return pluggedType.GetTypeInfo().BaseType; 192 | } 193 | 194 | if (pluggedType.GetTypeInfo().BaseType == typeof(object)) yield break; 195 | 196 | foreach (var interfaceType in FindInterfacesThatClosesCore(pluggedType.GetTypeInfo().BaseType, templateType)) 197 | { 198 | yield return interfaceType; 199 | } 200 | } 201 | 202 | private static bool IsConcrete(this Type type) 203 | { 204 | return !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface; 205 | } 206 | 207 | private static void Fill(this IList list, T value) 208 | { 209 | if (list.Contains(value)) return; 210 | list.Add(value); 211 | } 212 | 213 | public static void AddRequiredServices(IServiceCollection services, MediatRServiceConfiguration serviceConfiguration) 214 | { 215 | // Use TryAdd, so any existing ServiceFactory/IMediator registration doesn't get overriden 216 | services.TryAddTransient(p => p.GetRequiredService); 217 | services.TryAdd(new ServiceDescriptor(typeof(IMediator), serviceConfiguration.MediatorImplementationType, serviceConfiguration.Lifetime)); 218 | services.TryAdd(new ServiceDescriptor(typeof(ISender), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); 219 | services.TryAdd(new ServiceDescriptor(typeof(IPublisher), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); 220 | 221 | // Use TryAddTransientExact (see below), we dó want to register our Pre/Post processor behavior, even if (a more concrete) 222 | // registration for IPipelineBehavior<,> already exists. But only once. 223 | services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); 224 | services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>)); 225 | 226 | if (serviceConfiguration.RequestExceptionActionProcessorStrategy == RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions) 227 | { 228 | services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); 229 | services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); 230 | } 231 | else 232 | { 233 | services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); 234 | services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); 235 | } 236 | } 237 | 238 | /// 239 | /// Adds a new transient registration to the service collection only when no existing registration of the same service type and implementation type exists. 240 | /// In contrast to TryAddTransient, which only checks the service type. 241 | /// 242 | /// The service collection 243 | /// Service type 244 | /// Implementation type 245 | private static void TryAddTransientExact(this IServiceCollection services, Type serviceType, Type implementationType) 246 | { 247 | if (services.Any(reg => reg.ServiceType == serviceType && reg.ImplementationType == implementationType)) 248 | { 249 | return; 250 | } 251 | 252 | services.AddTransient(serviceType, implementationType); 253 | } 254 | } -------------------------------------------------------------------------------- /test/MediatR.Extensions.Microsoft.DependencyInjection.Tests/PipelineTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Pipeline; 12 | using Shouldly; 13 | using Xunit; 14 | 15 | public class PipelineTests 16 | { 17 | public class OuterBehavior : IPipelineBehavior 18 | { 19 | private readonly Logger _output; 20 | 21 | public OuterBehavior(Logger output) 22 | { 23 | _output = output; 24 | } 25 | 26 | public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) 27 | { 28 | _output.Messages.Add("Outer before"); 29 | var response = await next(); 30 | _output.Messages.Add("Outer after"); 31 | 32 | return response; 33 | } 34 | } 35 | 36 | public class InnerBehavior : IPipelineBehavior 37 | { 38 | private readonly Logger _output; 39 | 40 | public InnerBehavior(Logger output) 41 | { 42 | _output = output; 43 | } 44 | 45 | public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) 46 | { 47 | _output.Messages.Add("Inner before"); 48 | var response = await next(); 49 | _output.Messages.Add("Inner after"); 50 | 51 | return response; 52 | } 53 | } 54 | 55 | public class InnerBehavior : IPipelineBehavior 56 | where TRequest : IRequest 57 | { 58 | private readonly Logger _output; 59 | 60 | public InnerBehavior(Logger output) 61 | { 62 | _output = output; 63 | } 64 | 65 | public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) 66 | { 67 | _output.Messages.Add("Inner generic before"); 68 | var response = await next(); 69 | _output.Messages.Add("Inner generic after"); 70 | 71 | return response; 72 | } 73 | } 74 | 75 | public class OuterBehavior : IPipelineBehavior 76 | where TRequest : IRequest 77 | { 78 | private readonly Logger _output; 79 | 80 | public OuterBehavior(Logger output) 81 | { 82 | _output = output; 83 | } 84 | 85 | public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) 86 | { 87 | _output.Messages.Add("Outer generic before"); 88 | var response = await next(); 89 | _output.Messages.Add("Outer generic after"); 90 | 91 | return response; 92 | } 93 | } 94 | 95 | public class ConstrainedBehavior : IPipelineBehavior 96 | where TRequest : Ping, IRequest 97 | where TResponse : Pong 98 | { 99 | private readonly Logger _output; 100 | 101 | public ConstrainedBehavior(Logger output) 102 | { 103 | _output = output; 104 | } 105 | 106 | public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) 107 | { 108 | _output.Messages.Add("Constrained before"); 109 | var response = await next(); 110 | _output.Messages.Add("Constrained after"); 111 | 112 | return response; 113 | } 114 | } 115 | 116 | public class FirstPreProcessor : IRequestPreProcessor where TRequest : notnull 117 | { 118 | private readonly Logger _output; 119 | 120 | public FirstPreProcessor(Logger output) 121 | { 122 | _output = output; 123 | } 124 | public Task Process(TRequest request, CancellationToken cancellationToken) 125 | { 126 | _output.Messages.Add("First pre processor"); 127 | return Task.FromResult(0); 128 | } 129 | } 130 | 131 | public class FirstConcretePreProcessor : IRequestPreProcessor 132 | { 133 | private readonly Logger _output; 134 | 135 | public FirstConcretePreProcessor(Logger output) 136 | { 137 | _output = output; 138 | } 139 | public Task Process(Ping request, CancellationToken cancellationToken) 140 | { 141 | _output.Messages.Add("First concrete pre processor"); 142 | return Task.FromResult(0); 143 | } 144 | } 145 | 146 | public class NextPreProcessor : IRequestPreProcessor where TRequest : notnull 147 | { 148 | private readonly Logger _output; 149 | 150 | public NextPreProcessor(Logger output) 151 | { 152 | _output = output; 153 | } 154 | public Task Process(TRequest request, CancellationToken cancellationToken) 155 | { 156 | _output.Messages.Add("Next pre processor"); 157 | return Task.FromResult(0); 158 | } 159 | } 160 | 161 | public class NextConcretePreProcessor : IRequestPreProcessor 162 | { 163 | private readonly Logger _output; 164 | 165 | public NextConcretePreProcessor(Logger output) 166 | { 167 | _output = output; 168 | } 169 | public Task Process(Ping request, CancellationToken cancellationToken) 170 | { 171 | _output.Messages.Add("Next concrete pre processor"); 172 | return Task.FromResult(0); 173 | } 174 | } 175 | 176 | public class FirstPostProcessor : IRequestPostProcessor 177 | where TRequest : IRequest 178 | { 179 | private readonly Logger _output; 180 | 181 | public FirstPostProcessor(Logger output) 182 | { 183 | _output = output; 184 | } 185 | public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) 186 | { 187 | _output.Messages.Add("First post processor"); 188 | return Task.FromResult(0); 189 | } 190 | } 191 | 192 | public class FirstConcretePostProcessor : IRequestPostProcessor 193 | { 194 | private readonly Logger _output; 195 | 196 | public FirstConcretePostProcessor(Logger output) 197 | { 198 | _output = output; 199 | } 200 | public Task Process(Ping request, Pong response, CancellationToken cancellationToken) 201 | { 202 | _output.Messages.Add("First concrete post processor"); 203 | return Task.FromResult(0); 204 | } 205 | } 206 | 207 | public class NextPostProcessor : IRequestPostProcessor 208 | where TRequest : IRequest 209 | { 210 | private readonly Logger _output; 211 | 212 | public NextPostProcessor(Logger output) 213 | { 214 | _output = output; 215 | } 216 | public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) 217 | { 218 | _output.Messages.Add("Next post processor"); 219 | return Task.FromResult(0); 220 | } 221 | } 222 | 223 | public class NextConcretePostProcessor : IRequestPostProcessor 224 | { 225 | private readonly Logger _output; 226 | 227 | public NextConcretePostProcessor(Logger output) 228 | { 229 | _output = output; 230 | } 231 | public Task Process(Ping request, Pong response, CancellationToken cancellationToken) 232 | { 233 | _output.Messages.Add("Next concrete post processor"); 234 | return Task.FromResult(0); 235 | } 236 | } 237 | 238 | public class PingPongGenericExceptionAction : IRequestExceptionAction 239 | { 240 | private readonly Logger _output; 241 | 242 | public PingPongGenericExceptionAction(Logger output) => _output = output; 243 | 244 | public Task Execute(Ping request, Exception exception, CancellationToken cancellationToken) 245 | { 246 | _output.Messages.Add("Logging generic exception"); 247 | 248 | return Task.CompletedTask; 249 | } 250 | } 251 | 252 | public class PingPongApplicationExceptionAction : IRequestExceptionAction 253 | { 254 | private readonly Logger _output; 255 | 256 | public PingPongApplicationExceptionAction(Logger output) => _output = output; 257 | 258 | public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) 259 | { 260 | _output.Messages.Add("Logging ApplicationException exception"); 261 | 262 | return Task.CompletedTask; 263 | } 264 | } 265 | 266 | public class PingPongExceptionActionForType1 : IRequestExceptionAction 267 | { 268 | private readonly Logger _output; 269 | 270 | public PingPongExceptionActionForType1(Logger output) => _output = output; 271 | 272 | public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) 273 | { 274 | _output.Messages.Add("Logging exception 1"); 275 | 276 | return Task.CompletedTask; 277 | } 278 | } 279 | 280 | public class PingPongExceptionActionForType2 : IRequestExceptionAction 281 | { 282 | private readonly Logger _output; 283 | 284 | public PingPongExceptionActionForType2(Logger output) => _output = output; 285 | 286 | public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) 287 | { 288 | _output.Messages.Add("Logging exception 2"); 289 | 290 | return Task.CompletedTask; 291 | } 292 | } 293 | 294 | public class PingPongExceptionHandlerForType : IRequestExceptionHandler 295 | { 296 | public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) 297 | { 298 | state.SetHandled(new Pong { Message = exception.Message + " Handled by Specific Type" }); 299 | 300 | return Task.CompletedTask; 301 | } 302 | } 303 | 304 | public class PingPongGenericExceptionHandler : IRequestExceptionHandler 305 | { 306 | private readonly Logger _output; 307 | 308 | public PingPongGenericExceptionHandler(Logger output) => _output = output; 309 | 310 | public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) 311 | { 312 | _output.Messages.Add(exception.Message + " Logged by Generic Type"); 313 | 314 | return Task.CompletedTask; 315 | } 316 | } 317 | 318 | [Fact] 319 | public async Task Should_wrap_with_behavior() 320 | { 321 | var output = new Logger(); 322 | IServiceCollection services = new ServiceCollection(); 323 | services.AddSingleton(output); 324 | services.AddTransient, OuterBehavior>(); 325 | services.AddTransient, InnerBehavior>(); 326 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 327 | var provider = services.BuildServiceProvider(); 328 | 329 | var mediator = provider.GetRequiredService(); 330 | 331 | var response = await mediator.Send(new Ping { Message = "Ping" }); 332 | 333 | response.Message.ShouldBe("Ping Pong"); 334 | 335 | output.Messages.ShouldBe(new[] 336 | { 337 | "Outer before", 338 | "Inner before", 339 | "First concrete pre processor", 340 | "Next concrete pre processor", 341 | "First pre processor", 342 | "Next pre processor", 343 | "Handler", 344 | "First concrete post processor", 345 | "Next concrete post processor", 346 | "First post processor", 347 | "Next post processor", 348 | "Inner after", 349 | "Outer after" 350 | }); 351 | } 352 | 353 | 354 | [Fact] 355 | public async Task Should_wrap_generics_with_behavior() 356 | { 357 | var output = new Logger(); 358 | IServiceCollection services = new ServiceCollection(); 359 | services.AddSingleton(output); 360 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); 361 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); 362 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 363 | var provider = services.BuildServiceProvider(); 364 | 365 | var mediator = provider.GetRequiredService(); 366 | 367 | var response = await mediator.Send(new Ping { Message = "Ping" }); 368 | 369 | response.Message.ShouldBe("Ping Pong"); 370 | 371 | output.Messages.ShouldBe(new[] 372 | { 373 | "Outer generic before", 374 | "Inner generic before", 375 | "First concrete pre processor", 376 | "Next concrete pre processor", 377 | "First pre processor", 378 | "Next pre processor", 379 | "Handler", 380 | "First concrete post processor", 381 | "Next concrete post processor", 382 | "First post processor", 383 | "Next post processor", 384 | "Inner generic after", 385 | "Outer generic after", 386 | }); 387 | } 388 | 389 | [Fact] 390 | public async Task Should_pick_up_pre_and_post_processors() 391 | { 392 | var output = new Logger(); 393 | IServiceCollection services = new ServiceCollection(); 394 | services.AddSingleton(output); 395 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 396 | var provider = services.BuildServiceProvider(); 397 | 398 | var mediator = provider.GetRequiredService(); 399 | 400 | var response = await mediator.Send(new Ping { Message = "Ping" }); 401 | 402 | response.Message.ShouldBe("Ping Pong"); 403 | 404 | output.Messages.ShouldBe(new[] 405 | { 406 | "First concrete pre processor", 407 | "Next concrete pre processor", 408 | "First pre processor", 409 | "Next pre processor", 410 | "Handler", 411 | "First concrete post processor", 412 | "Next concrete post processor", 413 | "First post processor", 414 | "Next post processor", 415 | }); 416 | } 417 | 418 | [Fact] 419 | public async Task Should_pick_up_specific_exception_behaviors() 420 | { 421 | var output = new Logger(); 422 | IServiceCollection services = new ServiceCollection(); 423 | services.AddSingleton(output); 424 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 425 | var provider = services.BuildServiceProvider(); 426 | 427 | var mediator = provider.GetRequiredService(); 428 | 429 | var response = await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new ApplicationException(msg.Message + " Thrown")}); 430 | 431 | response.Message.ShouldBe("Ping Thrown Handled by Specific Type"); 432 | output.Messages.ShouldNotContain("Logging ApplicationException exception"); 433 | } 434 | 435 | [Fact] 436 | public void Should_pick_up_base_exception_behaviors() 437 | { 438 | var output = new Logger(); 439 | IServiceCollection services = new ServiceCollection(); 440 | services.AddSingleton(output); 441 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 442 | var provider = services.BuildServiceProvider(); 443 | 444 | var mediator = provider.GetRequiredService(); 445 | 446 | Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new Exception(msg.Message + " Thrown")})); 447 | 448 | output.Messages.ShouldContain("Ping Thrown Logged by Generic Type"); 449 | output.Messages.ShouldContain("Logging generic exception"); 450 | } 451 | 452 | [Fact] 453 | public void Should_pick_up_exception_actions() 454 | { 455 | var output = new Logger(); 456 | IServiceCollection services = new ServiceCollection(); 457 | services.AddSingleton(output); 458 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 459 | var provider = services.BuildServiceProvider(); 460 | 461 | var mediator = provider.GetRequiredService(); 462 | 463 | Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new SystemException(msg.Message + " Thrown")})); 464 | 465 | output.Messages.ShouldContain("Logging exception 1"); 466 | output.Messages.ShouldContain("Logging exception 2"); 467 | } 468 | 469 | [Fact] 470 | public async Task Should_handle_constrained_generics() 471 | { 472 | var output = new Logger(); 473 | IServiceCollection services = new ServiceCollection(); 474 | services.AddSingleton(output); 475 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); 476 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); 477 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstrainedBehavior<,>)); 478 | services.AddMediatR(typeof(Ping).GetTypeInfo().Assembly); 479 | var provider = services.BuildServiceProvider(); 480 | 481 | var mediator = provider.GetRequiredService(); 482 | 483 | var response = await mediator.Send(new Ping { Message = "Ping" }); 484 | 485 | response.Message.ShouldBe("Ping Pong"); 486 | 487 | output.Messages.ShouldBe(new[] 488 | { 489 | "Outer generic before", 490 | "Inner generic before", 491 | "Constrained before", 492 | "First concrete pre processor", 493 | "Next concrete pre processor", 494 | "First pre processor", 495 | "Next pre processor", 496 | "Handler", 497 | "First concrete post processor", 498 | "Next concrete post processor", 499 | "First post processor", 500 | "Next post processor", 501 | "Constrained after", 502 | "Inner generic after", 503 | "Outer generic after" 504 | }); 505 | 506 | output.Messages.Clear(); 507 | 508 | var zingResponse = await mediator.Send(new Zing { Message = "Zing" }); 509 | 510 | zingResponse.Message.ShouldBe("Zing Zong"); 511 | 512 | output.Messages.ShouldBe(new[] 513 | { 514 | "Outer generic before", 515 | "Inner generic before", 516 | "First pre processor", 517 | "Next pre processor", 518 | "Handler", 519 | "First post processor", 520 | "Next post processor", 521 | "Inner generic after", 522 | "Outer generic after" 523 | }); 524 | } 525 | 526 | } --------------------------------------------------------------------------------