Repository: LuckyPennySoftware/MediatR Branch: main Commit: f72aef8d786f Files: 182 Total size: 438.7 KB Directory structure: gitextract_64igt8ai/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── ci.yml │ ├── release.yml │ └── triage-issues.yml ├── .gitignore ├── Build.ps1 ├── BuildContracts.ps1 ├── Directory.Build.props ├── LICENSE.md ├── MediatR.slnx ├── MediatR.snk ├── NuGet.Config ├── Push.ps1 ├── README.md ├── samples/ │ ├── MediatR.Examples/ │ │ ├── ConstrainedRequestPostProcessor.cs │ │ ├── ExceptionHandler/ │ │ │ ├── Exceptions.cs │ │ │ ├── ExceptionsHandlers.cs │ │ │ ├── ExceptionsHandlersOverrides.cs │ │ │ ├── Handlers.cs │ │ │ ├── LogExceptionAction.cs │ │ │ ├── Requests.cs │ │ │ └── RequestsOverrides.cs │ │ ├── GenericHandler.cs │ │ ├── GenericPipelineBehavior.cs │ │ ├── GenericRequestPostProcessor.cs │ │ ├── GenericRequestPreProcessor.cs │ │ ├── Jing.cs │ │ ├── JingHandler.cs │ │ ├── MediatR.Examples.csproj │ │ ├── Ping.cs │ │ ├── PingHandler.cs │ │ ├── Pinged.cs │ │ ├── PingedHandler.cs │ │ ├── Pong.cs │ │ ├── Ponged.cs │ │ ├── Runner.cs │ │ └── Streams/ │ │ ├── GenericStreamPipelineBehavior.cs │ │ ├── Sing.cs │ │ ├── SingHandler.cs │ │ └── Song.cs │ ├── MediatR.Examples.AspNetCore/ │ │ ├── MediatR.Examples.AspNetCore.csproj │ │ └── Program.cs │ ├── MediatR.Examples.Autofac/ │ │ ├── MediatR.Examples.Autofac.csproj │ │ └── Program.cs │ ├── MediatR.Examples.DryIoc/ │ │ ├── MediatR.Examples.DryIoc.csproj │ │ └── Program.cs │ ├── MediatR.Examples.Lamar/ │ │ ├── MediatR.Examples.Lamar.csproj │ │ └── Program.cs │ ├── MediatR.Examples.LightInject/ │ │ ├── MediatR.Examples.LightInject.csproj │ │ └── Program.cs │ ├── MediatR.Examples.PublishStrategies/ │ │ ├── AsyncPingedHandler.cs │ │ ├── CustomMediator.cs │ │ ├── MediatR.Examples.PublishStrategies.csproj │ │ ├── Program.cs │ │ ├── PublishStrategy.cs │ │ ├── Publisher.cs │ │ └── SyncPingedHandler.cs │ ├── MediatR.Examples.SimpleInjector/ │ │ ├── MediatR.Examples.SimpleInjector.csproj │ │ └── Program.cs │ ├── MediatR.Examples.Stashbox/ │ │ ├── MediatR.Examples.Stashbox.csproj │ │ └── Program.cs │ └── MediatR.Examples.Windsor/ │ ├── ContravariantFilter.cs │ ├── MediatR.Examples.Windsor.csproj │ └── Program.cs ├── src/ │ ├── MediatR/ │ │ ├── Entities/ │ │ │ └── OpenBehavior.cs │ │ ├── IMediator.cs │ │ ├── INotificationHandler.cs │ │ ├── INotificationPublisher.cs │ │ ├── IPipelineBehavior.cs │ │ ├── IPublisher.cs │ │ ├── IRequestHandler.cs │ │ ├── ISender.cs │ │ ├── IStreamPipelineBehavior.cs │ │ ├── IStreamRequestHandler.cs │ │ ├── Internal/ │ │ │ ├── HandlersOrderer.cs │ │ │ └── ObjectDetails.cs │ │ ├── Licensing/ │ │ │ ├── BuildInfo.cs │ │ │ ├── Edition.cs │ │ │ ├── License.cs │ │ │ ├── LicenseAccessor.cs │ │ │ ├── LicenseValidator.cs │ │ │ └── ProductType.cs │ │ ├── MediatR.csproj │ │ ├── Mediator.cs │ │ ├── MicrosoftExtensionsDI/ │ │ │ ├── MediatRServiceCollectionExtensions.cs │ │ │ ├── MediatrServiceConfiguration.cs │ │ │ └── RequestExceptionActionProcessorStrategy.cs │ │ ├── NotificationHandlerExecutor.cs │ │ ├── NotificationPublishers/ │ │ │ ├── ForeachAwaitPublisher.cs │ │ │ └── TaskWhenAllPublisher.cs │ │ ├── Pipeline/ │ │ │ ├── IRequestExceptionAction.cs │ │ │ ├── IRequestExceptionHandler.cs │ │ │ ├── IRequestPostProcessor.cs │ │ │ ├── IRequestPreProcessor.cs │ │ │ ├── RequestExceptionActionProcessorBehavior.cs │ │ │ ├── RequestExceptionHandlerState.cs │ │ │ ├── RequestExceptionProcessorBehavior.cs │ │ │ ├── RequestPostProcessorBehavior.cs │ │ │ └── RequestPreProcessorBehavior.cs │ │ ├── Registration/ │ │ │ └── ServiceRegistrar.cs │ │ ├── TypeForwardings.cs │ │ ├── Wrappers/ │ │ │ ├── NotificationHandlerWrapper.cs │ │ │ ├── RequestHandlerWrapper.cs │ │ │ └── StreamRequestHandlerWrapper.cs │ │ └── license.txt │ └── MediatR.Contracts/ │ ├── INotification.cs │ ├── IRequest.cs │ ├── IStreamRequest.cs │ ├── MediatR.Contracts.csproj │ └── Unit.cs └── test/ ├── MediatR.Benchmarks/ │ ├── Benchmarks.cs │ ├── DotTraceDiagnoser.cs │ ├── GenericPipelineBehavior.cs │ ├── GenericRequestPostProcessor.cs │ ├── GenericRequestPreProcessor.cs │ ├── MediatR.Benchmarks.csproj │ ├── Ping.cs │ ├── Pinged.cs │ └── Program.cs ├── MediatR.DependencyInjectionTests/ │ ├── Abstractions/ │ │ ├── BaseAssemblyResolutionTests.cs │ │ └── BaseServiceProviderFixture.cs │ ├── AutoFacDependencyInjectionTests.cs │ ├── Contracts/ │ │ ├── Notifications/ │ │ │ └── Ding.cs │ │ ├── Requests/ │ │ │ ├── InternalPing.cs │ │ │ ├── InternalVoidPing.cs │ │ │ ├── PrivatePing.cs │ │ │ ├── PrivateVoidPing.cs │ │ │ ├── PublicPing.cs │ │ │ └── PublicVoidPing.cs │ │ ├── Responses/ │ │ │ └── Pong.cs │ │ └── StreamRequests/ │ │ ├── InternalZing.cs │ │ ├── PrivateZing.cs │ │ └── PublicZing.cs │ ├── DryIocDependencyInjectionTests.cs │ ├── LamarDependencyInjectionTests.cs │ ├── LightInjectDependencyInjectionTests.cs │ ├── MediatR.DependencyInjectionTests.csproj │ ├── MicrosoftDependencyInjectionTests.cs │ ├── Providers/ │ │ ├── AutoFacServiceProviderFixture.cs │ │ ├── DryIocServiceProviderFixture.cs │ │ ├── LamarServiceProviderFixture.cs │ │ ├── LightInjectServiceProviderFixture.cs │ │ ├── MicrosoftServiceProviderFixture.cs │ │ └── StashBoxServiceProviderFixture.cs │ ├── StashBoxDependencyInjectionTests.cs │ └── Usings.cs └── MediatR.Tests/ ├── CreateStreamTests.cs ├── ExceptionTests.cs ├── GenericRequestHandlerTests.cs ├── GenericTypeConstraintsTests.cs ├── GlobalUsings.cs ├── Licensing/ │ └── LicenseValidatorTests.cs ├── MediatR.Tests.csproj ├── MicrosoftExtensionsDI/ │ ├── AssemblyResolutionTests.cs │ ├── BaseGenericRequestHandlerTests.cs │ ├── CustomMediatorTests.cs │ ├── DerivingRequestsTests.cs │ ├── DuplicateAssemblyResolutionTests.cs │ ├── Handlers.cs │ ├── Issue1118Tests.cs │ ├── NotificationPublisherTests.cs │ ├── PipeLineMultiCallToConstructorTest.cs │ ├── PipelineTests.cs │ ├── StreamPipelineTests.cs │ ├── TypeEvaluatorTests.cs │ └── TypeResolutionTests.cs ├── NotificationHandlerTests.cs ├── NotificationPublisherTests.cs ├── Pipeline/ │ ├── RequestExceptionActionTests.cs │ ├── RequestExceptionHandlerTests.cs │ ├── RequestPostProcessorTests.cs │ ├── RequestPreProcessorTests.cs │ └── Streams/ │ └── StreamPipelineBehaviorTests.cs ├── PipelineTests.cs ├── PublishTests.cs ├── SendTests.cs ├── SendVoidInterfaceTests.cs ├── ServiceFactoryTests.cs ├── StreamPipelineTests.cs ├── TestContainer.cs └── UnitTests.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root=true ; EditorConfig helps developers define and maintain consistent ; coding styles between different editors and IDEs. ; For more visit http://editorconfig.org. ; Choose between lf or rf on "end_of_line" property [*.proto] indent_style=tab indent_size=tab tab_width=4 [*.{asax,ascx,aspx,cs,cshtml,css,htm,html,js,jsx,master,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] indent_style=space indent_size=4 tab_width=4 [*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] indent_style=space indent_size=2 tab_width=2 [*] # Standard properties end_of_line=native insert_final_newline=false # Microsoft .NET properties csharp_indent_braces=false csharp_indent_switch_labels=true csharp_new_line_before_catch=true csharp_new_line_before_else=true csharp_new_line_before_finally=true csharp_new_line_before_members_in_object_initializers=false csharp_new_line_before_open_brace=all csharp_new_line_between_query_expression_clauses=true csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion csharp_preserve_single_line_blocks=true csharp_space_after_cast=true csharp_space_after_colon_in_inheritance_clause=true csharp_space_after_comma=true csharp_space_after_dot=false csharp_space_after_keywords_in_control_flow_statements=true csharp_space_after_semicolon_in_for_statement=true csharp_space_around_binary_operators=before_and_after csharp_space_before_colon_in_inheritance_clause=true csharp_space_before_comma=false csharp_space_before_dot=false csharp_space_before_open_square_brackets=false csharp_space_before_semicolon_in_for_statement=false csharp_space_between_empty_square_brackets=false csharp_space_between_method_call_empty_parameter_list_parentheses=false csharp_space_between_method_call_name_and_opening_parenthesis=false csharp_space_between_method_call_parameter_list_parentheses=false csharp_space_between_method_declaration_empty_parameter_list_parentheses=false csharp_space_between_method_declaration_name_and_open_parenthesis=false csharp_space_between_method_declaration_parameter_list_parentheses=false csharp_space_between_parentheses=false csharp_space_between_square_brackets=false csharp_style_expression_bodied_accessors=true:suggestion csharp_style_expression_bodied_constructors=true:suggestion csharp_style_expression_bodied_methods=true:suggestion csharp_style_expression_bodied_properties=true:suggestion csharp_style_var_elsewhere=true:hint csharp_style_var_for_built_in_types=true:hint csharp_style_var_when_type_is_apparent=true:hint csharp_using_directive_placement=outside_namespace:silent dotnet_style_predefined_type_for_locals_parameters_members=true:hint dotnet_style_predefined_type_for_member_access=true:hint dotnet_style_qualification_for_event=false:hint dotnet_style_qualification_for_field=false:hint dotnet_style_qualification_for_method=false:hint dotnet_style_qualification_for_property=false:hint dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint ================================================ FILE: .gitattributes ================================================ *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain *.jpg binary *.png binary *.gif binary core.eol crlf *.cs diff=csharp *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union *.sln merge=union *.slnx merge=union ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: workflow_dispatch: push: branches: - main pull_request: env: DOTNET_NOLOGO: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true MINVERBUILDMETADATA: build.${{ github.run_id }}.${{ github.run_attempt}} permissions: id-token: write contents: read checks: write jobs: build: runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4.2.0 with: fetch-depth: 0 filter: tree:0 - name: Setup dotnet uses: actions/setup-dotnet@v4 with: dotnet-version: | 8.0.x 9.0.x 10.0.x - name: Install NuGetKeyVaultSignTool run: dotnet tool install --global NuGetKeyVaultSignTool - name: Build and Test run: ./Build.ps1 shell: pwsh - name: Report Test Results uses: dorny/test-reporter@v1.9.1 if: success() || failure() with: name: Test Results (Windows) path: '**/TestResults/**/*.trx' reporter: dotnet-trx - name: Get package version id: version shell: pwsh run: | $pkg = Get-ChildItem ./artifacts -Filter "MediatR.*.nupkg" | Select-Object -First 1 $version = $pkg.BaseName -replace '^MediatR\.', '' "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - name: Generate SBOM shell: pwsh run: | dotnet tool install --global Microsoft.Sbom.DotNetTool sbom-tool generate ` -b ./artifacts ` -bc ./src/MediatR ` -pn MediatR ` -pv ${{ steps.version.outputs.version }} ` -ps "Lucky Penny Software LLC" ` -nsb https://mediatr.io - name: Push to MyGet if: github.ref == 'refs/heads/main' env: NUGET_URL: https://f.feedz.io/lucky-penny-software/mediatr/nuget/index.json NUGET_API_KEY: ${{ secrets.FEEDZIO_ACCESS_TOKEN }} run: ./Push.ps1 shell: pwsh - name: Artifacts uses: actions/upload-artifact@v4 with: name: artifacts path: artifacts/**/* ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: tags: - '*.*.*' env: DOTNET_NOLOGO: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true MINVERBUILDMETADATA: build.${{ github.run_id }}.${{ github.run_attempt}} permissions: id-token: write contents: read checks: write jobs: build: strategy: matrix: os: [windows-2025] fail-fast: false runs-on: windows-2025 steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 filter: tree:0 - name: Azure Login via OIDC uses: azure/login@v2 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Setup dotnet uses: actions/setup-dotnet@v4 with: dotnet-version: | 8.0.x 9.0.x 10.0.x - name: Install NuGetKeyVaultSignTool run: dotnet tool install --global NuGetKeyVaultSignTool - name: Build and Test run: ./Build.ps1 shell: pwsh - name: Report Test Results uses: dorny/test-reporter@v1.9.1 if: always() with: name: Test Results (Windows) path: '**/TestResults/**/*.trx' reporter: dotnet-trx - name: Get package version id: version shell: pwsh run: | $pkg = Get-ChildItem ./artifacts -Filter "MediatR.*.nupkg" | Select-Object -First 1 $version = $pkg.BaseName -replace '^MediatR\.', '' "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - name: Generate SBOM shell: pwsh run: | dotnet tool install --global Microsoft.Sbom.DotNetTool sbom-tool generate ` -b ./artifacts ` -bc ./src/MediatR ` -pn MediatR ` -pv ${{ steps.version.outputs.version }} ` -ps "Lucky Penny Software LLC" ` -nsb https://mediatr.io - name: Sign packages run: |- foreach ($f in Get-ChildItem "./artifacts" -Filter "*.nupkg") { NuGetKeyVaultSignTool sign $f.FullName --file-digest sha256 --timestamp-rfc3161 http://timestamp.digicert.com --azure-key-vault-managed-identity --azure-key-vault-url ${{ secrets.AZURE_KEYVAULT_URI }} --azure-key-vault-certificate ${{ secrets.CODESIGN_CERT_NAME }} } - name: Push to MyGet env: NUGET_URL: https://f.feedz.io/lucky-penny-software/mediatr/nuget/index.json NUGET_API_KEY: ${{ secrets.FEEDZIO_ACCESS_TOKEN }} run: ./Push.ps1 shell: pwsh - name: Push to NuGet env: NUGET_URL: https://api.nuget.org/v3/index.json NUGET_API_KEY: ${{ secrets.MEDIATR_NUGET_API_KEY }} run: ./Push.ps1 shell: pwsh - name: Artifacts uses: actions/upload-artifact@v4 with: name: artifacts path: artifacts/**/* ================================================ FILE: .github/workflows/triage-issues.yml ================================================ # https://github.com/actions/stale name: "Stale issue & PR handler" on: workflow_dispatch: schedule: - cron: "0 12 * * *" env: ISSUES_DAYS_BEFORE_CLOSE: 14 PR_DAYS_BEFORE_CLOSE: 14 ISSUES_DAYS_BEFORE_STALE: 60 PR_DAYS_BEFORE_STALE: 28 jobs: issues: name: "Close stale issues and PRs" runs-on: "ubuntu-latest" steps: - uses: "actions/stale@v6.0.0" with: stale-issue-label: "stale" stale-issue-message: "This issue is stale because it has been open ${{ env.ISSUES_DAYS_BEFORE_STALE }} days with no activity. Remove stale label or comment or this will be closed in ${{ env.ISSUES_DAYS_BEFORE_CLOSE }} days." close-issue-message: 'This issue was closed because it has been stalled for ${{ env.ISSUES_DAYS_BEFORE_CLOSE }} days with no activity.' days-before-close: "${{ env.ISSUES_DAYS_BEFORE_CLOSE }}" days-before-stale: "${{ env.ISSUES_DAYS_BEFORE_STALE }}" exempt-issue-assignees: true exempt-issue-labels: 'awaiting-approval,work-in-progress,up-for-grabs' stale-pr-label: "stale" stale-pr-message: 'This PR is stale because it has been open ${{ env.PR_DAYS_BEFORE_STALE }} days with no activity. Remove stale label or comment or this will be closed in ${{ env.PR_DAYS_BEFORE_CLOSE }} days.' close-pr-message: 'This PR was closed because it has been stalled for ${{ env.PR_DAYS_BEFORE_CLOSE }} days with no activity.' days-before-pr-close: "${{ env.PR_DAYS_BEFORE_CLOSE }}" days-before-pr-stale: "${{ env.PR_DAYS_BEFORE_STALE }}" exempt-all-pr-assignees: true exempt-pr-labels: 'awaiting-approval,work-in-progress' ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ artifacts/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* *.ncrunchsolution # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Project Rider *.iml .idea # Apple .DS_Store ================================================ FILE: Build.ps1 ================================================ # Taken from psake https://github.com/psake/psake <# .SYNOPSIS This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode to see if an error occcured. If an error is detected then an exception is thrown. This function allows you to run command-line programs without having to explicitly check the $lastexitcode variable. .EXAMPLE exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" #> function Exec { [CmdletBinding()] param( [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd) ) & $cmd if ($lastexitcode -ne 0) { throw ("Exec: " + $errorMessage) } } $artifacts = ".\artifacts" if(Test-Path $artifacts) { Remove-Item $artifacts -Force -Recurse } exec { & dotnet clean -c Release } exec { & dotnet build -c Release } exec { & dotnet test -c Release --no-build -l trx --verbosity=normal } exec { & dotnet pack .\src\MediatR\MediatR.csproj -c Release -o $artifacts --no-build } ================================================ FILE: BuildContracts.ps1 ================================================ # Taken from psake https://github.com/psake/psake <# .SYNOPSIS This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode to see if an error occcured. If an error is detected then an exception is thrown. This function allows you to run command-line programs without having to explicitly check the $lastexitcode variable. .EXAMPLE exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" #> function Exec { [CmdletBinding()] param( [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd) ) & $cmd if ($lastexitcode -ne 0) { throw ("Exec: " + $errorMessage) } } $artifacts = ".\artifacts" $contracts = ".\src\MediatR.Contracts\MediatR.Contracts.csproj" if(Test-Path $artifacts) { Remove-Item $artifacts -Force -Recurse } exec { & dotnet clean $contracts -c Release } exec { & dotnet build $contracts -c Release -p:ContinuousIntegrationBuild=true } exec { & dotnet pack $contracts -c Release -o $artifacts --no-build } ================================================ FILE: Directory.Build.props ================================================ $([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::get_OSX()))) $([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::get_Windows()))) 13.0 $(NoWarn);CS1701;CS1702;CS1591;NU1900 true ================================================ FILE: LICENSE.md ================================================ By accessing code under the [Lucky Penny Software GitHub Organization](https://github.com/LuckyPennySoftware) (Lucky Penny Software) here, you are agreeing to the following licensing terms. If you do not agree to these terms, do not access Lucky Penny Software code. Your license to Lucky Penny Software source code and/or binaries is governed by the Reciprocal Public License 1.5 (RPL1.5) license as described here: https://opensource.org/license/rpl-1-5/ If you do not wish to release the source of software you build using Lucky Penny Software source code and/or binaries under the terms above, you may use Lucky Penny Software source code and/or binaries under the License Agreement described here: https://luckypennysoftware.com/license ================================================ FILE: MediatR.slnx ================================================ ================================================ FILE: NuGet.Config ================================================  ================================================ FILE: Push.ps1 ================================================ $scriptName = $MyInvocation.MyCommand.Name $artifacts = "./artifacts" if ([string]::IsNullOrEmpty($Env:NUGET_API_KEY)) { Write-Host "${scriptName}: NUGET_API_KEY is empty or not set. Skipped pushing package(s)." } else { Get-ChildItem $artifacts -Filter "*.nupkg" | ForEach-Object { Write-Host "$($scriptName): Pushing $($_.Name)" dotnet nuget push $_ --source $Env:NUGET_URL --api-key $Env:NUGET_API_KEY --skip-duplicate if ($lastexitcode -ne 0) { throw ("Exec: " + $errorMessage) } } } ================================================ FILE: README.md ================================================ MediatR ======= ![CI](https://github.com/LuckyPennySoftware/MediatR/workflows/CI/badge.svg) [![NuGet](https://img.shields.io/nuget/dt/mediatr.svg)](https://www.nuget.org/packages/mediatr) [![NuGet](https://img.shields.io/nuget/vpre/mediatr.svg)](https://www.nuget.org/packages/mediatr) [![MyGet (dev)](https://img.shields.io/myget/mediatr-ci/v/MediatR.svg)](https://myget.org/gallery/mediatr-ci) Simple mediator implementation in .NET In-process messaging with no dependencies. Supports request/response, commands, queries, notifications and events, synchronous and async with intelligent dispatching via C# generic variance. Examples in the [wiki](https://github.com/LuckyPennySoftware/MediatR/wiki). ### Installing MediatR You should install [MediatR with NuGet](https://www.nuget.org/packages/MediatR): Install-Package MediatR Or via the .NET Core command line interface: dotnet add package MediatR Either commands, from Package Manager Console or .NET Core CLI, will download and install MediatR and all required dependencies. ### Using Contracts-Only Package To reference only the contracts for MediatR, which includes: - `IRequest` (including generic variants) - `INotification` - `IStreamRequest` Add a package reference to [MediatR.Contracts](https://www.nuget.org/packages/MediatR.Contracts) This package is useful in scenarios where your MediatR contracts are in a separate assembly/project from handlers. Example scenarios include: - API contracts - GRPC contracts - Blazor ### Registering with `IServiceCollection` MediatR supports `Microsoft.Extensions.DependencyInjection.Abstractions` directly. To register various MediatR services and handlers: ``` services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining()); ``` or with an assembly: ``` services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly)); ``` This registers: - `IMediator` as transient - `ISender` as transient - `IPublisher` as transient - `IRequestHandler<,>` concrete implementations as transient - `IRequestHandler<>` concrete implementations as transient - `INotificationHandler<>` concrete implementations as transient - `IStreamRequestHandler<>` concrete implementations as transient - `IRequestExceptionHandler<,,>` concrete implementations as transient - `IRequestExceptionAction<,>)` concrete implementations as transient This also registers open generic implementations for: - `INotificationHandler<>` - `IRequestExceptionHandler<,,>` - `IRequestExceptionAction<,>` To register behaviors, stream behaviors, pre/post processors: ```csharp services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly); cfg.AddBehavior(); cfg.AddStreamBehavior(); cfg.AddRequestPreProcessor(); cfg.AddRequestPostProcessor(); cfg.AddOpenBehavior(typeof(GenericBehavior<,>)); }); ``` With additional methods for open generics and overloads for explicit service types. ### Setting the license key You can set the license key when registering MediatR: ```csharp services.AddMediatR(cfg => { cfg.LicenseKey = ""; }) ``` Or if not using Microsoft.Extensions.DependencyInjection: ```csharp Mediator.LicenseKey = ""; ``` > [!TIP] > The license key does not need to be set on client applications (such as Blazor WASM). > Turn off the license warning by configuring logging in your logging start configuration: > `builder.Logging.AddFilter("LuckyPennySoftware.MediatR.License", LogLevel.None);` You can register for your license key at [MediatR.io](https://mediatr.io) ================================================ FILE: samples/MediatR.Examples/ConstrainedRequestPostProcessor.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; using MediatR.Pipeline; namespace MediatR.Examples; public class ConstrainedRequestPostProcessor : IRequestPostProcessor where TRequest : Ping { private readonly TextWriter _writer; public ConstrainedRequestPostProcessor(TextWriter writer) { _writer = writer; } public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) { return _writer.WriteLineAsync("- All Done with Ping"); } } ================================================ FILE: samples/MediatR.Examples/ExceptionHandler/Exceptions.cs ================================================ using System; namespace MediatR.Examples.ExceptionHandler; public class ConnectionException : Exception { } public class ForbiddenException : ConnectionException { } public class ResourceNotFoundException : ConnectionException { } public class ServerException : Exception { } ================================================ FILE: samples/MediatR.Examples/ExceptionHandler/ExceptionsHandlers.cs ================================================ using MediatR.Pipeline; using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.ExceptionHandler; public class CommonExceptionHandler : IRequestExceptionHandler { private readonly TextWriter _writer; public CommonExceptionHandler(TextWriter writer) => _writer = writer; public async Task Handle(PingResource request, Exception exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { // Exception type name must be written in messages by LogExceptionAction before // Exception handler type name required because it is checked later in messages await _writer.WriteLineAsync($"---- Exception Handler: '{typeof(CommonExceptionHandler).FullName}'").ConfigureAwait(false); state.SetHandled(new Pong()); } } public class ConnectionExceptionHandler : IRequestExceptionHandler { private readonly TextWriter _writer; public ConnectionExceptionHandler(TextWriter writer) => _writer = writer; public async Task Handle(PingResource request, ConnectionException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { // Exception type name must be written in messages by LogExceptionAction before // Exception handler type name required because it is checked later in messages await _writer.WriteLineAsync($"---- Exception Handler: '{typeof(ConnectionExceptionHandler).FullName}'").ConfigureAwait(false); state.SetHandled(new Pong()); } } public class AccessDeniedExceptionHandler : IRequestExceptionHandler { private readonly TextWriter _writer; public AccessDeniedExceptionHandler(TextWriter writer) => _writer = writer; public async Task Handle(PingResource request, ForbiddenException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { // Exception type name must be written in messages by LogExceptionAction before // Exception handler type name required because it is checked later in messages await _writer.WriteLineAsync($"---- Exception Handler: '{typeof(AccessDeniedExceptionHandler).FullName}'").ConfigureAwait(false); state.SetHandled(new Pong()); } } public class ServerExceptionHandler : IRequestExceptionHandler { private readonly TextWriter _writer; public ServerExceptionHandler(TextWriter writer) => _writer = writer; public virtual async Task Handle(PingNewResource request, ServerException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { // Exception type name must be written in messages by LogExceptionAction before // Exception handler type name required because it is checked later in messages await _writer.WriteLineAsync($"---- Exception Handler: '{typeof(ServerExceptionHandler).FullName}'").ConfigureAwait(false); state.SetHandled(new Pong()); } } ================================================ FILE: samples/MediatR.Examples/ExceptionHandler/ExceptionsHandlersOverrides.cs ================================================ using MediatR.Pipeline; using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.ExceptionHandler.Overrides; public class CommonExceptionHandler : IRequestExceptionHandler { private readonly TextWriter _writer; public CommonExceptionHandler(TextWriter writer) => _writer = writer; public async Task Handle(PingResourceTimeout request, Exception exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { // Exception type name must be written in messages by LogExceptionAction before // Exception handler type name required because it is checked later in messages await _writer.WriteLineAsync($"---- Exception Handler: '{typeof(CommonExceptionHandler).FullName}'").ConfigureAwait(false); state.SetHandled(new Pong()); } } public class ServerExceptionHandler : ExceptionHandler.ServerExceptionHandler { private readonly TextWriter _writer; public ServerExceptionHandler(TextWriter writer) : base(writer) => _writer = writer; public override async Task Handle(PingNewResource request, ServerException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { // Exception type name must be written in messages by LogExceptionAction before // Exception handler type name required because it is checked later in messages await _writer.WriteLineAsync($"---- Exception Handler: '{typeof(ServerExceptionHandler).FullName}'").ConfigureAwait(false); state.SetHandled(new Pong()); } } ================================================ FILE: samples/MediatR.Examples/ExceptionHandler/Handlers.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.ExceptionHandler; public class PingResourceHandler : IRequestHandler { private readonly TextWriter _writer; public PingResourceHandler(TextWriter writer) => _writer = writer; public Task Handle(PingResource request, CancellationToken cancellationToken) { throw new ResourceNotFoundException(); } } public class PingNewResourceHandler : IRequestHandler { private readonly TextWriter _writer; public PingNewResourceHandler(TextWriter writer) => _writer = writer; public Task Handle(PingNewResource request, CancellationToken cancellationToken) { throw new ServerException(); } } public class PingResourceTimeoutHandler : IRequestHandler { private readonly TextWriter _writer; public PingResourceTimeoutHandler(TextWriter writer) => _writer = writer; public Task Handle(PingResourceTimeout request, CancellationToken cancellationToken) { throw new TaskCanceledException(); } } public class PingResourceTimeoutOverrideHandler : IRequestHandler { private readonly TextWriter _writer; public PingResourceTimeoutOverrideHandler(TextWriter writer) => _writer = writer; public Task Handle(Overrides.PingResourceTimeout request, CancellationToken cancellationToken) { throw new TaskCanceledException(); } } public class PingProtectedResourceHandler : IRequestHandler { private readonly TextWriter _writer; public PingProtectedResourceHandler(TextWriter writer) => _writer = writer; public Task Handle(PingProtectedResource request, CancellationToken cancellationToken) { throw new ForbiddenException(); } } ================================================ FILE: samples/MediatR.Examples/ExceptionHandler/LogExceptionAction.cs ================================================ using MediatR.Pipeline; using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.ExceptionHandler; public class LogExceptionAction : IRequestExceptionAction { private readonly TextWriter _writer; public LogExceptionAction(TextWriter writer) => _writer = writer; public Task Execute(Ping request, Exception exception, CancellationToken cancellationToken) => _writer.WriteLineAsync($"--- Exception: '{exception.GetType().FullName}'"); } ================================================ FILE: samples/MediatR.Examples/ExceptionHandler/Requests.cs ================================================ namespace MediatR.Examples.ExceptionHandler; public class PingResource : Ping { } public class PingNewResource : Ping { } public class PingResourceTimeout : PingResource { } public class PingProtectedResource : PingResource { } ================================================ FILE: samples/MediatR.Examples/ExceptionHandler/RequestsOverrides.cs ================================================ namespace MediatR.Examples.ExceptionHandler.Overrides; public class PingResourceTimeout : ExceptionHandler.PingResourceTimeout { } ================================================ FILE: samples/MediatR.Examples/GenericHandler.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples; public class GenericHandler : INotificationHandler { private readonly TextWriter _writer; public GenericHandler(TextWriter writer) { _writer = writer; } public Task Handle(INotification notification, CancellationToken cancellationToken) { return _writer.WriteLineAsync("Got notified."); } } ================================================ FILE: samples/MediatR.Examples/GenericPipelineBehavior.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples; public class GenericPipelineBehavior : IPipelineBehavior { private readonly TextWriter _writer; public GenericPipelineBehavior(TextWriter writer) { _writer = writer; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { await _writer.WriteLineAsync("-- Handling Request"); var response = await next(); await _writer.WriteLineAsync("-- Finished Request"); return response; } } ================================================ FILE: samples/MediatR.Examples/GenericRequestPostProcessor.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; using MediatR.Pipeline; namespace MediatR.Examples; public class GenericRequestPostProcessor : IRequestPostProcessor { private readonly TextWriter _writer; public GenericRequestPostProcessor(TextWriter writer) { _writer = writer; } public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) { return _writer.WriteLineAsync("- All Done"); } } ================================================ FILE: samples/MediatR.Examples/GenericRequestPreProcessor.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; using MediatR.Pipeline; namespace MediatR.Examples; public class GenericRequestPreProcessor : IRequestPreProcessor { private readonly TextWriter _writer; public GenericRequestPreProcessor(TextWriter writer) { _writer = writer; } public Task Process(TRequest request, CancellationToken cancellationToken) { return _writer.WriteLineAsync("- Starting Up"); } } ================================================ FILE: samples/MediatR.Examples/Jing.cs ================================================ namespace MediatR.Examples; public class Jing : IRequest { public string Message { get; set; } } ================================================ FILE: samples/MediatR.Examples/JingHandler.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples; public class JingHandler : IRequestHandler { private readonly TextWriter _writer; public JingHandler(TextWriter writer) { _writer = writer; } public Task Handle(Jing request, CancellationToken cancellationToken) { return _writer.WriteLineAsync($"--- Handled Jing: {request.Message}, no Jong"); } } ================================================ FILE: samples/MediatR.Examples/MediatR.Examples.csproj ================================================  net10.0 ================================================ FILE: samples/MediatR.Examples/Ping.cs ================================================ namespace MediatR.Examples; public class Ping : IRequest { public string Message { get; set; } } ================================================ FILE: samples/MediatR.Examples/PingHandler.cs ================================================ using System.IO; using System.Threading; namespace MediatR.Examples; using System.Threading.Tasks; public class PingHandler : IRequestHandler { private readonly TextWriter _writer; public PingHandler(TextWriter writer) { _writer = writer; } public async Task Handle(Ping request, CancellationToken cancellationToken) { await _writer.WriteLineAsync($"--- Handled Ping: {request.Message}"); return new Pong { Message = request.Message + " Pong" }; } } ================================================ FILE: samples/MediatR.Examples/Pinged.cs ================================================ namespace MediatR.Examples; public class Pinged : INotification { } ================================================ FILE: samples/MediatR.Examples/PingedHandler.cs ================================================ using System.Threading; namespace MediatR.Examples; using System.IO; using System.Threading.Tasks; public class PingedHandler : INotificationHandler { private readonly TextWriter _writer; public PingedHandler(TextWriter writer) { _writer = writer; } public Task Handle(Pinged notification, CancellationToken cancellationToken) { return _writer.WriteLineAsync("Got pinged async."); } } public class PongedHandler : INotificationHandler { private readonly TextWriter _writer; public PongedHandler(TextWriter writer) { _writer = writer; } public Task Handle(Ponged notification, CancellationToken cancellationToken) { return _writer.WriteLineAsync("Got ponged async."); } } public class ConstrainedPingedHandler : INotificationHandler where TNotification : Pinged { private readonly TextWriter _writer; public ConstrainedPingedHandler(TextWriter writer) { _writer = writer; } public Task Handle(TNotification notification, CancellationToken cancellationToken) { return _writer.WriteLineAsync("Got pinged constrained async."); } } public class PingedAlsoHandler : INotificationHandler { private readonly TextWriter _writer; public PingedAlsoHandler(TextWriter writer) { _writer = writer; } public Task Handle(Pinged notification, CancellationToken cancellationToken) { return _writer.WriteLineAsync("Got pinged also async."); } } ================================================ FILE: samples/MediatR.Examples/Pong.cs ================================================ namespace MediatR.Examples; public class Pong { public string Message { get; set; } } ================================================ FILE: samples/MediatR.Examples/Ponged.cs ================================================ namespace MediatR.Examples; public class Ponged : INotification { } ================================================ FILE: samples/MediatR.Examples/Runner.cs ================================================ using System; using System.Linq; using System.Text; namespace MediatR.Examples; using MediatR.Examples.ExceptionHandler; using System.IO; using System.Threading.Tasks; public static class Runner { public static async Task Run(IMediator mediator, WrappingWriter writer, string projectName, bool testStreams = false) { await writer.WriteLineAsync("==============="); await writer.WriteLineAsync(projectName); await writer.WriteLineAsync("==============="); await writer.WriteLineAsync(); await writer.WriteLineAsync("Sending Ping..."); var pong = await mediator.Send(new Ping { Message = "Ping" }); await writer.WriteLineAsync("Received: " + pong.Message); await writer.WriteLineAsync(); await writer.WriteLineAsync("Publishing Pinged..."); await mediator.Publish(new Pinged()); await writer.WriteLineAsync(); await writer.WriteLineAsync("Publishing Ponged..."); var failedPong = false; try { await mediator.Publish(new Ponged()); } catch (Exception e) { failedPong = true; await writer.WriteLineAsync(e.ToString()); } await writer.WriteLineAsync(); var failedJing = false; await writer.WriteLineAsync("Sending Jing..."); try { await mediator.Send(new Jing { Message = "Jing" }); } catch (Exception e) { failedJing = true; await writer.WriteLineAsync(e.ToString()); } await writer.WriteLineAsync(); bool failedSing = false; if (testStreams) { await writer.WriteLineAsync("Sending Sing..."); try { int i = 0; await foreach (Song s in mediator.CreateStream(new Sing { Message = "Sing" })) { if (i == 0) { failedSing = !(s.Message.Contains("Singing do")); } else if (i == 1) { failedSing = !(s.Message.Contains("Singing re")); } else if (i == 2) { failedSing = !(s.Message.Contains("Singing mi")); } else if (i == 3) { failedSing = !(s.Message.Contains("Singing fa")); } else if (i == 4) { failedSing = !(s.Message.Contains("Singing so")); } else if (i == 5) { failedSing = !(s.Message.Contains("Singing la")); } else if (i == 6) { failedSing = !(s.Message.Contains("Singing ti")); } else if (i == 7) { failedSing = !(s.Message.Contains("Singing do")); } failedSing = failedSing || (++i) > 10; } } catch (Exception e) { failedSing = true; await writer.WriteLineAsync(e.ToString()); } await writer.WriteLineAsync(); } var isHandlerForSameExceptionWorks = await IsHandlerForSameExceptionWorks(mediator, writer).ConfigureAwait(false); var isHandlerForBaseExceptionWorks = await IsHandlerForBaseExceptionWorks(mediator, writer).ConfigureAwait(false); var isHandlerForLessSpecificExceptionWorks = await IsHandlerForLessSpecificExceptionWorks(mediator, writer).ConfigureAwait(false); var isPreferredHandlerForBaseExceptionWorks = await IsPreferredHandlerForBaseExceptionWorks(mediator, writer).ConfigureAwait(false); var isOverriddenHandlerForBaseExceptionWorks = await IsOverriddenHandlerForBaseExceptionWorks(mediator, writer).ConfigureAwait(false); await writer.WriteLineAsync("---------------"); var contents = writer.Contents; var order = new[] { contents.IndexOf("- Starting Up", StringComparison.OrdinalIgnoreCase), contents.IndexOf("-- Handling Request", StringComparison.OrdinalIgnoreCase), contents.IndexOf("--- Handled Ping", StringComparison.OrdinalIgnoreCase), contents.IndexOf("-- Finished Request", StringComparison.OrdinalIgnoreCase), contents.IndexOf("- All Done", StringComparison.OrdinalIgnoreCase), contents.IndexOf("- All Done with Ping", StringComparison.OrdinalIgnoreCase), }; var streamOrder = new[] { contents.IndexOf("-- Handling StreamRequest", StringComparison.OrdinalIgnoreCase), contents.IndexOf("--- Handled Sing: Sing, Song", StringComparison.OrdinalIgnoreCase), contents.IndexOf("-- Finished StreamRequest", StringComparison.OrdinalIgnoreCase), }; var results = new RunResults { RequestHandlers = contents.Contains("--- Handled Ping:"), VoidRequestsHandlers = contents.Contains("--- Handled Jing:"), PipelineBehaviors = contents.Contains("-- Handling Request"), RequestPreProcessors = contents.Contains("- Starting Up"), RequestPostProcessors = contents.Contains("- All Done"), ConstrainedGenericBehaviors = contents.Contains("- All Done with Ping") && !failedJing, OrderedPipelineBehaviors = order.SequenceEqual(order.OrderBy(i => i)), NotificationHandler = contents.Contains("Got pinged async"), MultipleNotificationHandlers = contents.Contains("Got pinged async") && contents.Contains("Got pinged also async"), ConstrainedGenericNotificationHandler = contents.Contains("Got pinged constrained async") && !failedPong, CovariantNotificationHandler = contents.Contains("Got notified"), HandlerForSameException = isHandlerForSameExceptionWorks, HandlerForBaseException = isHandlerForBaseExceptionWorks, HandlerForLessSpecificException = isHandlerForLessSpecificExceptionWorks, PreferredHandlerForBaseException = isPreferredHandlerForBaseExceptionWorks, OverriddenHandlerForBaseException = isOverriddenHandlerForBaseExceptionWorks, // Streams StreamRequestHandlers = contents.Contains("--- Handled Sing: Sing, Song") && !failedSing, StreamPipelineBehaviors = contents.Contains("-- Handling StreamRequest"), StreamOrderedPipelineBehaviors = streamOrder.SequenceEqual(streamOrder.OrderBy(i => i)) }; await writer.WriteLineAsync($"Request Handler....................................................{(results.RequestHandlers ? "Y" : "N")}"); await writer.WriteLineAsync($"Void Request Handler...............................................{(results.VoidRequestsHandlers ? "Y" : "N")}"); await writer.WriteLineAsync($"Pipeline Behavior..................................................{(results.PipelineBehaviors ? "Y" : "N")}"); await writer.WriteLineAsync($"Pre-Processor......................................................{(results.RequestPreProcessors ? "Y" : "N")}"); await writer.WriteLineAsync($"Post-Processor.....................................................{(results.RequestPostProcessors ? "Y" : "N")}"); await writer.WriteLineAsync($"Constrained Post-Processor.........................................{(results.ConstrainedGenericBehaviors ? "Y" : "N")}"); await writer.WriteLineAsync($"Ordered Behaviors..................................................{(results.OrderedPipelineBehaviors ? "Y" : "N")}"); await writer.WriteLineAsync($"Notification Handler...............................................{(results.NotificationHandler ? "Y" : "N")}"); await writer.WriteLineAsync($"Notification Handlers..............................................{(results.MultipleNotificationHandlers ? "Y" : "N")}"); await writer.WriteLineAsync($"Constrained Notification Handler...................................{(results.ConstrainedGenericNotificationHandler ? "Y" : "N")}"); await writer.WriteLineAsync($"Covariant Notification Handler.....................................{(results.CovariantNotificationHandler ? "Y" : "N")}"); await writer.WriteLineAsync($"Handler for inherited request with same exception used.............{(results.HandlerForSameException ? "Y" : "N")}"); await writer.WriteLineAsync($"Handler for inherited request with base exception used.............{(results.HandlerForBaseException ? "Y" : "N")}"); await writer.WriteLineAsync($"Handler for request with less specific exception used by priority..{(results.HandlerForLessSpecificException ? "Y" : "N")}"); await writer.WriteLineAsync($"Preferred handler for inherited request with base exception used...{(results.PreferredHandlerForBaseException ? "Y" : "N")}"); await writer.WriteLineAsync($"Overridden handler for inherited request with same exception used..{(results.OverriddenHandlerForBaseException ? "Y" : "N")}"); if (testStreams) { await writer.WriteLineAsync($"Stream Request Handler.............................................{(results.StreamRequestHandlers ? "Y" : "N")}"); await writer.WriteLineAsync($"Stream Pipeline Behavior...........................................{(results.StreamPipelineBehaviors ? "Y" : "N")}"); await writer.WriteLineAsync($"Stream Ordered Behaviors...........................................{(results.StreamOrderedPipelineBehaviors ? "Y" : "N")}"); } await writer.WriteLineAsync(); } private static async Task IsHandlerForSameExceptionWorks(IMediator mediator, WrappingWriter writer) { var isHandledCorrectly = false; await writer.WriteLineAsync("Checking handler to catch exact exception..."); try { await mediator.Send(new PingProtectedResource { Message = "Ping to protected resource" }); isHandledCorrectly = IsExceptionHandledBy(writer); } catch (Exception e) { await writer.WriteLineAsync(e.Message); } await writer.WriteLineAsync(); return isHandledCorrectly; } private static async Task IsHandlerForBaseExceptionWorks(IMediator mediator, WrappingWriter writer) { var isHandledCorrectly = false; await writer.WriteLineAsync("Checking shared handler to catch exception by base type..."); try { await mediator.Send(new PingResource { Message = "Ping to missed resource" }); isHandledCorrectly = IsExceptionHandledBy(writer); } catch (Exception e) { await writer.WriteLineAsync(e.Message); } await writer.WriteLineAsync(); return isHandledCorrectly; } private static async Task IsHandlerForLessSpecificExceptionWorks(IMediator mediator, WrappingWriter writer) { var isHandledCorrectly = false; await writer.WriteLineAsync("Checking base handler to catch any exception..."); try { await mediator.Send(new PingResourceTimeout { Message = "Ping to ISS resource" }); isHandledCorrectly = IsExceptionHandledBy (writer); } catch (Exception e) { await writer.WriteLineAsync(e.Message); } await writer.WriteLineAsync(); return isHandledCorrectly; } private static async Task IsPreferredHandlerForBaseExceptionWorks(IMediator mediator, WrappingWriter writer) { var isHandledCorrectly = false; await writer.WriteLineAsync("Selecting preferred handler to handle exception..."); try { await mediator.Send(new ExceptionHandler.Overrides.PingResourceTimeout { Message = "Ping to ISS resource (preferred)" }); isHandledCorrectly = IsExceptionHandledBy (writer); } catch (Exception e) { await writer.WriteLineAsync(e.Message); } await writer.WriteLineAsync(); return isHandledCorrectly; } private static async Task IsOverriddenHandlerForBaseExceptionWorks(IMediator mediator, WrappingWriter writer) { var isHandledCorrectly = false; await writer.WriteLineAsync("Selecting new handler to handle exception..."); try { await mediator.Send(new PingNewResource { Message = "Ping to ISS resource (override)" }); isHandledCorrectly = IsExceptionHandledBy (writer); } catch (Exception e) { await writer.WriteLineAsync(e.Message); } await writer.WriteLineAsync(); return isHandledCorrectly; } private static bool IsExceptionHandledBy(WrappingWriter writer) where TException : Exception { var messages = writer.Contents.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None).ToList(); if (messages.Count - 3 < 0) return false; // Note: For this handler type to be found in messages, it must be written in messages by LogExceptionAction return messages[messages.Count - 2].Contains(typeof(THandler).FullName) // Note: For this exception type to be found in messages, exception must be written in all tested exception handlers && messages[messages.Count - 3].Contains(typeof(TException).FullName); } } public class RunResults { public bool RequestHandlers { get; set; } public bool VoidRequestsHandlers { get; set; } public bool PipelineBehaviors { get; set; } public bool RequestPreProcessors { get; set; } public bool RequestPostProcessors { get; set; } public bool OrderedPipelineBehaviors { get; set; } public bool ConstrainedGenericBehaviors { get; set; } public bool NotificationHandler { get; set; } public bool MultipleNotificationHandlers { get; set; } public bool CovariantNotificationHandler { get; set; } public bool ConstrainedGenericNotificationHandler { get; set; } public bool HandlerForSameException { get; set; } public bool HandlerForBaseException { get; set; } public bool HandlerForLessSpecificException { get; set; } public bool PreferredHandlerForBaseException { get; set; } public bool OverriddenHandlerForBaseException { get; set; } // Stream results public bool StreamRequestHandlers { get; set; } public bool StreamPipelineBehaviors { get; set; } public bool StreamOrderedPipelineBehaviors { get; set; } } public class WrappingWriter : TextWriter { private readonly TextWriter _innerWriter; private readonly StringBuilder _stringWriter = new StringBuilder(); public WrappingWriter(TextWriter innerWriter) { _innerWriter = innerWriter; } public override void Write(char value) { _stringWriter.Append(value); _innerWriter.Write(value); } public override Task WriteLineAsync(string value) { _stringWriter.AppendLine(value); return _innerWriter.WriteLineAsync(value); } public override Encoding Encoding => _innerWriter.Encoding; public string Contents => _stringWriter.ToString(); } ================================================ FILE: samples/MediatR.Examples/Streams/GenericStreamPipelineBehavior.cs ================================================ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples; public class GenericStreamPipelineBehavior : IStreamPipelineBehavior { private readonly TextWriter _writer; public GenericStreamPipelineBehavior(TextWriter writer) { _writer = writer; } public async IAsyncEnumerable Handle(TRequest request, StreamHandlerDelegate next, [EnumeratorCancellation]CancellationToken cancellationToken) { await _writer.WriteLineAsync("-- Handling StreamRequest"); await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false)) { yield return response; } await _writer.WriteLineAsync("-- Finished StreamRequest"); } } ================================================ FILE: samples/MediatR.Examples/Streams/Sing.cs ================================================ namespace MediatR.Examples; public class Sing : IStreamRequest { public string Message { get; set; } } ================================================ FILE: samples/MediatR.Examples/Streams/SingHandler.cs ================================================ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples; public class SingHandler : IStreamRequestHandler { private readonly TextWriter _writer; public SingHandler(TextWriter writer) { _writer = writer; } public async IAsyncEnumerable Handle(Sing request, [EnumeratorCancellation]CancellationToken cancellationToken) { await _writer.WriteLineAsync($"--- Handled Sing: {request.Message}, Song"); yield return await Task.Run(() => new Song { Message = request.Message + "ing do" }); yield return await Task.Run(() => new Song { Message = request.Message + "ing re" }); yield return await Task.Run(() => new Song { Message = request.Message + "ing mi" }); yield return await Task.Run(() => new Song { Message = request.Message + "ing fa" }); yield return await Task.Run(() => new Song { Message = request.Message + "ing so" }); yield return await Task.Run(() => new Song { Message = request.Message + "ing la" }); yield return await Task.Run(() => new Song { Message = request.Message + "ing ti" }); yield return await Task.Run(() => new Song { Message = request.Message + "ing do" }); } } ================================================ FILE: samples/MediatR.Examples/Streams/Song.cs ================================================ namespace MediatR.Examples; public class Song { public string Message { get; set; } } ================================================ FILE: samples/MediatR.Examples.AspNetCore/MediatR.Examples.AspNetCore.csproj ================================================  net10.0 Exe ================================================ FILE: samples/MediatR.Examples.AspNetCore/Program.cs ================================================ using System; using System.IO; using System.Threading.Tasks; using MediatR.Pipeline; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Examples.AspNetCore; public static class Program { public static Task Main(string[] args) { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "ASP.NET Core DI", testStreams: true); } private static IMediator BuildMediator(WrappingWriter writer) { var services = new ServiceCollection(); services.AddSingleton(writer); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Ping).Assembly, typeof(Sing).Assembly); }); services.AddScoped(typeof(IStreamRequestHandler), typeof(SingHandler)); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(GenericPipelineBehavior<,>)); services.AddScoped(typeof(IRequestPreProcessor<>), typeof(GenericRequestPreProcessor<>)); services.AddScoped(typeof(IRequestPostProcessor<,>), typeof(GenericRequestPostProcessor<,>)); services.AddScoped(typeof(IStreamPipelineBehavior<,>), typeof(GenericStreamPipelineBehavior<,>)); var provider = services.BuildServiceProvider(); return provider.GetRequiredService(); } } ================================================ FILE: samples/MediatR.Examples.Autofac/MediatR.Examples.Autofac.csproj ================================================  net10.0 Exe ================================================ FILE: samples/MediatR.Examples.Autofac/Program.cs ================================================ using Autofac.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Examples.Autofac; using global::Autofac; using MediatR.Pipeline; using System; using System.IO; using System.Reflection; using System.Threading.Tasks; internal static class Program { public static Task Main(string[] args) { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "Autofac", testStreams: true); } private static IMediator BuildMediator(WrappingWriter writer) { var builder = new ContainerBuilder(); builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces(); var mediatrOpenTypes = new[] { typeof(IRequestHandler<,>), typeof(IRequestExceptionHandler<,,>), typeof(IRequestExceptionAction<,>), typeof(INotificationHandler<>), typeof(IStreamRequestHandler<,>) }; foreach (var mediatrOpenType in mediatrOpenTypes) { builder .RegisterAssemblyTypes(typeof(Ping).GetTypeInfo().Assembly) .AsClosedTypesOf(mediatrOpenType) // when having a single class implementing several handler types // this call will cause a handler to be called twice // in general you should try to avoid having a class implementing for instance `IRequestHandler<,>` and `INotificationHandler<>` // the other option would be to remove this call // see also https://github.com/LuckyPennySoftware/MediatR/issues/462 .AsImplementedInterfaces(); } builder.RegisterInstance(writer).As(); // It appears Autofac returns the last registered types first builder.RegisterGeneric(typeof(GenericStreamPipelineBehavior<,>)).As(typeof(IStreamPipelineBehavior<,>)); builder.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); builder.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); builder.RegisterGeneric(typeof(RequestExceptionActionProcessorBehavior<,>)) .As(typeof(IPipelineBehavior<,>)); builder.RegisterGeneric(typeof(RequestExceptionProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); builder.RegisterGeneric(typeof(GenericRequestPreProcessor<>)).As(typeof(IRequestPreProcessor<>)); builder.RegisterGeneric(typeof(GenericRequestPostProcessor<,>)).As(typeof(IRequestPostProcessor<,>)); builder.RegisterGeneric(typeof(GenericPipelineBehavior<,>)).As(typeof(IPipelineBehavior<,>)); builder.RegisterGeneric(typeof(ConstrainedRequestPostProcessor<,>)).As(typeof(IRequestPostProcessor<,>)); builder.RegisterGeneric(typeof(ConstrainedPingedHandler<>)).As(typeof(INotificationHandler<>)); var services = new ServiceCollection(); builder.Populate(services); // The below returns: // - RequestPreProcessorBehavior // - RequestPostProcessorBehavior // - GenericPipelineBehavior // - GenericStreamPipelineBehavior // - RequestExceptionActionProcessorBehavior // - RequestExceptionProcessorBehavior //var behaviors = container // .Resolve>>() // .ToList(); var container = builder.Build(); var serviceProvider = new AutofacServiceProvider(container); var mediator = serviceProvider.GetRequiredService(); return mediator; } } ================================================ FILE: samples/MediatR.Examples.DryIoc/MediatR.Examples.DryIoc.csproj ================================================  net10.0 Exe ================================================ FILE: samples/MediatR.Examples.DryIoc/Program.cs ================================================ using System; using System.IO; using System.Threading.Tasks; using DryIoc; using DryIoc.Microsoft.DependencyInjection; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Examples.DryIoc; class Program { static Task Main() { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "DryIoc"); } private static IMediator BuildMediator(WrappingWriter writer) { var container = new Container(); // Since Mediator has multiple constructors, consider adding rule to allow that // var container = new Container(rules => rules.With(FactoryMethod.ConstructorWithResolvableArguments)) container.Use(writer); //Pipeline works out of the box here container.RegisterMany(new[] { typeof(IMediator).GetAssembly(), typeof(Ping).GetAssembly() }, Registrator.Interfaces); //Without the container having FactoryMethod.ConstructorWithResolvableArguments commented above //You must select the desired constructor container.Register(made: Made.Of(() => new Mediator(Arg.Of()))); var services = new ServiceCollection(); var adapterContainer = container.WithDependencyInjectionAdapter(services); return adapterContainer.GetRequiredService(); } } ================================================ FILE: samples/MediatR.Examples.Lamar/MediatR.Examples.Lamar.csproj ================================================ Exe net10.0 ================================================ FILE: samples/MediatR.Examples.Lamar/Program.cs ================================================ using System; using System.IO; using System.Threading.Tasks; using Lamar; using MediatR.Pipeline; namespace MediatR.Examples.Lamar; class Program { static Task Main(string[] args) { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "Lamar"); } private static IMediator BuildMediator(WrappingWriter writer) { var container = new Container(cfg => { cfg.Scan(scanner => { scanner.AssemblyContainingType(); scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>)); scanner.ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>)); scanner.ConnectImplementationsToTypesClosing(typeof(IRequestExceptionAction<,>)); scanner.ConnectImplementationsToTypesClosing(typeof(IRequestExceptionHandler<,,>)); }); //Pipeline cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestExceptionProcessorBehavior<,>)); cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestExceptionActionProcessorBehavior<,>)); cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPreProcessorBehavior<,>)); cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(RequestPostProcessorBehavior<,>)); cfg.For(typeof(IPipelineBehavior<,>)).Add(typeof(GenericPipelineBehavior<,>)); cfg.For(typeof(IRequestPreProcessor<>)).Add(typeof(GenericRequestPreProcessor<>)); cfg.For(typeof(IRequestPostProcessor<,>)).Add(typeof(GenericRequestPostProcessor<,>)); cfg.For(typeof(IRequestPostProcessor<,>)).Add(typeof(ConstrainedRequestPostProcessor<,>)); //Constrained notification handlers cfg.For(typeof(INotificationHandler<>)).Add(typeof(ConstrainedPingedHandler<>)); // This is the default but let's be explicit. At most we should be container scoped. cfg.For().Use().Transient(); cfg.For().Use(writer); }); var mediator = container.GetInstance(); return mediator; } } ================================================ FILE: samples/MediatR.Examples.LightInject/MediatR.Examples.LightInject.csproj ================================================ net10.0 Exe ================================================ FILE: samples/MediatR.Examples.LightInject/Program.cs ================================================ using System; using System.IO; using System.Reflection; using System.Threading.Tasks; using LightInject; using LightInject.Microsoft.DependencyInjection; using MediatR.Pipeline; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Examples.LightInject; class Program { static Task Main(string[] args) { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "LightInject"); } private static IMediator BuildMediator(WrappingWriter writer) { var serviceContainer = new ServiceContainer(ContainerOptions.Default.WithMicrosoftSettings()); serviceContainer.Register(); serviceContainer.RegisterInstance(writer); serviceContainer.RegisterAssembly(typeof(Ping).GetTypeInfo().Assembly, (serviceType, implementingType) => serviceType.IsConstructedGenericType && ( serviceType.GetGenericTypeDefinition() == typeof(IRequestHandler<,>) || serviceType.GetGenericTypeDefinition() == typeof(INotificationHandler<>) )); serviceContainer.RegisterOrdered(typeof(IPipelineBehavior<,>), new[] { typeof(RequestPreProcessorBehavior<,>), typeof(RequestPostProcessorBehavior<,>), typeof(GenericPipelineBehavior<,>) }, type => null); serviceContainer.RegisterOrdered(typeof(IRequestPostProcessor<,>), new[] { typeof(GenericRequestPostProcessor<,>), typeof(ConstrainedRequestPostProcessor<,>) }, type => null); serviceContainer.Register(typeof(IRequestPreProcessor<>), typeof(GenericRequestPreProcessor<>)); var services = new ServiceCollection(); var provider = serviceContainer.CreateServiceProvider(services); return provider.GetRequiredService(); } } ================================================ FILE: samples/MediatR.Examples.PublishStrategies/AsyncPingedHandler.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.PublishStrategies; public class AsyncPingedHandler : INotificationHandler { public AsyncPingedHandler(string name) { Name = name; } public string Name { get; set; } public async Task Handle(Pinged notification, CancellationToken cancellationToken) { if (Name == "2") { throw new ArgumentException("Name cannot be '2'"); } Console.WriteLine($"[AsyncPingedHandler {Name}] {DateTime.Now:HH:mm:ss.fff} : Pinged"); await Task.Delay(100).ConfigureAwait(false); Console.WriteLine($"[AsyncPingedHandler {Name}] {DateTime.Now:HH:mm:ss.fff} : After pinged"); } } ================================================ FILE: samples/MediatR.Examples.PublishStrategies/CustomMediator.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.PublishStrategies; public class CustomMediator : Mediator { private readonly Func, INotification, CancellationToken, Task> _publish; public CustomMediator(IServiceProvider serviceFactory, Func, INotification, CancellationToken, Task> publish) : base(serviceFactory) => _publish = publish; protected override Task PublishCore(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) => _publish(handlerExecutors, notification, cancellationToken); } ================================================ FILE: samples/MediatR.Examples.PublishStrategies/MediatR.Examples.PublishStrategies.csproj ================================================ Exe net10.0 ================================================ FILE: samples/MediatR.Examples.PublishStrategies/Program.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Examples.PublishStrategies; class Program { static async Task Main(string[] args) { var services = new ServiceCollection(); services.AddSingleton(); services.AddTransient>(sp => new SyncPingedHandler("1")); services.AddTransient>(sp => new AsyncPingedHandler("2")); services.AddTransient>(sp => new AsyncPingedHandler("3")); services.AddTransient>(sp => new SyncPingedHandler("4")); var provider = services.BuildServiceProvider(); var publisher = provider.GetRequiredService(); var pinged = new Pinged(); foreach (PublishStrategy strategy in Enum.GetValues(typeof(PublishStrategy))) { Console.WriteLine($"Strategy: {strategy}"); Console.WriteLine("----------"); try { await publisher.Publish(pinged, strategy); } catch (Exception ex) { Console.WriteLine($"{ex.GetType()}: {ex.Message}"); } await Task.Delay(1000); Console.WriteLine("----------"); } Console.WriteLine("done"); } } ================================================ FILE: samples/MediatR.Examples.PublishStrategies/PublishStrategy.cs ================================================ namespace MediatR.Examples.PublishStrategies; /// /// Strategy to use when publishing notifications /// public enum PublishStrategy { /// /// Run each notification handler after one another. Returns when all handlers are finished. In case of any exception(s), they will be captured in an AggregateException. /// SyncContinueOnException = 0, /// /// Run each notification handler after one another. Returns when all handlers are finished or an exception has been thrown. In case of an exception, any handlers after that will not be run. /// SyncStopOnException = 1, /// /// Run all notification handlers asynchronously. Returns when all handlers are finished. In case of any exception(s), they will be captured in an AggregateException. /// Async = 2, /// /// Run each notification handler on its own thread using Task.Run(). Returns immediately and does not wait for any handlers to finish. Note that you cannot capture any exceptions, even if you await the call to Publish. /// ParallelNoWait = 3, /// /// Run each notification handler on its own thread using Task.Run(). Returns when all threads (handlers) are finished. In case of any exception(s), they are captured in an AggregateException by Task.WhenAll. /// ParallelWhenAll = 4, /// /// Run each notification handler on its own thread using Task.Run(). Returns when any thread (handler) is finished. Note that you cannot capture any exceptions (See msdn documentation of Task.WhenAny) /// ParallelWhenAny = 5, } ================================================ FILE: samples/MediatR.Examples.PublishStrategies/Publisher.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.PublishStrategies; public class Publisher { private readonly IServiceProvider _serviceFactory; public Publisher(IServiceProvider serviceFactory) { _serviceFactory = serviceFactory; PublishStrategies[PublishStrategy.Async] = new CustomMediator(_serviceFactory, AsyncContinueOnException); PublishStrategies[PublishStrategy.ParallelNoWait] = new CustomMediator(_serviceFactory, ParallelNoWait); PublishStrategies[PublishStrategy.ParallelWhenAll] = new CustomMediator(_serviceFactory, ParallelWhenAll); PublishStrategies[PublishStrategy.ParallelWhenAny] = new CustomMediator(_serviceFactory, ParallelWhenAny); PublishStrategies[PublishStrategy.SyncContinueOnException] = new CustomMediator(_serviceFactory, SyncContinueOnException); PublishStrategies[PublishStrategy.SyncStopOnException] = new CustomMediator(_serviceFactory, SyncStopOnException); } public IDictionary PublishStrategies = new Dictionary(); public PublishStrategy DefaultStrategy { get; set; } = PublishStrategy.SyncContinueOnException; public Task Publish(TNotification notification) { return Publish(notification, DefaultStrategy, default(CancellationToken)); } public Task Publish(TNotification notification, PublishStrategy strategy) { return Publish(notification, strategy, default(CancellationToken)); } public Task Publish(TNotification notification, CancellationToken cancellationToken) { return Publish(notification, DefaultStrategy, cancellationToken); } public Task Publish(TNotification notification, PublishStrategy strategy, CancellationToken cancellationToken) { if (!PublishStrategies.TryGetValue(strategy, out var mediator)) { throw new ArgumentException($"Unknown strategy: {strategy}"); } return mediator.Publish(notification, cancellationToken); } private Task ParallelWhenAll(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var tasks = new List(); foreach (var handler in handlers) { tasks.Add(Task.Run(() => handler.HandlerCallback(notification, cancellationToken))); } return Task.WhenAll(tasks); } private Task ParallelWhenAny(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var tasks = new List(); foreach (var handler in handlers) { tasks.Add(Task.Run(() => handler.HandlerCallback(notification, cancellationToken))); } return Task.WhenAny(tasks); } private Task ParallelNoWait(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { foreach (var handler in handlers) { Task.Run(() => handler.HandlerCallback(notification, cancellationToken)); } return Task.CompletedTask; } private async Task AsyncContinueOnException(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var tasks = new List(); var exceptions = new List(); foreach (var handler in handlers) { try { tasks.Add(handler.HandlerCallback(notification, cancellationToken)); } catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException)) { exceptions.Add(ex); } } try { await Task.WhenAll(tasks).ConfigureAwait(false); } catch (AggregateException ex) { exceptions.AddRange(ex.Flatten().InnerExceptions); } catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException)) { exceptions.Add(ex); } if (exceptions.Any()) { throw new AggregateException(exceptions); } } private async Task SyncStopOnException(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { foreach (var handler in handlers) { await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); } } private async Task SyncContinueOnException(IEnumerable handlers, INotification notification, CancellationToken cancellationToken) { var exceptions = new List(); foreach (var handler in handlers) { try { await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); } catch (AggregateException ex) { exceptions.AddRange(ex.Flatten().InnerExceptions); } catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException)) { exceptions.Add(ex); } } if (exceptions.Any()) { throw new AggregateException(exceptions); } } } ================================================ FILE: samples/MediatR.Examples.PublishStrategies/SyncPingedHandler.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace MediatR.Examples.PublishStrategies; public class SyncPingedHandler : INotificationHandler { public SyncPingedHandler(string name) { Name = name; } public string Name { get; set; } public Task Handle(Pinged notification, CancellationToken cancellationToken) { if (Name == "2") { throw new ArgumentException("Name cannot be '2'"); } Console.WriteLine($"[SyncPingedHandler {Name}] {DateTime.Now:HH:mm:ss.fff} : Pinged"); Thread.Sleep(100); Console.WriteLine($"[SyncPingedHandler {Name}] {DateTime.Now:HH:mm:ss.fff} : After pinged"); return Task.CompletedTask; } } ================================================ FILE: samples/MediatR.Examples.SimpleInjector/MediatR.Examples.SimpleInjector.csproj ================================================ net10.0 Exe ================================================ FILE: samples/MediatR.Examples.SimpleInjector/Program.cs ================================================ using System.IO; using System.Threading.Tasks; using MediatR.Pipeline; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Examples.SimpleInjector; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using global::SimpleInjector; internal static class Program { private static Task Main(string[] args) { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "SimpleInjector", true); } private static IMediator BuildMediator(WrappingWriter writer) { var container = new Container(); var services = new ServiceCollection(); services .AddSimpleInjector(container); var assemblies = GetAssemblies().ToArray(); container.RegisterSingleton(); container.Register(typeof(IRequestHandler<,>), assemblies); RegisterHandlers(container, typeof(INotificationHandler<>), assemblies); RegisterHandlers(container, typeof(IRequestExceptionAction<,>), assemblies); RegisterHandlers(container, typeof(IRequestExceptionHandler<,,>), assemblies); RegisterHandlers(container, typeof(IStreamRequestHandler<,>), assemblies); container.Register(() => (TextWriter) writer, Lifestyle.Singleton); //Pipeline container.Collection.Register(typeof(IPipelineBehavior<,>), new[] { typeof(RequestExceptionProcessorBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>), typeof(RequestPreProcessorBehavior<,>), typeof(RequestPostProcessorBehavior<,>), typeof(GenericPipelineBehavior<,>) }); container.Collection.Register(typeof(IRequestPreProcessor<>), new[] { typeof(GenericRequestPreProcessor<>) }); container.Collection.Register(typeof(IRequestPostProcessor<,>), new[] { typeof(GenericRequestPostProcessor<,>), typeof(ConstrainedRequestPostProcessor<,>) }); container.Collection.Register(typeof(IStreamPipelineBehavior<,>), new[] { typeof(GenericStreamPipelineBehavior<,>) }); var serviceProvider = services.BuildServiceProvider().UseSimpleInjector(container); container.RegisterInstance(container); var mediator = container.GetRequiredService(); return mediator; } private static void RegisterHandlers(Container container, Type collectionType, Assembly[] assemblies) { // we have to do this because by default, generic type definitions (such as the Constrained Notification Handler) won't be registered var handlerTypes = container.GetTypesToRegister(collectionType, assemblies, new TypesToRegisterOptions { IncludeGenericTypeDefinitions = true, IncludeComposites = false, }); container.Collection.Register(collectionType, handlerTypes); } private static IEnumerable GetAssemblies() { yield return typeof(IMediator).GetTypeInfo().Assembly; yield return typeof(Ping).GetTypeInfo().Assembly; } } ================================================ FILE: samples/MediatR.Examples.Stashbox/MediatR.Examples.Stashbox.csproj ================================================  Exe net10.0 ================================================ FILE: samples/MediatR.Examples.Stashbox/Program.cs ================================================ using Stashbox; using Stashbox.Configuration; using System; using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Examples.Stashbox; class Program { static Task Main() { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "Stashbox", testStreams: true); } private static IMediator BuildMediator(WrappingWriter writer) { var container = new StashboxContainer() .RegisterInstance(writer) .RegisterAssemblies(new[] { typeof(Mediator).Assembly, typeof(Ping).Assembly }, serviceTypeSelector: Rules.ServiceRegistrationFilters.Interfaces, registerSelf: false); return container.GetRequiredService(); } } ================================================ FILE: samples/MediatR.Examples.Windsor/ContravariantFilter.cs ================================================ namespace MediatR.Examples.Windsor; using System; using System.Linq; using System.Reflection; using Castle.MicroKernel; public class ContravariantFilter : IHandlersFilter { public bool HasOpinionAbout(Type service) { if (!service.IsGenericType) return false; var genericType = service.GetGenericTypeDefinition(); var genericArguments = genericType.GetGenericArguments(); return genericArguments.Count() == 1 && genericArguments.Single().GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant); } public IHandler[] SelectHandlers(Type service, IHandler[] handlers) { return handlers; } } ================================================ FILE: samples/MediatR.Examples.Windsor/MediatR.Examples.Windsor.csproj ================================================ net6.0 Exe ================================================ FILE: samples/MediatR.Examples.Windsor/Program.cs ================================================ using System.Linq; using System.Threading.Tasks; using Castle.MicroKernel.Resolvers.SpecializedResolvers; using MediatR.Pipeline; namespace MediatR.Examples.Windsor; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using Castle.MicroKernel; using Castle.MicroKernel.Registration; using Castle.Windsor; internal class Program { private static Task Main(string[] args) { var writer = new WrappingWriter(Console.Out); var mediator = BuildMediator(writer); return Runner.Run(mediator, writer, "Castle.Windsor", true); } private static IMediator BuildMediator(WrappingWriter writer) { var container = new WindsorContainer(); container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel)); container.Kernel.AddHandlersFilter(new ContravariantFilter()); // *** The default lifestyle for Windsor is Singleton // *** If you are using ASP.net, it's better to register your services with 'Per Web Request LifeStyle'. var fromAssemblyContainingPing = Classes.FromAssemblyContaining(); container.Register(fromAssemblyContainingPing.BasedOn(typeof(IRequestHandler<,>)).WithServiceAllInterfaces().AllowMultipleMatches()); container.Register(fromAssemblyContainingPing.BasedOn(typeof(INotificationHandler<>)).WithServiceAllInterfaces().AllowMultipleMatches()); container.Register(Component.For(typeof(IPipelineBehavior<,>)).ImplementedBy(typeof(RequestExceptionProcessorBehavior<,>))); container.Register(Component.For(typeof(IPipelineBehavior<,>)).ImplementedBy(typeof(RequestExceptionActionProcessorBehavior<,>))); container.Register(fromAssemblyContainingPing.BasedOn(typeof(IRequestExceptionAction<,>)).WithServiceAllInterfaces().AllowMultipleMatches()); container.Register(fromAssemblyContainingPing.BasedOn(typeof(IRequestExceptionHandler<,,>)).WithServiceAllInterfaces().AllowMultipleMatches()); container.Register(fromAssemblyContainingPing.BasedOn(typeof(IStreamRequestHandler<,>)).WithServiceAllInterfaces().AllowMultipleMatches()); container.Register(fromAssemblyContainingPing.BasedOn(typeof(IRequestPreProcessor<>)).WithServiceAllInterfaces().AllowMultipleMatches()); container.Register(fromAssemblyContainingPing.BasedOn(typeof(IRequestPostProcessor<,>)).WithServiceAllInterfaces().AllowMultipleMatches()); container.Register(Component.For().ImplementedBy()); container.Register(Component.For().Instance(writer)); //container.Register(Component.For().UsingFactoryMethod(k => (type => //{ // var enumerableType = type // .GetInterfaces() // .Concat(new[] { type }) // .FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); // var service = enumerableType?.GetGenericArguments()?[0]; // var resolvedType = enumerableType != null ? k.ResolveAll(service) : k.Resolve(type); // var genericArguments = service?.GetGenericArguments(); // // Handle exceptions even using the base request types for IRequestExceptionHandler<,,> // var isRequestExceptionHandler = service?.GetGenericTypeDefinition() // ?.IsAssignableTo(typeof(IRequestExceptionHandler<,,>)) ?? false; // if (isRequestExceptionHandler) // return ResolveRequestExceptionHandler(k, type, service, resolvedType, genericArguments); // // Handle exceptions even using the base request types for IRequestExceptionAction<,> // var isRequestExceptionAction = service?.GetGenericTypeDefinition() // ?.IsAssignableTo(typeof(IRequestExceptionAction<,>)) ?? false; // if (isRequestExceptionAction) // return ResolveRequestExceptionAction(k, type, service, resolvedType, genericArguments); // return resolvedType; //}))); //Pipeline container.Register(Component.For(typeof(IStreamPipelineBehavior<,>)).ImplementedBy(typeof(GenericStreamPipelineBehavior<,>))); container.Register(Component.For(typeof(IPipelineBehavior<,>)).ImplementedBy(typeof(RequestPreProcessorBehavior<,>)).NamedAutomatically("PreProcessorBehavior")); container.Register(Component.For(typeof(IPipelineBehavior<,>)).ImplementedBy(typeof(RequestPostProcessorBehavior<,>)).NamedAutomatically("PostProcessorBehavior")); container.Register(Component.For(typeof(IPipelineBehavior<,>)).ImplementedBy(typeof(GenericPipelineBehavior<,>)).NamedAutomatically("Pipeline")); container.Register(Component.For(typeof(IRequestPreProcessor<>)).ImplementedBy(typeof(GenericRequestPreProcessor<>)).NamedAutomatically("PreProcessor")); container.Register(Component.For(typeof(IRequestPostProcessor<,>)).ImplementedBy(typeof(GenericRequestPostProcessor<,>)).NamedAutomatically("PostProcessor")); container.Register(Component.For(typeof(IRequestPostProcessor<,>), typeof(ConstrainedRequestPostProcessor<,>)).NamedAutomatically("ConstrainedRequestPostProcessor")); container.Register(Component.For(typeof(INotificationHandler<>), typeof(ConstrainedPingedHandler<>)).NamedAutomatically("ConstrainedPingedHandler")); var mediator = container.Resolve(); return mediator; } private static object ResolveRequestExceptionHandler(IKernel k, Type type, Type service, object resolvedType, Type[] genericArguments) { if (service == null || genericArguments == null || !service.IsInterface || !service.IsGenericType || !service.IsConstructedGenericType || !(service.GetGenericTypeDefinition() ?.IsAssignableTo(typeof(IRequestExceptionHandler<,,>)) ?? false) || genericArguments.Length != 3) { return resolvedType; } var serviceFactory = k.Resolve(); var baseRequestType = genericArguments[0].BaseType; var response = genericArguments[1]; var exceptionType = genericArguments[2]; // Check if the base request type is valid if (baseRequestType == null || !baseRequestType.IsClass || baseRequestType == typeof(object) || ((!baseRequestType.GetInterfaces() ?.Any(i => i.IsAssignableFrom(typeof(IRequest<>)))) ?? true)) { return resolvedType; } var exceptionHandlerInterfaceType = typeof(IRequestExceptionHandler<,,>).MakeGenericType(baseRequestType, response, exceptionType); var enumerableExceptionHandlerInterfaceType = typeof(IEnumerable<>).MakeGenericType(exceptionHandlerInterfaceType); Array resultArray = CreateArraysOutOfResolvedTypeAndEnumerableInterfaceTypes(type, resolvedType, serviceFactory, enumerableExceptionHandlerInterfaceType); return resultArray; } private static object ResolveRequestExceptionAction(IKernel k, Type type, Type service, object resolvedType, Type[] genericArguments) { if (service == null || genericArguments == null || !service.IsInterface || !service.IsGenericType || !service.IsConstructedGenericType || !(service.GetGenericTypeDefinition() ?.IsAssignableTo(typeof(IRequestExceptionAction<,>)) ?? false) || genericArguments.Length != 2) { return resolvedType; } var serviceFactory = k.Resolve(); var baseRequestType = genericArguments[0].BaseType; var exceptionType = genericArguments[1]; // Check if the base request type is valid if (baseRequestType == null || !baseRequestType.IsClass || baseRequestType == typeof(object) || ((!baseRequestType.GetInterfaces() ?.Any(i => i.IsAssignableFrom(typeof(IRequest<>)))) ?? true)) { return resolvedType; } var exceptionHandlerInterfaceType = typeof(IRequestExceptionAction<,>).MakeGenericType(baseRequestType, exceptionType); var enumerableExceptionHandlerInterfaceType = typeof(IEnumerable<>).MakeGenericType(exceptionHandlerInterfaceType); Array resultArray = CreateArraysOutOfResolvedTypeAndEnumerableInterfaceTypes(type, resolvedType, serviceFactory, enumerableExceptionHandlerInterfaceType); return resultArray; } private static Array CreateArraysOutOfResolvedTypeAndEnumerableInterfaceTypes(Type type, object resolvedType, ServiceFactory serviceFactory, Type enumerableExceptionHandlerInterfaceType) { var firstArray = serviceFactory.Invoke(enumerableExceptionHandlerInterfaceType) as Array; Debug.Assert(firstArray != null, $"Array '{nameof(firstArray)}' should not be null because this method calls ResolveAll when a {typeof(IEnumerable<>).FullName} " + $"is passed as argument in argument named '{nameof(type)}'"); var secondArray = resolvedType is Array ? resolvedType as Array : new[] { resolvedType }; Debug.Assert(secondArray != null, $"Array '{nameof(secondArray)}' should not be null because '{nameof(resolvedType)}' is an array or created as an array"); var resultArray = Array.CreateInstance(typeof(object), firstArray.Length + secondArray.Length); Array.Copy(firstArray, resultArray, firstArray.Length); Array.Copy(secondArray, 0, resultArray, firstArray.Length, secondArray.Length); return resultArray; } } ================================================ FILE: src/MediatR/Entities/OpenBehavior.cs ================================================ using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; namespace MediatR.Entities; /// /// Represents a registration entity for pipeline behaviors with a specified service lifetime. /// public class OpenBehavior { /// /// Initializes a new instance of the class. /// /// The type of the pipeline behavior to register. /// The lifetime of the registered service. Defaults to Transient. /// Thrown if the specified type does not implement IPipelineBehavior. /// Thrown if is null. public OpenBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { ValidatePipelineBehaviorType(openBehaviorType); OpenBehaviorType = openBehaviorType; ServiceLifetime = serviceLifetime; } /// /// The type of the open behavior. /// public Type OpenBehaviorType { get; } /// /// The service lifetime of the open behavior. /// public ServiceLifetime ServiceLifetime { get; } /// /// Validates whether the specified type implements the interface. /// /// The type to validate. /// Thrown if the type does not implement . /// Thrown if is null. private static void ValidatePipelineBehaviorType(Type openBehaviorType) { if (openBehaviorType == null) throw new ArgumentNullException($"Open behavior type can not be null."); bool isPipelineBehavior = openBehaviorType.GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPipelineBehavior<,>)); if (!isPipelineBehavior) { throw new InvalidOperationException($"The type \"{openBehaviorType.Name}\" must implement IPipelineBehavior<,> interface."); } } } ================================================ FILE: src/MediatR/IMediator.cs ================================================ namespace MediatR; /// /// Defines a mediator to encapsulate request/response and publishing interaction patterns /// public interface IMediator : ISender, IPublisher { } ================================================ FILE: src/MediatR/INotificationHandler.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace MediatR; /// /// Defines a handler for a notification /// /// The type of notification being handled public interface INotificationHandler where TNotification : INotification { /// /// Handles a notification /// /// The notification /// Cancellation token Task Handle(TNotification notification, CancellationToken cancellationToken); } /// /// Wrapper class for a synchronous notification handler /// /// The notification type public abstract class NotificationHandler : INotificationHandler where TNotification : INotification { Task INotificationHandler.Handle(TNotification notification, CancellationToken cancellationToken) { Handle(notification); return Task.CompletedTask; } /// /// Override in a derived class for the handler logic /// /// Notification protected abstract void Handle(TNotification notification); } ================================================ FILE: src/MediatR/INotificationPublisher.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; using System.Threading; namespace MediatR; public interface INotificationPublisher { Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/IPipelineBehavior.cs ================================================ namespace MediatR; using System.Threading; using System.Threading.Tasks; /// /// Represents an async continuation for the next task to execute in the pipeline /// /// Response type /// Awaitable task returning a public delegate Task RequestHandlerDelegate(CancellationToken t = default); /// /// Pipeline behavior to surround the inner handler. /// Implementations add additional behavior and await the next delegate. /// /// Request type /// Response type public interface IPipelineBehavior where TRequest : notnull { /// /// Pipeline handler. Perform any additional behavior and await the delegate as necessary /// /// Incoming request /// Awaitable delegate for the next action in the pipeline. Eventually this delegate represents the handler. /// Cancellation token /// Awaitable task returning the Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/IPublisher.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace MediatR; /// /// Publish a notification or event through the mediator pipeline to be handled by multiple handlers. /// public interface IPublisher { /// /// Asynchronously send a notification to multiple handlers /// /// Notification object /// Optional cancellation token /// A task that represents the publish operation. Task Publish(object notification, CancellationToken cancellationToken = default); /// /// Asynchronously send a notification to multiple handlers /// /// Notification object /// Optional cancellation token /// A task that represents the publish operation. Task Publish(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification; } ================================================ FILE: src/MediatR/IRequestHandler.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace MediatR; /// /// Defines a handler for a request /// /// The type of request being handled /// The type of response from the handler public interface IRequestHandler where TRequest : IRequest { /// /// Handles a request /// /// The request /// Cancellation token /// Response from the request Task Handle(TRequest request, CancellationToken cancellationToken); } /// /// Defines a handler for a request with a void response. /// /// The type of request being handled public interface IRequestHandler where TRequest : IRequest { /// /// Handles a request /// /// The request /// Cancellation token /// Response from the request Task Handle(TRequest request, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/ISender.cs ================================================ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MediatR; /// /// Send a request through the mediator pipeline to be handled by a single handler. /// public interface ISender { /// /// Asynchronously send a request to a single handler /// /// Response type /// Request object /// Optional cancellation token /// A task that represents the send operation. The task result contains the handler response Task Send(IRequest request, CancellationToken cancellationToken = default); /// /// Asynchronously send a request to a single handler with no response /// /// Request object /// Optional cancellation token /// A task that represents the send operation. Task Send(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest; /// /// Asynchronously send an object request to a single handler via dynamic dispatch /// /// Request object /// Optional cancellation token /// A task that represents the send operation. The task result contains the type erased handler response Task Send(object request, CancellationToken cancellationToken = default); /// /// Create a stream via a single stream handler /// /// /// /// /// IAsyncEnumerable CreateStream(IStreamRequest request, CancellationToken cancellationToken = default); /// /// Create a stream via an object request to a stream handler /// /// /// /// IAsyncEnumerable CreateStream(object request, CancellationToken cancellationToken = default); } ================================================ FILE: src/MediatR/IStreamPipelineBehavior.cs ================================================  namespace MediatR; using System.Collections.Generic; using System.Threading; /// /// Represents an async enumerable continuation for the next task to execute in the pipeline /// /// Response type /// Async Enumerable returning a public delegate IAsyncEnumerable StreamHandlerDelegate(); /// /// Stream Pipeline behavior to surround the inner handler. /// Implementations add additional behavior and await the next delegate. /// /// Request type /// Response type public interface IStreamPipelineBehavior where TRequest : notnull { /// /// Stream Pipeline handler. Perform any additional behavior and iterate the delegate as necessary /// /// Incoming request /// Awaitable delegate for the next action in the pipeline. Eventually this delegate represents the handler. /// Cancellation token /// Awaitable task returning the IAsyncEnumerable Handle(TRequest request, StreamHandlerDelegate next, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/IStreamRequestHandler.cs ================================================ using System.Collections.Generic; using System.Threading; namespace MediatR; /// /// Defines a handler for a stream request using IAsyncEnumerable as return type. /// /// The type of request being handled /// The type of response from the handler public interface IStreamRequestHandler where TRequest : IStreamRequest { /// /// Handles a stream request with IAsyncEnumerable as return type. /// /// The request /// Cancellation token /// Response from the request IAsyncEnumerable Handle(TRequest request, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/Internal/HandlersOrderer.cs ================================================ namespace MediatR.Internal; using System; using System.Collections.Generic; using System.Linq; internal static class HandlersOrderer { public static IList Prioritize(IList handlers, TRequest request) where TRequest : notnull { if (handlers.Count < 2) { return handlers; } var requestObjectDetails = new ObjectDetails(request); var handlerObjectsDetails = handlers.Select(static s => new ObjectDetails(s)).ToList(); var uniqueHandlers = RemoveOverridden(handlerObjectsDetails).ToArray(); Array.Sort(uniqueHandlers, requestObjectDetails); return uniqueHandlers.Select(static s => s.Value).ToList(); } private static IEnumerable RemoveOverridden(IList handlersData) { for (var i = 0; i < handlersData.Count - 1; i++) { for (var j = i + 1; j < handlersData.Count; j++) { if (handlersData[i].IsOverridden || handlersData[j].IsOverridden) { continue; } if (handlersData[i].Type.IsAssignableFrom(handlersData[j].Type)) { handlersData[i].IsOverridden = true; } else if (handlersData[j].Type.IsAssignableFrom(handlersData[i].Type)) { handlersData[j].IsOverridden = true; } } } return handlersData.Where(static w => !w.IsOverridden); } } ================================================ FILE: src/MediatR/Internal/ObjectDetails.cs ================================================ using System; using System.Collections.Generic; namespace MediatR.Internal; internal class ObjectDetails : IComparer { public string Name { get; } public string? AssemblyName { get; } public string? Location { get; } public object Value { get; } public Type Type { get; } public bool IsOverridden { get; set; } public ObjectDetails(object value) { Value = value; Type = Value.GetType(); var exceptionHandlerType = value.GetType(); Name = exceptionHandlerType.Name; AssemblyName = exceptionHandlerType.Assembly.GetName().Name; Location = exceptionHandlerType.Namespace?.Replace($"{AssemblyName}.", string.Empty); } public int Compare(ObjectDetails? x, ObjectDetails? y) { if (x == null) { return 1; } if (y == null) { return -1; } return CompareByAssembly(x, y) ?? CompareByNamespace(x, y) ?? CompareByLocation(x, y); } /// /// Compare two objects according to current assembly /// /// First object to compare /// Second object to compare /// /// An object has a higher priority if it belongs to the current assembly and the other is not; /// If none of the objects belong to the current assembly, they can be considered equal; /// If both objects belong to the current assembly, they can't be compared only by this criterion. /// private int? CompareByAssembly(ObjectDetails x, ObjectDetails y) { if (x.AssemblyName == AssemblyName && y.AssemblyName != AssemblyName) { return -1; } if (x.AssemblyName != AssemblyName && y.AssemblyName == AssemblyName) { return 1; } if (x.AssemblyName != AssemblyName && y.AssemblyName != AssemblyName) { return 0; } return null; } /// /// Compare two objects according to current namespace /// /// First object to compare /// Second object to compare /// /// An object has a higher priority if it belongs to the current/child namespace and the other is not; /// If both objects belong to the current/child namespace, they can be considered equal; /// If none of the objects belong to the current/child namespace, they can't be compared by this criterion. /// private int? CompareByNamespace(ObjectDetails x, ObjectDetails y) { if (Location is null || x.Location is null || y.Location is null) { return 0; } if (x.Location.StartsWith(Location, StringComparison.Ordinal) && !y.Location.StartsWith(Location, StringComparison.Ordinal)) { return -1; } if (!x.Location.StartsWith(Location, StringComparison.Ordinal) && y.Location.StartsWith(Location, StringComparison.Ordinal)) { return 1; } if (x.Location.StartsWith(Location, StringComparison.Ordinal) && y.Location.StartsWith(Location, StringComparison.Ordinal)) { return 0; } return null; } /// /// Compare two objects according to location in the assembly /// /// First object to compare /// Second object to compare /// /// An object has a higher priority if it location is part of the current location and the other is not; /// If both objects are part of the current location, the closest has higher priority; /// If none of the objects are part of the current location, they can be considered equal. /// private int CompareByLocation(ObjectDetails x, ObjectDetails y) { if (Location is null || x.Location is null || y.Location is null) { return 0; } if (Location.StartsWith(x.Location, StringComparison.Ordinal) && !Location.StartsWith(y.Location, StringComparison.Ordinal)) { return -1; } if (!Location.StartsWith(x.Location, StringComparison.Ordinal) && Location.StartsWith(y.Location, StringComparison.Ordinal)) { return 1; } if (x.Location.Length > y.Location.Length) { return -1; } if (x.Location.Length < y.Location.Length) { return 1; } return 0; } } ================================================ FILE: src/MediatR/Licensing/BuildInfo.cs ================================================ using System; using System.Linq; using System.Reflection; namespace MediatR.Licensing; internal static class BuildInfo { public static DateTimeOffset? BuildDate { get; } = GetBuildDate(); private static DateTimeOffset? GetBuildDate() { var assembly = typeof(BuildInfo).Assembly; var buildDateAttribute = assembly .GetCustomAttributes() .FirstOrDefault(a => a.Key == "BuildDateUtc"); if (buildDateAttribute?.Value != null && DateTimeOffset.TryParse(buildDateAttribute.Value, out var buildDate)) { return buildDate; } return null; } } ================================================ FILE: src/MediatR/Licensing/Edition.cs ================================================ namespace MediatR.Licensing; internal enum Edition { Community = 0, Standard = 1, Professional = 2, Enterprise = 3 } ================================================ FILE: src/MediatR/Licensing/License.cs ================================================ using System; using System.Diagnostics.CodeAnalysis; using System.Security.Claims; namespace MediatR.Licensing; internal class License { internal License(params Claim[] claims) : this(new ClaimsPrincipal(new ClaimsIdentity(claims))) { } public License(ClaimsPrincipal claims) { if (Guid.TryParse(claims.FindFirst("account_id")?.Value, out var accountId)) { AccountId = accountId; } CustomerId = claims.FindFirst("customer_id")?.Value; SubscriptionId = claims.FindFirst("sub_id")?.Value; if (long.TryParse(claims.FindFirst("iat")?.Value, out var iat)) { var startedAt = DateTimeOffset.FromUnixTimeSeconds(iat); StartDate = startedAt; } if (long.TryParse(claims.FindFirst("exp")?.Value, out var exp)) { var expiredAt = DateTimeOffset.FromUnixTimeSeconds(exp); ExpirationDate = expiredAt; } if (Enum.TryParse(claims.FindFirst("edition")?.Value, out var edition)) { Edition = edition; } if (Enum.TryParse(claims.FindFirst("type")?.Value, out var productType)) { ProductType = productType; } var perpetualValue = claims.FindFirst("perpetual")?.Value; IsPerpetual = perpetualValue?.ToLowerInvariant() is "true" or "1"; IsConfigured = AccountId != null && CustomerId != null && SubscriptionId != null && StartDate != null && ExpirationDate != null && Edition != null && ProductType != null; } public Guid? AccountId { get; } public string? CustomerId { get; } public string? SubscriptionId { get; } public DateTimeOffset? StartDate { get; } public DateTimeOffset? ExpirationDate { get; } public Edition? Edition { get; } public ProductType? ProductType { get; } public bool IsPerpetual { get; } public bool IsConfigured { get; } } ================================================ FILE: src/MediatR/Licensing/LicenseAccessor.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using System.Security.Claims; using System.Security.Cryptography; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using Convert = System.Convert; namespace MediatR.Licensing; internal class LicenseAccessor { private readonly MediatRServiceConfiguration? _configuration; private readonly ILogger _logger; public LicenseAccessor(MediatRServiceConfiguration configuration, ILoggerFactory loggerFactory) { _configuration = configuration; _logger = loggerFactory.CreateLogger("LuckyPennySoftware.MediatR.License"); } public LicenseAccessor(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger("LuckyPennySoftware.MediatR.License"); } private License? _license; private readonly object _lock = new(); public License Current => _license ??= Initialize(); private License Initialize() { lock (_lock) { if (_license != null) { return _license; } var key = _configuration?.LicenseKey ?? Mediator.LicenseKey ?? null; if (key == null) { return new License(); } var licenseClaims = ValidateKey(key); return licenseClaims.Any() ? new License(new ClaimsPrincipal(new ClaimsIdentity(licenseClaims))) : new License(); } } private Claim[] ValidateKey(string licenseKey) { var handler = new JsonWebTokenHandler(); var rsa = new RSAParameters { Exponent = Convert.FromBase64String("AQAB"), Modulus = Convert.FromBase64String( "2LTtdJV2b0mYoRqChRCfcqnbpKvsiCcDYwJ+qPtvQXWXozOhGo02/V0SWMFBdbZHUzpEytIiEcojo7Vbq5mQmt4lg92auyPKsWq6qSmCVZCUuL/kpYqLCit4yUC0YqZfw4H9zLf1yAIOgyXQf1x6g+kscDo1pWAniSl9a9l/LXRVEnGz+OfeUrN/5gzpracGUY6phx6T09UCRuzi4YqqO4VJzL877W0jCW2Q7jMzHxOK04VSjNc22CADuCd34mrFs23R0vVm1DVLYtPGD76/rGOcxO6vmRc7ydBAvt1IoUsrY0vQ2rahp51YPxqqhKPd8nNOomHWblCCA7YUeV3C1Q==") };; var key = new RsaSecurityKey(rsa) { KeyId = "LuckyPennySoftwareLicenseKey/bbb13acb59904d89b4cb1c85f088ccf9" }; var parms = new TokenValidationParameters { ValidIssuer = "https://luckypennysoftware.com", ValidAudience = "LuckyPennySoftware", IssuerSigningKey = key, ValidateLifetime = false }; var validateResult = Task.Run(() => handler.ValidateTokenAsync(licenseKey, parms)).GetAwaiter().GetResult(); if (!validateResult.IsValid) { _logger.LogCritical(validateResult.Exception, "Error validating the Lucky Penny software license key"); } return validateResult.ClaimsIdentity?.Claims.ToArray() ?? Array.Empty(); } } ================================================ FILE: src/MediatR/Licensing/LicenseValidator.cs ================================================ using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; namespace MediatR.Licensing; internal class LicenseValidator { private readonly ILogger _logger; private readonly DateTimeOffset? _buildDate; public LicenseValidator(ILoggerFactory loggerFactory) : this(loggerFactory, BuildInfo.BuildDate) { } public LicenseValidator(ILoggerFactory loggerFactory, DateTimeOffset? buildDate) { _logger = loggerFactory.CreateLogger("LuckyPennySoftware.MediatR.License"); _buildDate = buildDate; } public void Validate(License license) { var errors = new List(); if (license is not { IsConfigured: true }) { var message = "You do not have a valid license key for the Lucky Penny software MediatR. " + "This is allowed for development and testing scenarios. " + "If you are running in production you are required to have a licensed version. " + "Please visit https://luckypennysoftware.com to obtain a valid license."; _logger.LogWarning(message); return; } _logger.LogDebug("The Lucky Penny license key details: {license}", license); var diff = DateTime.UtcNow.Date.Subtract(license.ExpirationDate!.Value.Date).TotalDays; if (diff > 0) { // If perpetual, check if build date is before expiration if (license.IsPerpetual && _buildDate.HasValue) { var buildDateDiff = _buildDate.Value.Date.Subtract(license.ExpirationDate.Value.Date).TotalDays; if (buildDateDiff <= 0) { _logger.LogInformation( "Your license for the Lucky Penny software MediatR expired {expiredDaysAgo} days ago, but perpetual licensing is active because the build date ({buildDate:O}) is before the license expiration date ({licenseExpiration:O}).", diff, _buildDate, license.ExpirationDate); // Don't add to errors - perpetual fallback applies } else { errors.Add($"Your license for the Lucky Penny software MediatR expired {diff} days ago."); } } else { if (license.IsPerpetual) { _logger.LogWarning( "Your license for the Lucky Penny software MediatR has perpetual licensing enabled, but the build date could not be determined. Perpetual licensing cannot be applied. Please ensure the assembly metadata is correctly embedded at build time."); } errors.Add($"Your license for the Lucky Penny software MediatR expired {diff} days ago."); } } if (license.ProductType!.Value != ProductType.MediatR && license.ProductType.Value != ProductType.Bundle) { errors.Add("Your Lucky Penny software license does not include MediatR."); } if (errors.Count > 0) { foreach (var err in errors) { _logger.LogError(err); } _logger.LogError( "Please visit https://luckypennysoftware.com to obtain a valid license for the Lucky Penny software MediatR."); } else { _logger.LogInformation("You have a valid license key for the Lucky Penny software {type} {edition} edition. The license expires on {licenseExpiration}.", license.ProductType, license.Edition, license.ExpirationDate); } } } ================================================ FILE: src/MediatR/Licensing/ProductType.cs ================================================ namespace MediatR.Licensing; internal enum ProductType { AutoMapper = 0, MediatR = 1, Bundle = 2 } ================================================ FILE: src/MediatR/MediatR.csproj ================================================  Jimmy Bogard Simple, unambitious mediator implementation in .NET Copyright Jimmy Bogard netstandard2.0;net8.0;net9.0;net10.0 enable strict mediator;request;response;queries;commands;notifications true ..\..\MediatR.snk true gradient_128x128.png README.md True LICENSE.md https://mediatr.io v true true snupkg true true true $(TargetFrameworks);net462 $([System.DateTime]::UtcNow.ToString("O")) <_Parameter1>MediatR.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010091986edd141861f402457659cb82b56cf6a0d60b3bd2e5aa4ea73d88afa929d278462d6c4c0e2ecbce21948c15514a310a82e6b2e6beaab6cb14230a03bc026609be59f938423f2490fa0033ae87a982fb4950db77d1a4635e14f7727161e93e5511de766ed8e515efd801464b7820a27fca30a32161485824e442cc5ffecfbe all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: src/MediatR/Mediator.cs ================================================ using MediatR.NotificationPublishers; using Microsoft.Extensions.DependencyInjection; namespace MediatR; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Wrappers; /// /// Default mediator implementation relying on single- and multi instance delegates for resolving handlers. /// public class Mediator : IMediator { private readonly IServiceProvider _serviceProvider; private readonly INotificationPublisher _publisher; private static readonly ConcurrentDictionary _requestHandlers = new(); private static readonly ConcurrentDictionary _notificationHandlers = new(); private static readonly ConcurrentDictionary _streamRequestHandlers = new(); /// /// Initializes a new instance of the class. /// /// Service provider. Can be a scoped or root provider public Mediator(IServiceProvider serviceProvider) : this(serviceProvider, new ForeachAwaitPublisher()) { } /// /// Initializes a new instance of the class. /// /// Service provider. Can be a scoped or root provider /// Notification publisher. Defaults to . public Mediator(IServiceProvider serviceProvider, INotificationPublisher publisher) { _serviceProvider = serviceProvider; _publisher = publisher; _serviceProvider.CheckLicense(); } /// /// Gets or sets the license key. You can find your license key in your account. /// public static string? LicenseKey { get; set; } public Task Send(IRequest request, CancellationToken cancellationToken = default) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var handler = (RequestHandlerWrapper)_requestHandlers.GetOrAdd(request.GetType(), static requestType => { var wrapperType = typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse)); var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper type for {requestType}"); return (RequestHandlerBase)wrapper; }); return handler.Handle(request, _serviceProvider, cancellationToken); } public Task Send(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest { if (request == null) { throw new ArgumentNullException(nameof(request)); } var handler = (RequestHandlerWrapper)_requestHandlers.GetOrAdd(request.GetType(), static requestType => { var wrapperType = typeof(RequestHandlerWrapperImpl<>).MakeGenericType(requestType); var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper type for {requestType}"); return (RequestHandlerBase)wrapper; }); return handler.Handle(request, _serviceProvider, cancellationToken); } public Task Send(object request, CancellationToken cancellationToken = default) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var handler = _requestHandlers.GetOrAdd(request.GetType(), static requestType => { Type wrapperType; var requestInterfaceType = requestType.GetInterfaces().FirstOrDefault(static i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>)); if (requestInterfaceType is null) { requestInterfaceType = requestType.GetInterfaces().FirstOrDefault(static i => i == typeof(IRequest)); if (requestInterfaceType is null) { throw new ArgumentException($"{requestType.Name} does not implement {nameof(IRequest)}", nameof(request)); } wrapperType = typeof(RequestHandlerWrapperImpl<>).MakeGenericType(requestType); } else { var responseType = requestInterfaceType.GetGenericArguments()[0]; wrapperType = typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, responseType); } var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper for type {requestType}"); return (RequestHandlerBase)wrapper; }); // call via dynamic dispatch to avoid calling through reflection for performance reasons return handler.Handle(request, _serviceProvider, cancellationToken); } public Task Publish(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { if (notification == null) { throw new ArgumentNullException(nameof(notification)); } return PublishNotification(notification, cancellationToken); } public Task Publish(object notification, CancellationToken cancellationToken = default) => notification switch { null => throw new ArgumentNullException(nameof(notification)), INotification instance => PublishNotification(instance, cancellationToken), _ => throw new ArgumentException($"{nameof(notification)} does not implement ${nameof(INotification)}") }; /// /// Override in a derived class to control how the tasks are awaited. By default the implementation calls the . /// /// Enumerable of tasks representing invoking each notification handler /// The notification being published /// The cancellation token /// A task representing invoking all handlers protected virtual Task PublishCore(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) => _publisher.Publish(handlerExecutors, notification, cancellationToken); private Task PublishNotification(INotification notification, CancellationToken cancellationToken = default) { var handler = _notificationHandlers.GetOrAdd(notification.GetType(), static notificationType => { var wrapperType = typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType); var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper for type {notificationType}"); return (NotificationHandlerWrapper)wrapper; }); return handler.Handle(notification, _serviceProvider, PublishCore, cancellationToken); } public IAsyncEnumerable CreateStream(IStreamRequest request, CancellationToken cancellationToken = default) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var streamHandler = (StreamRequestHandlerWrapper)_streamRequestHandlers.GetOrAdd(request.GetType(), static requestType => { var wrapperType = typeof(StreamRequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse)); var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper for type {requestType}"); return (StreamRequestHandlerBase)wrapper; }); var items = streamHandler.Handle(request, _serviceProvider, cancellationToken); return items; } public IAsyncEnumerable CreateStream(object request, CancellationToken cancellationToken = default) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var handler = _streamRequestHandlers.GetOrAdd(request.GetType(), static requestType => { var requestInterfaceType = requestType.GetInterfaces().FirstOrDefault(static i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IStreamRequest<>)); if (requestInterfaceType is null) { throw new ArgumentException($"{requestType.Name} does not implement IStreamRequest", nameof(request)); } var responseType = requestInterfaceType.GetGenericArguments()[0]; var wrapperType = typeof(StreamRequestHandlerWrapperImpl<,>).MakeGenericType(requestType, responseType); var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper for type {requestType}"); return (StreamRequestHandlerBase)wrapper; }); var items = handler.Handle(request, _serviceProvider, cancellationToken); return items; } } ================================================ FILE: src/MediatR/MicrosoftExtensionsDI/MediatRServiceCollectionExtensions.cs ================================================ using System; using System.Linq; using MediatR; using MediatR.Licensing; using MediatR.Pipeline; using MediatR.Registration; using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.DependencyInjection; /// /// Extensions to scan for MediatR handlers and registers them. /// - Scans for any handler interface implementations and registers them as /// - Scans for any and implementations and registers them as transient instances /// Registers as a transient instance /// After calling AddMediatR you can use the container to resolve an instance. /// This does not scan for any instances including and . /// To register behaviors, use the with the open generic or closed generic types. /// public static class MediatRServiceCollectionExtensions { /// /// Registers handlers and mediator types from the specified assemblies /// /// Service collection /// The action used to configure the options /// Service collection public static IServiceCollection AddMediatR(this IServiceCollection services, Action configuration) { var serviceConfig = new MediatRServiceConfiguration(); configuration.Invoke(serviceConfig); return services.AddMediatR(serviceConfig); } /// /// Registers handlers and mediator types from the specified assemblies /// /// Service collection /// Configuration options /// Service collection public static IServiceCollection AddMediatR(this IServiceCollection services, MediatRServiceConfiguration configuration) { if (!configuration.AssembliesToRegister.Any()) { throw new ArgumentException("No assemblies found to scan. Supply at least one assembly to scan for handlers."); } ServiceRegistrar.SetGenericRequestHandlerRegistrationLimitations(configuration); ServiceRegistrar.AddMediatRClassesWithTimeout(services, configuration); ServiceRegistrar.AddRequiredServices(services, configuration); return services; } internal static void CheckLicense(this IServiceProvider serviceProvider) { if (LicenseChecked == false) { var licenseAccessor = serviceProvider.GetRequiredService(); var licenseValidator = serviceProvider.GetRequiredService(); var license = licenseAccessor.Current; licenseValidator.Validate(license); } LicenseChecked = true; } internal static bool LicenseChecked { get; set; } } ================================================ FILE: src/MediatR/MicrosoftExtensionsDI/MediatrServiceConfiguration.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using MediatR; using MediatR.Entities; using MediatR.NotificationPublishers; using MediatR.Pipeline; using MediatR.Registration; namespace Microsoft.Extensions.DependencyInjection; public class MediatRServiceConfiguration { /// /// Optional filter for types to register. Default value is a function returning true. /// public Func TypeEvaluator { get; set; } = t => true; /// /// Mediator implementation type to register. Default is /// public Type MediatorImplementationType { get; set; } = typeof(Mediator); /// /// Strategy for publishing notifications. Defaults to /// public INotificationPublisher NotificationPublisher { get; set; } = new ForeachAwaitPublisher(); /// /// Type of notification publisher strategy to register. If set, overrides /// public Type? NotificationPublisherType { get; set; } /// /// Service lifetime to register services under. Default value is /// public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient; /// /// Request exception action processor strategy. Default value is /// public RequestExceptionActionProcessorStrategy RequestExceptionActionProcessorStrategy { get; set; } = RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions; internal List AssembliesToRegister { get; } = new(); /// /// List of behaviors to register in specific order /// public List BehaviorsToRegister { get; } = new(); /// /// List of stream behaviors to register in specific order /// public List StreamBehaviorsToRegister { get; } = new(); /// /// List of request pre processors to register in specific order /// public List RequestPreProcessorsToRegister { get; } = new(); /// /// List of request post processors to register in specific order /// public List RequestPostProcessorsToRegister { get; } = new(); /// /// Automatically register processors during assembly scanning /// public bool AutoRegisterRequestProcessors { get; set; } /// /// Configure the maximum number of type parameters that a generic request handler can have. To Disable this constraint, set the value to 0. /// public int MaxGenericTypeParameters { get; set; } = 10; /// /// Configure the maximum number of types that can close a generic request type parameter constraint. To Disable this constraint, set the value to 0. /// public int MaxTypesClosing { get; set; } = 100; /// /// Configure the Maximum Amount of Generic RequestHandler Types MediatR will try to register. To Disable this constraint, set the value to 0. /// public int MaxGenericTypeRegistrations { get; set; } = 125000; /// /// Configure the Timeout in Milliseconds that the GenericHandler Registration Process will exit with error. To Disable this constraint, set the value to 0. /// public int RegistrationTimeout { get; set; } = 15000; /// /// Flag that controlls whether MediatR will attempt to register handlers that containg generic type parameters. /// public bool RegisterGenericHandlers { get; set; } = false; /// /// Gets or sets the license key. You can find your license key in your account. /// public string? LicenseKey { get; set; } /// /// Register various handlers from assembly containing given type /// /// Type from assembly to scan /// This public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining() => RegisterServicesFromAssemblyContaining(typeof(T)); /// /// Register various handlers from assembly containing given type /// /// Type from assembly to scan /// This public MediatRServiceConfiguration RegisterServicesFromAssemblyContaining(Type type) => RegisterServicesFromAssembly(type.Assembly); /// /// Register various handlers from assembly /// /// Assembly to scan /// This public MediatRServiceConfiguration RegisterServicesFromAssembly(Assembly assembly) { AssembliesToRegister.Add(assembly); return this; } /// /// Register various handlers from assemblies /// /// Assemblies to scan /// This public MediatRServiceConfiguration RegisterServicesFromAssemblies( params Assembly[] assemblies) { AssembliesToRegister.AddRange(assemblies); return this; } /// /// Register a closed behavior type /// /// Closed behavior interface type /// Closed behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddBehavior(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); /// /// Register a closed behavior type against all implementations /// /// Closed behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { return AddBehavior(typeof(TImplementationType), serviceLifetime); } /// /// Register a closed behavior type against all implementations /// /// Closed behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddBehavior(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IPipelineBehavior<,>)).ToList(); if (implementedGenericInterfaces.Count == 0) { throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IPipelineBehavior<,>).FullName}"); } foreach (var implementedBehaviorType in implementedGenericInterfaces) { BehaviorsToRegister.Add(new ServiceDescriptor(implementedBehaviorType, implementationType, serviceLifetime)); } return this; } /// /// Register a closed behavior type /// /// Closed behavior interface type /// Closed behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddBehavior(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { BehaviorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); return this; } /// /// Registers an open behavior type against the open generic interface type /// /// An open generic behavior type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddOpenBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { if (!openBehaviorType.IsGenericType) { throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); } var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IPipelineBehavior<,>))); if (implementedOpenBehaviorInterfaces.Count == 0) { throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IPipelineBehavior<,>).FullName}"); } foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) { BehaviorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); } return this; } /// /// Registers multiple open behavior types against the open generic interface type /// /// An open generic behavior type list includes multiple open generic behavior types. /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddOpenBehaviors(IEnumerable openBehaviorTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { foreach (var openBehaviorType in openBehaviorTypes) { AddOpenBehavior(openBehaviorType, serviceLifetime); } return this; } /// /// Registers open behaviors against the open generic interface type /// /// An open generic behavior list includes multiple open generic behaviors. /// This public MediatRServiceConfiguration AddOpenBehaviors(IEnumerable openBehaviors) { foreach (var openBehavior in openBehaviors) { AddOpenBehavior(openBehavior.OpenBehaviorType!, openBehavior.ServiceLifetime); } return this; } /// /// Register a closed stream behavior type /// /// Closed stream behavior interface type /// Closed stream behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddStreamBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddStreamBehavior(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); /// /// Register a closed stream behavior type /// /// Closed stream behavior interface type /// Closed stream behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddStreamBehavior(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { StreamBehaviorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); return this; } /// /// Register a closed stream behavior type against all implementations /// /// Closed stream behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddStreamBehavior(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddStreamBehavior(typeof(TImplementationType), serviceLifetime); /// /// Register a closed stream behavior type against all implementations /// /// Closed stream behavior implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddStreamBehavior(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IStreamPipelineBehavior<,>)).ToList(); if (implementedGenericInterfaces.Count == 0) { throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IStreamPipelineBehavior<,>).FullName}"); } foreach (var implementedBehaviorType in implementedGenericInterfaces) { StreamBehaviorsToRegister.Add(new ServiceDescriptor(implementedBehaviorType, implementationType, serviceLifetime)); } return this; } /// /// Registers an open stream behavior type against the open generic interface type /// /// An open generic stream behavior type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddOpenStreamBehavior(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { if (!openBehaviorType.IsGenericType) { throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); } var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IStreamPipelineBehavior<,>))); if (implementedOpenBehaviorInterfaces.Count == 0) { throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IStreamPipelineBehavior<,>).FullName}"); } foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) { StreamBehaviorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); } return this; } /// /// Register a closed request pre processor type /// /// Closed request pre processor interface type /// Closed request pre processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPreProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddRequestPreProcessor(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); /// /// Register a closed request pre processor type /// /// Closed request pre processor interface type /// Closed request pre processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPreProcessor(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { RequestPreProcessorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); return this; } /// /// Register a closed request pre processor type against all implementations /// /// Closed request pre processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPreProcessor( ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddRequestPreProcessor(typeof(TImplementationType), serviceLifetime); /// /// Register a closed request pre processor type against all implementations /// /// Closed request pre processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPreProcessor(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IRequestPreProcessor<>)).ToList(); if (implementedGenericInterfaces.Count == 0) { throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IRequestPreProcessor<>).FullName}"); } foreach (var implementedPreProcessorType in implementedGenericInterfaces) { RequestPreProcessorsToRegister.Add(new ServiceDescriptor(implementedPreProcessorType, implementationType, serviceLifetime)); } return this; } /// /// Registers an open request pre processor type against the open generic interface type /// /// An open generic request pre processor type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddOpenRequestPreProcessor(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { if (!openBehaviorType.IsGenericType) { throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); } var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IRequestPreProcessor<>))); if (implementedOpenBehaviorInterfaces.Count == 0) { throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IRequestPreProcessor<>).FullName}"); } foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) { RequestPreProcessorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); } return this; } /// /// Register a closed request post processor type /// /// Closed request post processor interface type /// Closed request post processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPostProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddRequestPostProcessor(typeof(TServiceType), typeof(TImplementationType), serviceLifetime); /// /// Register a closed request post processor type /// /// Closed request post processor interface type /// Closed request post processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPostProcessor(Type serviceType, Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { RequestPostProcessorsToRegister.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); return this; } /// /// Register a closed request post processor type against all implementations /// /// Closed request post processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPostProcessor(ServiceLifetime serviceLifetime = ServiceLifetime.Transient) => AddRequestPostProcessor(typeof(TImplementationType), serviceLifetime); /// /// Register a closed request post processor type against all implementations /// /// Closed request post processor implementation type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddRequestPostProcessor(Type implementationType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { var implementedGenericInterfaces = implementationType.FindInterfacesThatClose(typeof(IRequestPostProcessor<,>)).ToList(); if (implementedGenericInterfaces.Count == 0) { throw new InvalidOperationException($"{implementationType.Name} must implement {typeof(IRequestPostProcessor<,>).FullName}"); } foreach (var implementedPostProcessorType in implementedGenericInterfaces) { RequestPostProcessorsToRegister.Add(new ServiceDescriptor(implementedPostProcessorType, implementationType, serviceLifetime)); } return this; } /// /// Registers an open request post processor type against the open generic interface type /// /// An open generic request post processor type /// Optional service lifetime, defaults to . /// This public MediatRServiceConfiguration AddOpenRequestPostProcessor(Type openBehaviorType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) { if (!openBehaviorType.IsGenericType) { throw new InvalidOperationException($"{openBehaviorType.Name} must be generic"); } var implementedGenericInterfaces = openBehaviorType.GetInterfaces().Where(i => i.IsGenericType).Select(i => i.GetGenericTypeDefinition()); var implementedOpenBehaviorInterfaces = new HashSet(implementedGenericInterfaces.Where(i => i == typeof(IRequestPostProcessor<,>))); if (implementedOpenBehaviorInterfaces.Count == 0) { throw new InvalidOperationException($"{openBehaviorType.Name} must implement {typeof(IRequestPostProcessor<,>).FullName}"); } foreach (var openBehaviorInterface in implementedOpenBehaviorInterfaces) { RequestPostProcessorsToRegister.Add(new ServiceDescriptor(openBehaviorInterface, openBehaviorType, serviceLifetime)); } return this; } } ================================================ FILE: src/MediatR/MicrosoftExtensionsDI/RequestExceptionActionProcessorStrategy.cs ================================================ namespace Microsoft.Extensions.DependencyInjection; public enum RequestExceptionActionProcessorStrategy { ApplyForUnhandledExceptions, ApplyForAllExceptions } ================================================ FILE: src/MediatR/NotificationHandlerExecutor.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; namespace MediatR; public record NotificationHandlerExecutor(object HandlerInstance, Func HandlerCallback); ================================================ FILE: src/MediatR/NotificationPublishers/ForeachAwaitPublisher.cs ================================================ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MediatR.NotificationPublishers; /// /// Awaits each notification handler in a single foreach loop: /// /// foreach (var handler in handlers) { /// await handler(notification, cancellationToken); /// } /// /// public class ForeachAwaitPublisher : INotificationPublisher { public async Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) { foreach (var handler in handlerExecutors) { await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); } } } ================================================ FILE: src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediatR.NotificationPublishers; /// /// Uses Task.WhenAll with the list of Handler tasks: /// /// var tasks = handlers /// .Select(handler => handler.Handle(notification, cancellationToken)) /// .ToList(); /// /// return Task.WhenAll(tasks); /// /// public class TaskWhenAllPublisher : INotificationPublisher { public Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) { var tasks = handlerExecutors .Select(handler => handler.HandlerCallback(notification, cancellationToken)) .ToArray(); return Task.WhenAll(tasks); } } ================================================ FILE: src/MediatR/Pipeline/IRequestExceptionAction.cs ================================================ namespace MediatR.Pipeline; using System; using System.Threading; using System.Threading.Tasks; /// /// Defines an exception action for a request /// /// Request type /// Exception type public interface IRequestExceptionAction where TRequest : notnull where TException : Exception { /// /// Called when the request handler throws an exception /// /// Request instance /// The thrown exception /// Cancellation token /// An awaitable task Task Execute(TRequest request, TException exception, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/Pipeline/IRequestExceptionHandler.cs ================================================ namespace MediatR.Pipeline; using System; using System.Threading; using System.Threading.Tasks; /// /// Defines an exception handler for a request and response /// /// Request type /// Response type /// Exception type public interface IRequestExceptionHandler where TRequest : notnull where TException : Exception { /// /// Called when the request handler throws an exception /// /// Request instance /// The thrown exception /// The current state of handling the exception /// Cancellation token /// An awaitable task Task Handle(TRequest request, TException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/Pipeline/IRequestPostProcessor.cs ================================================ namespace MediatR.Pipeline; using System.Threading; using System.Threading.Tasks; /// /// Defines a request post-processor for a request /// /// Request type /// Response type public interface IRequestPostProcessor where TRequest : notnull { /// /// Process method executes after the Handle method on your handler /// /// Request instance /// Response instance /// Cancellation token /// An awaitable task Task Process(TRequest request, TResponse response, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/Pipeline/IRequestPreProcessor.cs ================================================ namespace MediatR.Pipeline; using System.Threading; using System.Threading.Tasks; /// /// Defined a request pre-processor for a handler /// /// Request type public interface IRequestPreProcessor where TRequest : notnull { /// /// Process method executes before calling the Handle method on your handler /// /// Incoming request /// Cancellation token /// An awaitable task Task Process(TRequest request, CancellationToken cancellationToken); } ================================================ FILE: src/MediatR/Pipeline/RequestExceptionActionProcessorBehavior.cs ================================================ namespace MediatR.Pipeline; using Internal; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; /// /// Behavior for executing all instances /// after an exception is thrown by the following pipeline steps /// /// Request type /// Response type public class RequestExceptionActionProcessorBehavior : IPipelineBehavior where TRequest : notnull { private readonly IServiceProvider _serviceProvider; public RequestExceptionActionProcessorBehavior(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { try { return await next(cancellationToken).ConfigureAwait(false); } catch (Exception exception) { var exceptionTypes = GetExceptionTypes(exception.GetType()); var actionsForException = exceptionTypes .SelectMany(exceptionType => GetActionsForException(exceptionType, request)) .GroupBy(static actionForException => actionForException.Action.GetType()) .Select(static actionForException => actionForException.First()) .Select(static actionForException => (MethodInfo: GetMethodInfoForAction(actionForException.ExceptionType), actionForException.Action)) .ToList(); foreach (var actionForException in actionsForException) { try { await ((Task)(actionForException.MethodInfo.Invoke(actionForException.Action, new object[] { request, exception, cancellationToken }) ?? throw new InvalidOperationException($"Could not create task for action method {actionForException.MethodInfo}."))).ConfigureAwait(false); } catch (TargetInvocationException invocationException) when (invocationException.InnerException != null) { // Unwrap invocation exception to throw the actual error ExceptionDispatchInfo.Capture(invocationException.InnerException).Throw(); } } throw; } } private static IEnumerable GetExceptionTypes(Type? exceptionType) { while (exceptionType != null && exceptionType != typeof(object)) { yield return exceptionType; exceptionType = exceptionType.BaseType; } } private IEnumerable<(Type ExceptionType, object Action)> GetActionsForException(Type exceptionType, TRequest request) { var exceptionActionInterfaceType = typeof(IRequestExceptionAction<,>).MakeGenericType(typeof(TRequest), exceptionType); var enumerableExceptionActionInterfaceType = typeof(IEnumerable<>).MakeGenericType(exceptionActionInterfaceType); var actionsForException = (IEnumerable)_serviceProvider.GetRequiredService(enumerableExceptionActionInterfaceType); return HandlersOrderer.Prioritize(actionsForException.ToList(), request) .Select(action => (exceptionType, action)); } private static MethodInfo GetMethodInfoForAction(Type exceptionType) { var exceptionActionInterfaceType = typeof(IRequestExceptionAction<,>).MakeGenericType(typeof(TRequest), exceptionType); var actionMethodInfo = exceptionActionInterfaceType.GetMethod(nameof(IRequestExceptionAction.Execute)) ?? throw new InvalidOperationException( $"Could not find method {nameof(IRequestExceptionAction.Execute)} on type {exceptionActionInterfaceType}"); return actionMethodInfo; } } ================================================ FILE: src/MediatR/Pipeline/RequestExceptionHandlerState.cs ================================================ namespace MediatR.Pipeline; /// /// Represents the result of handling an exception thrown by a request handler /// /// Response type public class RequestExceptionHandlerState { /// /// Indicates whether the current exception has been handled and the response should be returned. /// public bool Handled { get; private set; } /// /// The response that is returned if is true. /// public TResponse? Response { get; private set; } /// /// Call to indicate whether the current exception should be considered handled and the specified response should be returned. /// /// Set the response that will be returned. public void SetHandled(TResponse response) { Handled = true; Response = response; } } ================================================ FILE: src/MediatR/Pipeline/RequestExceptionProcessorBehavior.cs ================================================ namespace MediatR.Pipeline; using Internal; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; /// /// Behavior for executing all instances /// after an exception is thrown by the following pipeline steps /// /// Request type /// Response type public class RequestExceptionProcessorBehavior : IPipelineBehavior where TRequest : notnull { private readonly IServiceProvider _serviceProvider; public RequestExceptionProcessorBehavior(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { try { return await next(cancellationToken).ConfigureAwait(false); } catch (Exception exception) { var state = new RequestExceptionHandlerState(); var exceptionTypes = GetExceptionTypes(exception.GetType()); var handlersForException = exceptionTypes .SelectMany(exceptionType => GetHandlersForException(exceptionType, request)) .GroupBy(static handlerForException => handlerForException.Handler.GetType()) .Select(static handlerForException => handlerForException.First()) .Select(static handlerForException => (MethodInfo: GetMethodInfoForHandler(handlerForException.ExceptionType), handlerForException.Handler)) .ToList(); foreach (var handlerForException in handlersForException) { try { await ((Task) (handlerForException.MethodInfo.Invoke(handlerForException.Handler, new object[] { request, exception, state, cancellationToken }) ?? throw new InvalidOperationException("Did not return a Task from the exception handler."))).ConfigureAwait(false); } catch (TargetInvocationException invocationException) when (invocationException.InnerException != null) { // Unwrap invocation exception to throw the actual error ExceptionDispatchInfo.Capture(invocationException.InnerException).Throw(); } if (state.Handled) { break; } } if (!state.Handled) { throw; } if (state.Response is null) { throw; } return state.Response; //cannot be null if Handled } } private static IEnumerable GetExceptionTypes(Type? exceptionType) { while (exceptionType != null && exceptionType != typeof(object)) { yield return exceptionType; exceptionType = exceptionType.BaseType; } } private IEnumerable<(Type ExceptionType, object Handler)> GetHandlersForException(Type exceptionType, TRequest request) { var exceptionHandlerInterfaceType = typeof(IRequestExceptionHandler<,,>).MakeGenericType(typeof(TRequest), typeof(TResponse), exceptionType); var enumerableExceptionHandlerInterfaceType = typeof(IEnumerable<>).MakeGenericType(exceptionHandlerInterfaceType); var exceptionHandlers = (IEnumerable) _serviceProvider.GetRequiredService(enumerableExceptionHandlerInterfaceType); return HandlersOrderer.Prioritize(exceptionHandlers.ToList(), request) .Select(handler => (exceptionType, action: handler)); } private static MethodInfo GetMethodInfoForHandler(Type exceptionType) { var exceptionHandlerInterfaceType = typeof(IRequestExceptionHandler<,,>).MakeGenericType(typeof(TRequest), typeof(TResponse), exceptionType); var handleMethodInfo = exceptionHandlerInterfaceType.GetMethod(nameof(IRequestExceptionHandler.Handle)) ?? throw new InvalidOperationException($"Could not find method {nameof(IRequestExceptionHandler.Handle)} on type {exceptionHandlerInterfaceType}"); return handleMethodInfo; } } ================================================ FILE: src/MediatR/Pipeline/RequestPostProcessorBehavior.cs ================================================ namespace MediatR.Pipeline; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; /// /// Behavior for executing all instances after handling the request /// /// Request type /// Response type public class RequestPostProcessorBehavior : IPipelineBehavior where TRequest : notnull { private readonly IEnumerable> _postProcessors; public RequestPostProcessorBehavior(IEnumerable> postProcessors) => _postProcessors = postProcessors; public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { var response = await next(cancellationToken).ConfigureAwait(false); foreach (var processor in _postProcessors) { await processor.Process(request, response, cancellationToken).ConfigureAwait(false); } return response; } } ================================================ FILE: src/MediatR/Pipeline/RequestPreProcessorBehavior.cs ================================================ namespace MediatR.Pipeline; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; /// /// Behavior for executing all instances before handling a request /// /// /// public class RequestPreProcessorBehavior : IPipelineBehavior where TRequest : notnull { private readonly IEnumerable> _preProcessors; public RequestPreProcessorBehavior(IEnumerable> preProcessors) => _preProcessors = preProcessors; public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { foreach (var processor in _preProcessors) { await processor.Process(request, cancellationToken).ConfigureAwait(false); } return await next(cancellationToken).ConfigureAwait(false); } } ================================================ FILE: src/MediatR/Registration/ServiceRegistrar.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using MediatR.Licensing; using MediatR.Pipeline; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; namespace MediatR.Registration; public static class ServiceRegistrar { private static int MaxGenericTypeParameters; private static int MaxTypesClosing; private static int MaxGenericTypeRegistrations; private static int RegistrationTimeout; public static void SetGenericRequestHandlerRegistrationLimitations(MediatRServiceConfiguration configuration) { MaxGenericTypeParameters = configuration.MaxGenericTypeParameters; MaxTypesClosing = configuration.MaxTypesClosing; MaxGenericTypeRegistrations = configuration.MaxGenericTypeRegistrations; RegistrationTimeout = configuration.RegistrationTimeout; } public static void AddMediatRClassesWithTimeout(IServiceCollection services, MediatRServiceConfiguration configuration) { using(var cts = new CancellationTokenSource(RegistrationTimeout)) { try { AddMediatRClasses(services, configuration, cts.Token); } catch (OperationCanceledException) { throw new TimeoutException("The generic handler registration process timed out."); } } } public static void AddMediatRClasses(IServiceCollection services, MediatRServiceConfiguration configuration, CancellationToken cancellationToken = default) { var assembliesToScan = configuration.AssembliesToRegister.Distinct().ToArray(); ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>), services, assembliesToScan, false, configuration, cancellationToken); ConnectImplementationsToTypesClosing(typeof(IRequestHandler<>), services, assembliesToScan, false, configuration, cancellationToken); ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>), services, assembliesToScan, true, configuration); ConnectImplementationsToTypesClosing(typeof(IStreamRequestHandler<,>), services, assembliesToScan, false, configuration); ConnectImplementationsToTypesClosing(typeof(IRequestExceptionHandler<,,>), services, assembliesToScan, true, configuration); ConnectImplementationsToTypesClosing(typeof(IRequestExceptionAction<,>), services, assembliesToScan, true, configuration); if (configuration.AutoRegisterRequestProcessors) { ConnectImplementationsToTypesClosing(typeof(IRequestPreProcessor<>), services, assembliesToScan, true, configuration); ConnectImplementationsToTypesClosing(typeof(IRequestPostProcessor<,>), services, assembliesToScan, true, configuration); } var multiOpenInterfaces = new List { typeof(INotificationHandler<>), typeof(IRequestExceptionHandler<,,>), typeof(IRequestExceptionAction<,>) }; if (configuration.AutoRegisterRequestProcessors) { multiOpenInterfaces.Add(typeof(IRequestPreProcessor<>)); multiOpenInterfaces.Add(typeof(IRequestPostProcessor<,>)); } foreach (var multiOpenInterface in multiOpenInterfaces) { var arity = multiOpenInterface.GetGenericArguments().Length; var concretions = assembliesToScan .SelectMany(a => a.GetLoadableDefinedTypes()) .Where(type => type.FindInterfacesThatClose(multiOpenInterface).Any()) .Where(type => type.IsConcrete() && type.IsOpenGeneric()) .Where(type => type.GetGenericArguments().Length == arity) .Where(configuration.TypeEvaluator) .ToList(); foreach (var type in concretions) { services.AddTransient(multiOpenInterface, type); } } } private static void ConnectImplementationsToTypesClosing(Type openRequestInterface, IServiceCollection services, IEnumerable assembliesToScan, bool addIfAlreadyExists, MediatRServiceConfiguration configuration, CancellationToken cancellationToken = default) { var concretions = new List(); var interfaces = new List(); var genericConcretions = new List(); var genericInterfaces = new List(); var types = assembliesToScan .SelectMany(a => a.GetLoadableDefinedTypes()) .Where(t => !t.ContainsGenericParameters || configuration.RegisterGenericHandlers) .Where(t => t.IsConcrete() && t.FindInterfacesThatClose(openRequestInterface).Any()) .Where(configuration.TypeEvaluator) .ToList(); foreach (var type in types) { var interfaceTypes = type.FindInterfacesThatClose(openRequestInterface).ToArray(); if (!type.IsOpenGeneric()) { concretions.Add(type); foreach (var interfaceType in interfaceTypes) { interfaces.Fill(interfaceType); } } else { genericConcretions.Add(type); foreach (var interfaceType in interfaceTypes) { genericInterfaces.Fill(interfaceType); } } } foreach (var @interface in interfaces) { var exactMatches = concretions.Where(x => x.CanBeCastTo(@interface)).ToList(); if (addIfAlreadyExists) { foreach (var type in exactMatches) { services.AddTransient(@interface, type); } } else { if (exactMatches.Count > 1) { exactMatches.RemoveAll(m => !IsMatchingWithInterface(m, @interface)); } foreach (var type in exactMatches) { services.TryAddTransient(@interface, type); } } if (!@interface.IsOpenGeneric()) { AddConcretionsThatCouldBeClosed(@interface, concretions, services); } } foreach (var @interface in genericInterfaces) { var exactMatches = genericConcretions.Where(x => x.CanBeCastTo(@interface)).ToList(); AddAllConcretionsThatClose(@interface, exactMatches, services, assembliesToScan, cancellationToken); } } private static bool IsMatchingWithInterface(Type? handlerType, Type handlerInterface) { if (handlerType == null || handlerInterface == null) { return false; } if (handlerType.IsInterface) { if (handlerType.GenericTypeArguments.SequenceEqual(handlerInterface.GenericTypeArguments)) { return true; } } else { return IsMatchingWithInterface(handlerType.GetInterface(handlerInterface.Name), handlerInterface); } return false; } private static void AddConcretionsThatCouldBeClosed(Type @interface, List concretions, IServiceCollection services) { foreach (var type in concretions .Where(x => x.IsOpenGeneric() && x.CouldCloseTo(@interface))) { try { services.TryAddTransient(@interface, type.MakeGenericType(@interface.GenericTypeArguments)); } catch (Exception) { } } } private static (Type Service, Type Implementation) GetConcreteRegistrationTypes(Type openRequestHandlerInterface, Type concreteGenericTRequest, Type openRequestHandlerImplementation) { var closingTypes = concreteGenericTRequest.GetGenericArguments(); var concreteTResponse = concreteGenericTRequest.GetInterfaces() .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequest<>)) ?.GetGenericArguments() .FirstOrDefault(); var typeDefinition = openRequestHandlerInterface.GetGenericTypeDefinition(); var serviceType = concreteTResponse != null ? typeDefinition.MakeGenericType(concreteGenericTRequest, concreteTResponse) : typeDefinition.MakeGenericType(concreteGenericTRequest); return (serviceType, openRequestHandlerImplementation.MakeGenericType(closingTypes)); } private static List? GetConcreteRequestTypes(Type openRequestHandlerInterface, Type openRequestHandlerImplementation, IEnumerable assembliesToScan, CancellationToken cancellationToken) { //request generic type constraints var constraintsForEachParameter = openRequestHandlerImplementation .GetGenericArguments() .Select(x => x.GetGenericParameterConstraints()) .ToList(); var typesThatCanCloseForEachParameter = constraintsForEachParameter .Select(constraints => assembliesToScan .SelectMany(assembly => assembly.GetLoadableDefinedTypes()) .Where(type => type.IsClass && !type.IsAbstract && constraints.All(constraint => constraint.IsAssignableFrom(type))).ToList() ).ToList(); var requestType = openRequestHandlerInterface.GenericTypeArguments.First(); if (requestType.IsGenericParameter) return null; var requestGenericTypeDefinition = requestType.GetGenericTypeDefinition(); var combinations = GenerateCombinations(requestType, typesThatCanCloseForEachParameter, 0, cancellationToken); return combinations.Select(types => requestGenericTypeDefinition.MakeGenericType(types.ToArray())).ToList(); } // Method to generate combinations recursively public static List> GenerateCombinations(Type requestType, List> lists, int depth = 0, CancellationToken cancellationToken = default) { if (depth == 0) { // Initial checks if (MaxGenericTypeParameters > 0 && lists.Count > MaxGenericTypeParameters) throw new ArgumentException($"Error registering the generic type: {requestType.FullName}. The number of generic type parameters exceeds the maximum allowed ({MaxGenericTypeParameters})."); foreach (var list in lists) { if (MaxTypesClosing > 0 && list.Count > MaxTypesClosing) throw new ArgumentException($"Error registering the generic type: {requestType.FullName}. One of the generic type parameter's count of types that can close exceeds the maximum length allowed ({MaxTypesClosing})."); } // Calculate the total number of combinations long totalCombinations = 1; foreach (var list in lists) { totalCombinations *= list.Count; if (MaxGenericTypeParameters > 0 && totalCombinations > MaxGenericTypeRegistrations) throw new ArgumentException($"Error registering the generic type: {requestType.FullName}. The total number of generic type registrations exceeds the maximum allowed ({MaxGenericTypeRegistrations})."); } } if (depth >= lists.Count) return new List> { new List() }; cancellationToken.ThrowIfCancellationRequested(); var currentList = lists[depth]; var childCombinations = GenerateCombinations(requestType, lists, depth + 1, cancellationToken); var combinations = new List>(); foreach (var item in currentList) { foreach (var childCombination in childCombinations) { var currentCombination = new List { item }; currentCombination.AddRange(childCombination); combinations.Add(currentCombination); } } return combinations; } private static void AddAllConcretionsThatClose(Type openRequestInterface, List concretions, IServiceCollection services, IEnumerable assembliesToScan, CancellationToken cancellationToken) { foreach (var concretion in concretions) { var concreteRequests = GetConcreteRequestTypes(openRequestInterface, concretion, assembliesToScan, cancellationToken); if (concreteRequests is null) continue; var registrationTypes = concreteRequests .Select(concreteRequest => GetConcreteRegistrationTypes(openRequestInterface, concreteRequest, concretion)); foreach (var (Service, Implementation) in registrationTypes) { cancellationToken.ThrowIfCancellationRequested(); services.AddTransient(Service, Implementation); } } } internal static bool CouldCloseTo(this Type openConcretion, Type closedInterface) { var openInterface = closedInterface.GetGenericTypeDefinition(); var arguments = closedInterface.GenericTypeArguments; var concreteArguments = openConcretion.GenericTypeArguments; return arguments.Length == concreteArguments.Length && openConcretion.CanBeCastTo(openInterface); } private static bool CanBeCastTo(this Type pluggedType, Type pluginType) { if (pluggedType == null) return false; if (pluggedType == pluginType) return true; return pluginType.IsAssignableFrom(pluggedType); } private static bool IsOpenGeneric(this Type type) { return type.IsGenericTypeDefinition || type.ContainsGenericParameters; } internal static IEnumerable FindInterfacesThatClose(this Type pluggedType, Type templateType) { return FindInterfacesThatClosesCore(pluggedType, templateType).Distinct(); } private static IEnumerable FindInterfacesThatClosesCore(Type pluggedType, Type templateType) { if (pluggedType == null) yield break; if (!pluggedType.IsConcrete()) yield break; if (templateType.IsInterface) { foreach ( var interfaceType in pluggedType.GetInterfaces() .Where(type => type.IsGenericType && (type.GetGenericTypeDefinition() == templateType))) { yield return interfaceType; } } else if (pluggedType.BaseType!.IsGenericType && (pluggedType.BaseType!.GetGenericTypeDefinition() == templateType)) { yield return pluggedType.BaseType!; } if (pluggedType.BaseType == typeof(object)) yield break; foreach (var interfaceType in FindInterfacesThatClosesCore(pluggedType.BaseType!, templateType)) { yield return interfaceType; } } private static bool IsConcrete(this Type type) { return !type.IsAbstract && !type.IsInterface; } private static void Fill(this IList list, T value) { if (list.Contains(value)) return; list.Add(value); } private static IEnumerable GetLoadableDefinedTypes(this Assembly assembly) { try { return assembly.DefinedTypes; } catch (ReflectionTypeLoadException ex) { return ex.Types.OfType(); } } private static bool HasNestedGenericResponseType(Type openBehaviorType) { var iface = openBehaviorType.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPipelineBehavior<,>)); if (iface == null) return false; return iface.GetGenericArguments()[1].IsGenericType; } private static bool TryMatchType(Type pattern, Type concrete, Dictionary bindings) { if (pattern.IsGenericParameter) { if (bindings.TryGetValue(pattern, out var existing)) return existing == concrete; bindings[pattern] = concrete; return true; } if (pattern.IsGenericType && concrete.IsGenericType) { if (pattern.GetGenericTypeDefinition() != concrete.GetGenericTypeDefinition()) return false; var pArgs = pattern.GetGenericArguments(); var cArgs = concrete.GetGenericArguments(); if (pArgs.Length != cArgs.Length) return false; return pArgs.Zip(cArgs, (p, c) => TryMatchType(p, c, bindings)).All(x => x); } return pattern == concrete; } private static void RegisterClosedBehaviorsFromAssemblies( Type openBehaviorType, IServiceCollection services, IEnumerable assemblies, ServiceLifetime lifetime) { var iface = openBehaviorType.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPipelineBehavior<,>)); if (iface == null) return; var requestPattern = iface.GetGenericArguments()[0]; var responsePattern = iface.GetGenericArguments()[1]; var behaviorParams = openBehaviorType.GetGenericArguments(); var requests = assemblies .SelectMany(a => a.GetLoadableDefinedTypes()) .Where(t => t.IsConcrete() && !t.ContainsGenericParameters) .SelectMany(t => t.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>)) .Select(i => (RequestType: (Type)t, ResponseType: i.GetGenericArguments()[0]))); foreach (var (requestType, responseType) in requests) { var bindings = new Dictionary(); if (!TryMatchType(requestPattern, requestType, bindings)) continue; if (!TryMatchType(responsePattern, responseType, bindings)) continue; if (!behaviorParams.All(tp => bindings.ContainsKey(tp))) continue; try { var closingArgs = behaviorParams.Select(tp => bindings[tp]).ToArray(); var closedBehavior = openBehaviorType.MakeGenericType(closingArgs); var closedService = typeof(IPipelineBehavior<,>).MakeGenericType(requestType, responseType); services.TryAddEnumerable(new ServiceDescriptor(closedService, closedBehavior, lifetime)); } catch (Exception) { /* ignore invalid type constructions */ } } } public static void AddRequiredServices(IServiceCollection services, MediatRServiceConfiguration serviceConfiguration) { // Use TryAdd, so any existing ServiceFactory/IMediator registration doesn't get overridden services.TryAdd(new ServiceDescriptor(typeof(IMediator), serviceConfiguration.MediatorImplementationType, serviceConfiguration.Lifetime)); services.TryAdd(new ServiceDescriptor(typeof(ISender), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); services.TryAdd(new ServiceDescriptor(typeof(IPublisher), sp => sp.GetRequiredService(), serviceConfiguration.Lifetime)); MediatRServiceCollectionExtensions.LicenseChecked = false; services.TryAddSingleton(serviceConfiguration); services.TryAddSingleton(static sp => { var loggerFactory = sp.GetService() ?? throw new InvalidOperationException( "MediatR requires ILoggerFactory to be registered. " + "Call services.AddLogging() before services.AddMediatR()."); var config = sp.GetService(); return config != null ? new LicenseAccessor(config, loggerFactory) : new LicenseAccessor(loggerFactory); }); services.TryAddSingleton(static sp => { var loggerFactory = sp.GetService() ?? throw new InvalidOperationException( "MediatR requires ILoggerFactory to be registered. " + "Call services.AddLogging() before services.AddMediatR()."); return new LicenseValidator(loggerFactory); }); var notificationPublisherServiceDescriptor = serviceConfiguration.NotificationPublisherType != null ? new ServiceDescriptor(typeof(INotificationPublisher), serviceConfiguration.NotificationPublisherType, serviceConfiguration.Lifetime) : new ServiceDescriptor(typeof(INotificationPublisher), serviceConfiguration.NotificationPublisher); services.TryAdd(notificationPublisherServiceDescriptor); // Register pre processors, then post processors, then behaviors if (serviceConfiguration.RequestExceptionActionProcessorStrategy == RequestExceptionActionProcessorStrategy.ApplyForUnhandledExceptions) { RegisterBehaviorIfImplementationsExist(services, typeof(RequestExceptionActionProcessorBehavior<,>), typeof(IRequestExceptionAction<,>)); RegisterBehaviorIfImplementationsExist(services, typeof(RequestExceptionProcessorBehavior<,>), typeof(IRequestExceptionHandler<,,>)); } else { RegisterBehaviorIfImplementationsExist(services, typeof(RequestExceptionProcessorBehavior<,>), typeof(IRequestExceptionHandler<,,>)); RegisterBehaviorIfImplementationsExist(services, typeof(RequestExceptionActionProcessorBehavior<,>), typeof(IRequestExceptionAction<,>)); } if (serviceConfiguration.RequestPreProcessorsToRegister.Any()) { services.TryAddEnumerable(new ServiceDescriptor(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>), ServiceLifetime.Transient)); services.TryAddEnumerable(serviceConfiguration.RequestPreProcessorsToRegister); } if (serviceConfiguration.RequestPostProcessorsToRegister.Any()) { services.TryAddEnumerable(new ServiceDescriptor(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>), ServiceLifetime.Transient)); services.TryAddEnumerable(serviceConfiguration.RequestPostProcessorsToRegister); } foreach (var serviceDescriptor in serviceConfiguration.BehaviorsToRegister) { services.TryAddEnumerable(serviceDescriptor); // For open behaviors whose TResponse is a nested generic (e.g. List, Result), // the DI container cannot close them correctly via positional mapping. // Register explicitly-closed versions by scanning assemblies for matching request types. if (serviceDescriptor.ImplementationType != null && serviceDescriptor.ServiceType == typeof(IPipelineBehavior<,>) && serviceDescriptor.ImplementationType.IsOpenGeneric() && HasNestedGenericResponseType(serviceDescriptor.ImplementationType)) { RegisterClosedBehaviorsFromAssemblies( serviceDescriptor.ImplementationType, services, serviceConfiguration.AssembliesToRegister, serviceDescriptor.Lifetime); } } foreach (var serviceDescriptor in serviceConfiguration.StreamBehaviorsToRegister) { services.TryAddEnumerable(serviceDescriptor); } } private static void RegisterBehaviorIfImplementationsExist(IServiceCollection services, Type behaviorType, Type subBehaviorType) { var hasAnyRegistrationsOfSubBehaviorType = services .Where(service => !service.IsKeyedService) .Select(service => service.ImplementationType) .OfType() .SelectMany(type => type.GetInterfaces()) .Where(type => type.IsGenericType) .Select(type => type.GetGenericTypeDefinition()) .Any(type => type == subBehaviorType); if (hasAnyRegistrationsOfSubBehaviorType) { services.TryAddEnumerable(new ServiceDescriptor(typeof(IPipelineBehavior<,>), behaviorType, ServiceLifetime.Transient)); } } } ================================================ FILE: src/MediatR/TypeForwardings.cs ================================================ using System.Runtime.CompilerServices; using MediatR; [assembly: TypeForwardedTo(typeof(IBaseRequest))] [assembly: TypeForwardedTo(typeof(IRequest<>))] [assembly: TypeForwardedTo(typeof(IRequest))] [assembly: TypeForwardedTo(typeof(INotification))] [assembly: TypeForwardedTo(typeof(Unit))] ================================================ FILE: src/MediatR/Wrappers/NotificationHandlerWrapper.cs ================================================ namespace MediatR.Wrappers; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; public abstract class NotificationHandlerWrapper { public abstract Task Handle(INotification notification, IServiceProvider serviceFactory, Func, INotification, CancellationToken, Task> publish, CancellationToken cancellationToken); } public class NotificationHandlerWrapperImpl : NotificationHandlerWrapper where TNotification : INotification { public override Task Handle(INotification notification, IServiceProvider serviceFactory, Func, INotification, CancellationToken, Task> publish, CancellationToken cancellationToken) { var handlers = serviceFactory .GetServices>() .GroupBy(static x => x.GetType()) .Select(static g => g.First()) .Select(static x => new NotificationHandlerExecutor(x, (theNotification, theToken) => x.Handle((TNotification)theNotification, theToken))); return publish(handlers, notification, cancellationToken); } } ================================================ FILE: src/MediatR/Wrappers/RequestHandlerWrapper.cs ================================================ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Wrappers; public abstract class RequestHandlerBase { public abstract Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken); } public abstract class RequestHandlerWrapper : RequestHandlerBase { public abstract Task Handle(IRequest request, IServiceProvider serviceProvider, CancellationToken cancellationToken); } public abstract class RequestHandlerWrapper : RequestHandlerBase { public abstract Task Handle(IRequest request, IServiceProvider serviceProvider, CancellationToken cancellationToken); } public class RequestHandlerWrapperImpl : RequestHandlerWrapper where TRequest : IRequest { public override async Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken) => await Handle((IRequest) request, serviceProvider, cancellationToken).ConfigureAwait(false); public override Task Handle(IRequest request, IServiceProvider serviceProvider, CancellationToken cancellationToken) { Task Handler(CancellationToken t = default) => serviceProvider.GetRequiredService>() .Handle((TRequest) request, t == default ? cancellationToken : t); return serviceProvider .GetServices>() .Reverse() .Aggregate((RequestHandlerDelegate) Handler, (next, pipeline) => (t) => pipeline.Handle((TRequest) request, next, t == default ? cancellationToken : t))(); } } public class RequestHandlerWrapperImpl : RequestHandlerWrapper where TRequest : IRequest { public override async Task Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken) => await Handle((IRequest) request, serviceProvider, cancellationToken).ConfigureAwait(false); public override Task Handle(IRequest request, IServiceProvider serviceProvider, CancellationToken cancellationToken) { async Task Handler(CancellationToken t = default) { await serviceProvider.GetRequiredService>() .Handle((TRequest) request, t == default ? cancellationToken : t); return Unit.Value; } return serviceProvider .GetServices>() .Reverse() .Aggregate((RequestHandlerDelegate) Handler, (next, pipeline) => (t) => pipeline.Handle((TRequest) request, next, t == default ? cancellationToken : t))(); } } ================================================ FILE: src/MediatR/Wrappers/StreamRequestHandlerWrapper.cs ================================================ using System; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Wrappers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; internal abstract class StreamRequestHandlerBase { public abstract IAsyncEnumerable Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken); } internal abstract class StreamRequestHandlerWrapper : StreamRequestHandlerBase { public abstract IAsyncEnumerable Handle( IStreamRequest request, IServiceProvider serviceProvider, CancellationToken cancellationToken); } internal class StreamRequestHandlerWrapperImpl : StreamRequestHandlerWrapper where TRequest : IStreamRequest { public override async IAsyncEnumerable Handle(object request, IServiceProvider serviceProvider, [EnumeratorCancellation] CancellationToken cancellationToken) { await foreach (var item in Handle((IStreamRequest) request, serviceProvider, cancellationToken)) { yield return item; } } public override async IAsyncEnumerable Handle(IStreamRequest request, IServiceProvider serviceProvider, [EnumeratorCancellation] CancellationToken cancellationToken) { IAsyncEnumerable Handler() => serviceProvider .GetRequiredService>() .Handle((TRequest) request, cancellationToken); var items = serviceProvider .GetServices>() .Reverse() .Aggregate( (StreamHandlerDelegate) Handler, (next, pipeline) => () => pipeline.Handle( (TRequest) request, () => NextWrapper(next(), cancellationToken), cancellationToken ) )(); await foreach ( var item in items.WithCancellation(cancellationToken) ) { yield return item; } } private static async IAsyncEnumerable NextWrapper( IAsyncEnumerable items, [EnumeratorCancellation] CancellationToken cancellationToken) { var cancellable = items .WithCancellation(cancellationToken) .ConfigureAwait(false); await foreach (var item in cancellable) { yield return item; } } } ================================================ FILE: src/MediatR/license.txt ================================================ By accessing code under the [Lucky Penny Software GitHub Organization](https://github.com/LuckyPennySoftware) (Lucky Penny Software) here, you are agreeing to the following licensing terms. If you do not agree to these terms, do not access Lucky Penny Software code. Your license to Lucky Penny Software source code and/or binaries is governed by the Reciprocal Public License 1.5 (RPL1.5) license as described here: https://opensource.org/license/rpl-1-5/ If you do not wish to release the source of software you build using Lucky Penny Software source code and/or binaries under the terms above, you may use Lucky Penny Software source code and/or binaries under the License Agreement described here: https://luckypennysoftware.com/license ================================================ FILE: src/MediatR.Contracts/INotification.cs ================================================ namespace MediatR; /// /// Marker interface to represent a notification /// public interface INotification { } ================================================ FILE: src/MediatR.Contracts/IRequest.cs ================================================ namespace MediatR; /// /// Marker interface to represent a request with a void response /// public interface IRequest : IBaseRequest { } /// /// Marker interface to represent a request with a response /// /// Response type public interface IRequest : IBaseRequest { } /// /// Allows for generic type constraints of objects implementing IRequest or IRequest{TResponse} /// public interface IBaseRequest { } ================================================ FILE: src/MediatR.Contracts/IStreamRequest.cs ================================================ namespace MediatR; /// /// Marker interface to represent a request with a streaming response /// /// Response type public interface IStreamRequest { } ================================================ FILE: src/MediatR.Contracts/MediatR.Contracts.csproj ================================================  enable Jimmy Bogard Contracts package for requests, responses, and notifications Copyright Jimmy Bogard netstandard2.0 strict mediator;request;response;queries;commands;notifications true ..\..\MediatR.snk true gradient_128x128.png Apache-2.0 true true snupkg true true 2.0.1 MediatR ================================================ FILE: src/MediatR.Contracts/Unit.cs ================================================ namespace MediatR; using System; using System.Threading.Tasks; /// /// Represents a void type, since is not a valid return type in C#. /// public readonly struct Unit : IEquatable, IComparable, IComparable { private static readonly Unit _value = new(); /// /// Default and only value of the type. /// public static ref readonly Unit Value => ref _value; /// /// Task from a type. /// public static Task Task { get; } = System.Threading.Tasks.Task.FromResult(_value); /// /// Compares the current object with another object of the same type. /// /// An object to compare with this object. /// /// A value that indicates the relative order of the objects being compared. /// The return value has the following meanings: /// - Less than zero: This object is less than the parameter. /// - Zero: This object is equal to . /// - Greater than zero: This object is greater than . /// public int CompareTo(Unit other) => 0; /// /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. /// /// An object to compare with this instance. /// /// A value that indicates the relative order of the objects being compared. /// The return value has these meanings: /// - Less than zero: This instance precedes in the sort order. /// - Zero: This instance occurs in the same position in the sort order as . /// - Greater than zero: This instance follows in the sort order. /// int IComparable.CompareTo(object? obj) => 0; /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() => 0; /// /// Determines whether the current object is equal to another object of the same type. /// /// An object to compare with this object. /// /// true if the current object is equal to the parameter; otherwise, false. /// public bool Equals(Unit other) => true; /// /// Determines whether the specified is equal to this instance. /// /// The object to compare with the current instance. /// /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object? obj) => obj is Unit; /// /// Determines whether the object is equal to the object. /// /// The first object. /// The second object. /// true if the object is equal to the object; otherwise, false. public static bool operator ==(Unit first, Unit second) => true; /// /// Determines whether the object is not equal to the object. /// /// The first object. /// The second object. /// true if the object is not equal to the object; otherwise, false. public static bool operator !=(Unit first, Unit second) => false; /// /// Returns a that represents this instance. /// /// A that represents this instance. public override string ToString() => "()"; } ================================================ FILE: test/MediatR.Benchmarks/Benchmarks.cs ================================================ using System.IO; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Benchmarks { [DotTraceDiagnoser] public class Benchmarks { private IMediator _mediator; private readonly Ping _request = new Ping {Message = "Hello World"}; private readonly Pinged _notification = new Pinged(); [GlobalSetup] public void GlobalSetup() { var services = new ServiceCollection(); services.AddSingleton(TextWriter.Null); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(typeof(Ping)); cfg.AddOpenBehavior(typeof(GenericPipelineBehavior<,>)); }); var provider = services.BuildServiceProvider(); _mediator = provider.GetRequiredService(); } [Benchmark] public Task SendingRequests() { return _mediator.Send(_request); } [Benchmark] public Task PublishingNotifications() { return _mediator.Publish(_notification); } } } ================================================ FILE: test/MediatR.Benchmarks/DotTraceDiagnoser.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using BenchmarkDotNet.Analysers; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; using BenchmarkDotNet.Validators; namespace MediatR.Benchmarks { internal sealed class DotTraceDiagnoserAttribute : Attribute, IConfigSource { public DotTraceDiagnoserAttribute() { var manualConfig = ManualConfig.CreateEmpty(); manualConfig.AddDiagnoser(new DotTraceDiagnoser()); Config = manualConfig; } public IConfig Config { get; } } internal sealed class DotTraceDiagnoser : IDiagnoser { private const string DotTraceExecutableNotFoundErrorMessage = "dotTrace executable was not found. " + "Make sure it is part of the PATH or install JetBrains.dotTrace.GlobalTools"; private readonly string _saveLocation; public DotTraceDiagnoser() { _saveLocation = $"C:\\temp\\MediatR\\{DateTimeOffset.Now.UtcDateTime:yyyy-MM-dd-HH_mm_ss}.bench.dtp"; } /// public RunMode GetRunMode(BenchmarkCase benchmarkCase) => RunMode.ExtraRun; /// public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { if (signal != HostSignal.BeforeActualRun) { return; } try { if (!CanRunDotTrace()) { Console.WriteLine(DotTraceExecutableNotFoundErrorMessage); return; } // The directory must exist or an error is thrown by dotTrace. Directory.CreateDirectory(_saveLocation); RunDotTrace(parameters); } catch (Exception e) { Console.Error.WriteLine(e.ToString()); throw; } } private void RunDotTrace(DiagnoserActionParameters parameters) { var dotTrace = new Process { StartInfo = PrepareProcessStartInfo(parameters) }; dotTrace.ErrorDataReceived += (sender, eventArgs) => Console.Error.WriteLine(eventArgs.Data); dotTrace.OutputDataReceived += (sender, eventArgs) => Console.WriteLine(eventArgs.Data); dotTrace.Start(); dotTrace.BeginErrorReadLine(); dotTrace.BeginOutputReadLine(); dotTrace.Exited += (sender, args) => dotTrace.Dispose(); } private ProcessStartInfo PrepareProcessStartInfo(DiagnoserActionParameters parameters) { return new ProcessStartInfo( "dottrace", $"attach {parameters.Process.Id} --save-to={_saveLocation}") { RedirectStandardError = true, RedirectStandardOutput = true, WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, CreateNoWindow = true, }; } /// public IEnumerable ProcessResults(DiagnoserResults results) => Enumerable.Empty(); /// public void DisplayResults(ILogger logger) { } /// public IEnumerable Validate(ValidationParameters validationParameters) => Enumerable.Empty(); /// public IEnumerable Ids => new[] { nameof(DotTraceDiagnoser) }; /// public IEnumerable Exporters => Enumerable.Empty(); public IEnumerable Analysers { get; } = Enumerable.Empty(); private static bool CanRunDotTrace() { try { var startInfo = new ProcessStartInfo("dottrace") { RedirectStandardError = true, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, }; using var process = new Process { StartInfo = startInfo }; process.Start(); process.WaitForExit(); return true; } catch (Exception) { return false; } } } } ================================================ FILE: test/MediatR.Benchmarks/GenericPipelineBehavior.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; namespace MediatR.Benchmarks { public class GenericPipelineBehavior : IPipelineBehavior where TRequest : notnull { private readonly TextWriter _writer; public GenericPipelineBehavior(TextWriter writer) { _writer = writer; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { await _writer.WriteLineAsync("-- Handling Request"); var response = await next(); await _writer.WriteLineAsync("-- Finished Request"); return response; } } } ================================================ FILE: test/MediatR.Benchmarks/GenericRequestPostProcessor.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; using MediatR.Pipeline; namespace MediatR.Benchmarks { public class GenericRequestPostProcessor : IRequestPostProcessor where TRequest : notnull { private readonly TextWriter _writer; public GenericRequestPostProcessor(TextWriter writer) { _writer = writer; } public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) { return _writer.WriteLineAsync("- All Done"); } } } ================================================ FILE: test/MediatR.Benchmarks/GenericRequestPreProcessor.cs ================================================ using System.IO; using System.Threading; using System.Threading.Tasks; using MediatR.Pipeline; namespace MediatR.Benchmarks { public class GenericRequestPreProcessor : IRequestPreProcessor where TRequest : notnull { private readonly TextWriter _writer; public GenericRequestPreProcessor(TextWriter writer) { _writer = writer; } public Task Process(TRequest request, CancellationToken cancellationToken) { return _writer.WriteLineAsync("- Starting Up"); } } } ================================================ FILE: test/MediatR.Benchmarks/MediatR.Benchmarks.csproj ================================================ net10.0 Exe AnyCPU portable true true true Release ================================================ FILE: test/MediatR.Benchmarks/Ping.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace MediatR.Benchmarks { public class Ping : IRequest { public string Message { get; set; } } public class PingHandler : IRequestHandler { public Task Handle(Ping request, CancellationToken cancellationToken) => Task.CompletedTask; } } ================================================ FILE: test/MediatR.Benchmarks/Pinged.cs ================================================ using System.Threading; using System.Threading.Tasks; namespace MediatR.Benchmarks { public class Pinged : INotification { } public class PingedHandler : INotificationHandler { public Task Handle(Pinged notification, CancellationToken cancellationToken) { return Task.CompletedTask; } } } ================================================ FILE: test/MediatR.Benchmarks/Program.cs ================================================ using BenchmarkDotNet.Running; namespace MediatR.Benchmarks { public class Program { public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Abstractions/BaseAssemblyResolutionTests.cs ================================================ using MediatR.DependencyInjectionTests.Contracts.Notifications; using MediatR.DependencyInjectionTests.Contracts.StreamRequests; using Microsoft.Extensions.DependencyInjection; namespace MediatR.DependencyInjectionTests.Abstractions; public abstract class BaseAssemblyResolutionTests(BaseServiceProviderFixture fixture) : IClassFixture { private readonly IServiceProvider _provider = fixture.Provider; [Fact] public void Should_Resolve_Mediator() => _provider.GetService() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Public_RequestHandler() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Internal_RequestHandler() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Private_RequestHandler() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Public_Void_RequestHandler() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Internal_Void_RequestHandler() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Private_Void_RequestHandler() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Public_Private_Internal_Notification_Handlers() => _provider.GetServices>() .Count() .ShouldBe(3); [Fact] public void Should_Resolve_Public_Stream_Request_Handlers() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Internal_Stream_Request_Handlers() => _provider.GetService>() .ShouldNotBeNull(); [Fact] public void Should_Resolve_Private_Stream_Request_Handlers() => _provider.GetService>() .ShouldNotBeNull(); } ================================================ FILE: test/MediatR.DependencyInjectionTests/Abstractions/BaseServiceProviderFixture.cs ================================================ namespace MediatR.DependencyInjectionTests.Abstractions; public class BaseServiceProviderFixture { public virtual IServiceProvider Provider => throw new NotImplementedException(); } ================================================ FILE: test/MediatR.DependencyInjectionTests/AutoFacDependencyInjectionTests.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using MediatR.DependencyInjectionTests.Providers; namespace MediatR.DependencyInjectionTests; public class AutoFacDependencyInjectionTests : BaseAssemblyResolutionTests { public AutoFacDependencyInjectionTests() : base(new AutoFacServiceProviderFixture()) { } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Notifications/Ding.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Notifications; public record Ding : INotification { public class Door1 : INotificationHandler { public Task Handle(Ding notification, CancellationToken cancellationToken) => Task.CompletedTask; } internal class Door2 : INotificationHandler { public Task Handle(Ding notification, CancellationToken cancellationToken) => Task.CompletedTask; } private class Door3 : INotificationHandler { public Task Handle(Ding notification, CancellationToken cancellationToken) => Task.CompletedTask; } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Requests/InternalPing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Requests; internal record InternalPing : IRequest { internal class Handler : IRequestHandler { public Task Handle(InternalPing request, CancellationToken cancellationToken) => Task.FromResult(new Pong()); } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Requests/InternalVoidPing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Requests; internal record InternalVoidPing : IRequest { internal class Handler : IRequestHandler { public Task Handle(InternalVoidPing request, CancellationToken cancellationToken) => Task.CompletedTask; } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Requests/PrivatePing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Requests; public record PrivatePing : IRequest { private class Handler : IRequestHandler { public Task Handle(PrivatePing request, CancellationToken cancellationToken) => Task.FromResult(new Pong()); } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Requests/PrivateVoidPing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Requests; public record PrivateVoidPing : IRequest { private class Handler : IRequestHandler { public Task Handle(PrivateVoidPing request, CancellationToken cancellationToken) => Task.CompletedTask; } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Requests/PublicPing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Requests; public record PublicPing : IRequest { public class Handler : IRequestHandler { public Task Handle(PublicPing request, CancellationToken cancellationToken) => Task.FromResult(new Pong()); } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Requests/PublicVoidPing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Requests; public record PublicVoidPing : IRequest { public class Handler : IRequestHandler { public Task Handle(PublicVoidPing request, CancellationToken cancellationToken) => Task.CompletedTask; } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/Responses/Pong.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.Responses; public record Pong; public record Zong; ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/StreamRequests/InternalZing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.StreamRequests; internal record InternalZing : IStreamRequest { internal class Handler : IStreamRequestHandler { public IAsyncEnumerable Handle(InternalZing request, CancellationToken token) => throw new NotImplementedException(); } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/StreamRequests/PrivateZing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.StreamRequests; internal record PrivateZing : IStreamRequest { private class Handler : IStreamRequestHandler { public IAsyncEnumerable Handle(PrivateZing request, CancellationToken token) => throw new NotImplementedException(); } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Contracts/StreamRequests/PublicZing.cs ================================================ namespace MediatR.DependencyInjectionTests.Contracts.StreamRequests; public record PublicZing : IStreamRequest { public class Handler : IStreamRequestHandler { public IAsyncEnumerable Handle(PublicZing request, CancellationToken token) => throw new NotImplementedException(); } } ================================================ FILE: test/MediatR.DependencyInjectionTests/DryIocDependencyInjectionTests.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using MediatR.DependencyInjectionTests.Providers; namespace MediatR.DependencyInjectionTests; public class DryIocDependencyInjectionTests() : BaseAssemblyResolutionTests(new DryIocServiceProviderFixture()); ================================================ FILE: test/MediatR.DependencyInjectionTests/LamarDependencyInjectionTests.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using MediatR.DependencyInjectionTests.Providers; namespace MediatR.DependencyInjectionTests; public class LamarDependencyInjectionTests() : BaseAssemblyResolutionTests(new LamarServiceProviderFixture()); ================================================ FILE: test/MediatR.DependencyInjectionTests/LightInjectDependencyInjectionTests.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using MediatR.DependencyInjectionTests.Providers; namespace MediatR.DependencyInjectionTests; public class LightInjectDependencyInjectionTests() : BaseAssemblyResolutionTests(new LightInjectServiceProviderFixture()); ================================================ FILE: test/MediatR.DependencyInjectionTests/MediatR.DependencyInjectionTests.csproj ================================================  net10.0 enable enable false true ================================================ FILE: test/MediatR.DependencyInjectionTests/MicrosoftDependencyInjectionTests.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using MediatR.DependencyInjectionTests.Providers; namespace MediatR.DependencyInjectionTests; public class MicrosoftDependencyInjectionTests : BaseAssemblyResolutionTests { public MicrosoftDependencyInjectionTests() : base(new MicrosoftServiceProviderFixture()) { } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Providers/AutoFacServiceProviderFixture.cs ================================================ using Autofac; using Autofac.Extensions.DependencyInjection; using MediatR.DependencyInjectionTests.Abstractions; using Microsoft.Extensions.DependencyInjection; namespace MediatR.DependencyInjectionTests.Providers; public class AutoFacServiceProviderFixture : BaseServiceProviderFixture { public override IServiceProvider Provider { get { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(x => x.RegisterServicesFromAssemblyContaining(typeof(Pong))); var builder = new ContainerBuilder(); builder.Populate(services); var container = builder.Build(); return new AutofacServiceProvider(container); } } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Providers/DryIocServiceProviderFixture.cs ================================================ using DryIoc; using DryIoc.Microsoft.DependencyInjection; using MediatR.DependencyInjectionTests.Abstractions; using Microsoft.Extensions.DependencyInjection; namespace MediatR.DependencyInjectionTests.Providers; public class DryIocServiceProviderFixture : BaseServiceProviderFixture { public override IServiceProvider Provider { get { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(x => x.RegisterServicesFromAssemblyContaining(typeof(Pong))); var container = new Container(Rules.MicrosoftDependencyInjectionRules); container.WithDependencyInjectionAdapter(services); return container.BuildServiceProvider(); } } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Providers/LamarServiceProviderFixture.cs ================================================ using Lamar; using MediatR.DependencyInjectionTests.Abstractions; using Microsoft.Extensions.DependencyInjection; namespace MediatR.DependencyInjectionTests.Providers; public class LamarServiceProviderFixture : BaseServiceProviderFixture { public override IServiceProvider Provider { get { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(x => x.RegisterServicesFromAssemblyContaining(typeof(Pong))); var c = new Container(services); return c.ServiceProvider; } } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Providers/LightInjectServiceProviderFixture.cs ================================================ using LightInject; using LightInject.Microsoft.DependencyInjection; using MediatR.DependencyInjectionTests.Abstractions; using Microsoft.Extensions.DependencyInjection; namespace MediatR.DependencyInjectionTests.Providers; public class LightInjectServiceProviderFixture : BaseServiceProviderFixture { public override IServiceProvider Provider { get { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(x => x.RegisterServicesFromAssemblyContaining(typeof(Pong))); var container = new ServiceContainer(new ContainerOptions() { EnableMicrosoftCompatibility = true }); return container.CreateServiceProvider(services); } } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Providers/MicrosoftServiceProviderFixture.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using Microsoft.Extensions.DependencyInjection; namespace MediatR.DependencyInjectionTests.Providers; public class MicrosoftServiceProviderFixture : BaseServiceProviderFixture { public override IServiceProvider Provider => new ServiceCollection() .AddFakeLogging() .AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(PublicPing).Assembly)) .BuildServiceProvider(); } ================================================ FILE: test/MediatR.DependencyInjectionTests/Providers/StashBoxServiceProviderFixture.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using Microsoft.Extensions.DependencyInjection; using Stashbox; namespace MediatR.DependencyInjectionTests.Providers; public class StashBoxServiceProviderFixture : BaseServiceProviderFixture { public override IServiceProvider Provider { get { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(x => x.RegisterServicesFromAssemblyContaining(typeof(Pong))); var container = new StashboxContainer(); services.UseStashbox(container); container.Validate(); return services.BuildServiceProvider(); } } } ================================================ FILE: test/MediatR.DependencyInjectionTests/StashBoxDependencyInjectionTests.cs ================================================ using MediatR.DependencyInjectionTests.Abstractions; using MediatR.DependencyInjectionTests.Providers; namespace MediatR.DependencyInjectionTests; public class StashBoxDependencyInjectionTests : BaseAssemblyResolutionTests { public StashBoxDependencyInjectionTests() : base(new StashBoxServiceProviderFixture()) { } } ================================================ FILE: test/MediatR.DependencyInjectionTests/Usings.cs ================================================ global using MediatR.DependencyInjectionTests.Contracts.Requests; global using MediatR.DependencyInjectionTests.Contracts.Responses; global using Shouldly; ================================================ FILE: test/MediatR.Tests/CreateStreamTests.cs ================================================ using System.Threading; namespace MediatR.Tests; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Shouldly; using Xunit; public class CreateStreamTests { public class Ping : IStreamRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public class PingStreamHandler : IStreamRequestHandler { public async IAsyncEnumerable Handle(Ping request, [EnumeratorCancellation]CancellationToken cancellationToken) { yield return await Task.Run(() => new Pong { Message = request.Message + " Pang" }); } } [Fact] public async Task Should_resolve_main_handler() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IStreamRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var response = mediator.CreateStream(new Ping { Message = "Ping" }); int i = 0; await foreach (Pong result in response) { if (i == 0) { result.Message.ShouldBe("Ping Pang"); } i++; } i.ShouldBe(1); } [Fact] public async Task Should_resolve_main_handler_via_dynamic_dispatch() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IStreamRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); object request = new Ping { Message = "Ping" }; var response = mediator.CreateStream(request); int i = 0; await foreach (Pong? result in response) { if (i == 0) { result!.Message.ShouldBe("Ping Pang"); } i++; } i.ShouldBe(1); } [Fact] public async Task Should_resolve_main_handler_by_specific_interface() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IStreamRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var response = mediator.CreateStream(new Ping { Message = "Ping" }); int i = 0; await foreach (Pong result in response) { if (i == 0) { result.Message.ShouldBe("Ping Pang"); } i++; } i.ShouldBe(1); } [Fact] public void Should_raise_execption_on_null_request() { var container = TestContainer.Create(cfg => { cfg.AddTransient(); }); var mediator = container.GetRequiredService(); Should.Throw(() => mediator.CreateStream((Ping) null!)); } [Fact] public void Should_raise_execption_on_null_request_via_dynamic_dispatch() { var container = TestContainer.Create(cfg => { cfg.AddTransient(); }); var mediator = container.GetRequiredService(); Should.Throw(() => mediator.CreateStream((object) null!)); } } ================================================ FILE: test/MediatR.Tests/ExceptionTests.cs ================================================ using System.Threading; namespace MediatR.Tests; using System; using System.Threading.Tasks; using Shouldly; using Xunit; public class ExceptionTests { private readonly IMediator _mediator; public class Ping : IRequest { } public class Pong { } public class VoidPing : IRequest { } public class Pinged : INotification { } public class AsyncPing : IRequest { } public class AsyncVoidPing : IRequest { } public class AsyncPinged : INotification { } public class NullPing : IRequest { } public class VoidNullPing : IRequest { } public class NullPinged : INotification { } public class NullPingHandler : IRequestHandler { public Task Handle(NullPing request, CancellationToken cancellationToken) { return Task.FromResult(new Pong()); } } public class VoidNullPingHandler : IRequestHandler { public Task Handle(VoidNullPing request, CancellationToken cancellationToken) { return Task.CompletedTask; } } public ExceptionTests() { var container = TestContainer.Create(cfg => { cfg.AddTransient(); }); _mediator = container.GetRequiredService(); } [Fact] public async Task Should_throw_for_send() { await Should.ThrowAsync(async () => await _mediator.Send(new Ping())); } [Fact] public async Task Should_throw_for_void_send() { await Should.ThrowAsync(async () => await _mediator.Send(new VoidPing())); } [Fact] public async Task Should_not_throw_for_publish() { Exception ex = null!; try { await _mediator.Publish(new Pinged()); } catch (Exception e) { ex = e; } ex.ShouldBeNull(); } [Fact] public async Task Should_throw_for_async_send() { await Should.ThrowAsync(async () => await _mediator.Send(new AsyncPing())); } [Fact] public async Task Should_throw_for_async_void_send() { await Should.ThrowAsync(async () => await _mediator.Send(new AsyncVoidPing())); } [Fact] public async Task Should_not_throw_for_async_publish() { Exception ex = null!; try { await _mediator.Publish(new AsyncPinged()); } catch (Exception e) { ex = e; } ex.ShouldBeNull(); } [Fact] public async Task Should_throw_argument_exception_for_send_when_request_is_null() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); NullPing request = null!; await Should.ThrowAsync(async () => await mediator.Send(request)); } [Fact] public async Task Should_throw_argument_exception_for_void_send_when_request_is_null() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); VoidNullPing request = null!; await Should.ThrowAsync(async () => await mediator.Send(request)); } [Fact] public async Task Should_throw_argument_exception_for_publish_when_request_is_null() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); NullPinged notification = null!; await Should.ThrowAsync(async () => await mediator.Publish(notification)); } [Fact] public async Task Should_throw_argument_exception_for_publish_when_request_is_null_object() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); object notification = null!; await Should.ThrowAsync(async () => await mediator.Publish(notification)); } [Fact] public async Task Should_throw_argument_exception_for_publish_when_request_is_not_notification() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); object notification = "totally not notification"; await Should.ThrowAsync(async () => await mediator.Publish(notification)); } public class PingException : IRequest { } public class PingExceptionHandler : IRequestHandler { public Task Handle(PingException request, CancellationToken cancellationToken) { throw new NotImplementedException(); } } [Fact] public async Task Should_throw_exception_for_non_generic_send_when_exception_occurs() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); object pingException = new PingException(); await Should.ThrowAsync(async () => await mediator.Send(pingException)); } [Fact] public async Task Should_throw_exception_for_non_request_send() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); object nonRequest = new NonRequest(); var argumentException = await Should.ThrowAsync(async () => await mediator.Send(nonRequest)); Assert.StartsWith("NonRequest does not implement IRequest", argumentException.Message); } public class NonRequest { } [Fact] public async Task Should_throw_exception_for_generic_send_when_exception_occurs() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); PingException pingException = new PingException(); await Should.ThrowAsync(async () => await mediator.Send(pingException)); } } ================================================ FILE: test/MediatR.Tests/GenericRequestHandlerTests.cs ================================================ using System.Linq; using MediatR.Tests.MicrosoftExtensionsDI; namespace MediatR.Tests { [Collection(nameof(ServiceFactoryCollectionBehavior))] public class GenericRequestHandlerTests : BaseGenericRequestHandlerTests { [Theory] [InlineData(9, 3, 3)] [InlineData(10, 4, 4)] [InlineData(1, 1, 1)] [InlineData(50, 3, 3)] public void ShouldResolveAllCombinationsOfGenericHandler(int numberOfClasses, int numberOfInterfaces, int numberOfTypeParameters) { var services = new ServiceCollection(); var dynamicAssembly = GenerateCombinationsTestAssembly(numberOfClasses, numberOfInterfaces, numberOfTypeParameters); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(dynamicAssembly); cfg.RegisterGenericHandlers = true; }); var provider = services.BuildServiceProvider(); var dynamicRequestType = dynamicAssembly.GetType("DynamicRequest")!; int expectedCombinations = CalculateTotalCombinations(numberOfClasses, numberOfInterfaces, numberOfTypeParameters); var testClasses = Enumerable.Range(1, numberOfClasses) .Select(i => dynamicAssembly.GetType($"TestClass{i}")!) .ToArray(); var combinations = GenerateCombinations(testClasses, numberOfInterfaces); foreach (var combination in combinations) { var concreteRequestType = dynamicRequestType.MakeGenericType(combination); var requestHandlerInterface = typeof(IRequestHandler<>).MakeGenericType(concreteRequestType); var handler = provider.GetService(requestHandlerInterface); handler.ShouldNotBeNull($"Handler for {concreteRequestType} should not be null"); } } [Theory] [InlineData(9, 3, 3)] [InlineData(10, 4, 4)] [InlineData(1, 1, 1)] [InlineData(50, 3, 3)] public void ShouldRegisterTheCorrectAmountOfHandlers(int numberOfClasses, int numberOfInterfaces, int numberOfTypeParameters) { var dynamicAssembly = GenerateCombinationsTestAssembly(numberOfClasses, numberOfInterfaces, numberOfTypeParameters); int expectedCombinations = CalculateTotalCombinations(numberOfClasses, numberOfInterfaces, numberOfTypeParameters); var testClasses = Enumerable.Range(1, numberOfClasses) .Select(i => dynamicAssembly.GetType($"TestClass{i}")!) .ToArray(); var combinations = GenerateCombinations(testClasses, numberOfInterfaces); combinations.Count.ShouldBe(expectedCombinations, $"Should have tested all {expectedCombinations} combinations"); } [Theory] [InlineData(9, 3, 3)] [InlineData(10, 4, 4)] [InlineData(1, 1, 1)] [InlineData(50, 3, 3)] public void ShouldNotRegisterDuplicateHandlers(int numberOfClasses, int numberOfInterfaces, int numberOfTypeParameters) { var dynamicAssembly = GenerateCombinationsTestAssembly(numberOfClasses, numberOfInterfaces, numberOfTypeParameters); int expectedCombinations = CalculateTotalCombinations(numberOfClasses, numberOfInterfaces, numberOfTypeParameters); var testClasses = Enumerable.Range(1, numberOfClasses) .Select(i => dynamicAssembly.GetType($"TestClass{i}")!) .ToArray(); var combinations = GenerateCombinations(testClasses, numberOfInterfaces); var hasDuplicates = combinations .Select(x => string.Join(", ", x.Select(y => y.Name))) .GroupBy(x => x) .Any(g => g.Count() > 1); hasDuplicates.ShouldBeFalse(); } [Fact] public void ShouldThrowExceptionWhenTypesClosingExceedsMaximum() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); var assembly = GenerateTypesClosingExceedsMaximumAssembly(); Should.Throw(() => { services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(assembly); cfg.RegisterGenericHandlers = true; }); }) .Message.ShouldContain("One of the generic type parameter's count of types that can close exceeds the maximum length allowed"); } [Fact] public void ShouldThrowExceptionWhenGenericHandlerRegistrationsExceedsMaximum() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); var assembly = GenerateHandlerRegistrationsExceedsMaximumAssembly(); Should.Throw(() => { services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(assembly); cfg.RegisterGenericHandlers = true; }); }) .Message.ShouldContain("The total number of generic type registrations exceeds the maximum allowed"); } [Fact] public void ShouldThrowExceptionWhenGenericTypeParametersExceedsMaximum() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); var assembly = GenerateGenericTypeParametersExceedsMaximumAssembly(); Should.Throw(() => { services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(assembly); cfg.RegisterGenericHandlers = true; }); }) .Message.ShouldContain("The number of generic type parameters exceeds the maximum allowed"); } [Fact] public void ShouldThrowExceptionWhenTimeoutOccurs() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); var assembly = GenerateTimeoutOccursAssembly(); Should.Throw(() => { services.AddMediatR(cfg => { cfg.MaxGenericTypeParameters = 0; cfg.MaxGenericTypeRegistrations = 0; cfg.MaxTypesClosing = 0; cfg.RegistrationTimeout = 100; cfg.RegisterGenericHandlers = true; cfg.RegisterServicesFromAssembly(assembly); }); }) .Message.ShouldBe("The generic handler registration process timed out."); } [Fact] public void ShouldNotRegisterGenericHandlersWhenOptingOut() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); var assembly = GenerateOptOutAssembly(); services.AddMediatR(cfg => { //opt out flag set cfg.RegisterGenericHandlers = false; cfg.RegisterServicesFromAssembly(assembly); }); var provider = services.BuildServiceProvider(); var testClasses = Enumerable.Range(1, 2) .Select(i => assembly.GetType($"TestClass{i}")!) .ToArray(); var requestType = assembly.GetType("OptOutRequest")!; var combinations = GenerateCombinations(testClasses, 2); var concreteRequestType = requestType.MakeGenericType(combinations.First()); var requestHandlerInterface = typeof(IRequestHandler<>).MakeGenericType(concreteRequestType); var handler = provider.GetService(requestHandlerInterface); handler.ShouldBeNull($"Handler for {concreteRequestType} should be null"); } } } ================================================ FILE: test/MediatR.Tests/GenericTypeConstraintsTests.cs ================================================ using System.Reflection; using System.Threading; namespace MediatR.Tests; using System; using System.Linq; using Shouldly; using System.Threading.Tasks; using Xunit; public class GenericTypeConstraintsTests { public interface IGenericTypeRequestHandlerTestClass where TRequest : IBaseRequest { Type[] Handle(TRequest request); } public abstract class GenericTypeRequestHandlerTestClass : IGenericTypeRequestHandlerTestClass where TRequest : IBaseRequest { public bool IsIRequest { get; } public bool IsIRequestT { get; } public bool IsIBaseRequest { get; } public GenericTypeRequestHandlerTestClass() { IsIRequest = typeof(IRequest).IsAssignableFrom(typeof(TRequest)); IsIRequestT = typeof(TRequest).GetInterfaces() .Any(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequest<>)); IsIBaseRequest = typeof(IBaseRequest).IsAssignableFrom(typeof(TRequest)); } public Type[] Handle(TRequest request) { return typeof(TRequest).GetInterfaces(); } } public class GenericTypeConstraintPing : GenericTypeRequestHandlerTestClass { } public class GenericTypeConstraintJing : GenericTypeRequestHandlerTestClass { } public class Jing : IRequest { public string? Message { get; set; } } public class JingHandler : IRequestHandler { public Task Handle(Jing request, CancellationToken cancellationToken) { // empty handle return Task.CompletedTask; } } public class Ping : IRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public class PingHandler : IRequestHandler { public Task Handle(Ping request, CancellationToken cancellationToken) { return Task.FromResult(new Pong { Message = request.Message + " Pong" }); } } private readonly IMediator _mediator; public GenericTypeConstraintsTests() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<>))).AsImplementedInterfaces(); }); cfg.AddTransient(); }); _mediator = container.GetRequiredService(); } [Fact] public async Task Should_Resolve_Void_Return_Request() { // Create Request var jing = new Jing { Message = "Jing" }; // Test mediator still works sending request await _mediator.Send(jing); // Create new instance of type constrained class var genericTypeConstraintsVoidReturn = new GenericTypeConstraintJing(); // Assert it is of type IRequest and IRequest Assert.True(genericTypeConstraintsVoidReturn.IsIRequest); Assert.False(genericTypeConstraintsVoidReturn.IsIRequestT); Assert.True(genericTypeConstraintsVoidReturn.IsIBaseRequest); // Verify it is of IRequest and IBaseRequest var results = genericTypeConstraintsVoidReturn.Handle(jing); Assert.Equal(2, results.Length); results.ShouldNotContain(typeof(IRequest)); results.ShouldContain(typeof(IBaseRequest)); results.ShouldContain(typeof(IRequest)); } [Fact] public async Task Should_Resolve_Response_Return_Request() { // Create Request var ping = new Ping { Message = "Ping" }; // Test mediator still works sending request and gets response var pingResponse = await _mediator.Send(ping); pingResponse.Message.ShouldBe("Ping Pong"); // Create new instance of type constrained class var genericTypeConstraintsResponseReturn = new GenericTypeConstraintPing(); // Assert it is of type IRequest but not IRequest Assert.False(genericTypeConstraintsResponseReturn.IsIRequest); Assert.True(genericTypeConstraintsResponseReturn.IsIRequestT); Assert.True(genericTypeConstraintsResponseReturn.IsIBaseRequest); // Verify it is of IRequest and IBaseRequest, but not IRequest var results = genericTypeConstraintsResponseReturn.Handle(ping); Assert.Equal(2, results.Length); results.ShouldContain(typeof(IRequest)); results.ShouldContain(typeof(IBaseRequest)); results.ShouldNotContain(typeof(IRequest)); } } ================================================ FILE: test/MediatR.Tests/GlobalUsings.cs ================================================ global using Microsoft.Extensions.DependencyInjection; global using Shouldly; global using System; global using System.Threading; global using System.Threading.Tasks; global using Xunit; ================================================ FILE: test/MediatR.Tests/Licensing/LicenseValidatorTests.cs ================================================ using System; using System.Security.Claims; using MediatR.Licensing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; using Shouldly; using Xunit; using License = MediatR.Licensing.License; namespace MediatR.Tests.Licensing; public class LicenseValidatorTests { [Fact] public void Should_return_invalid_when_no_claims() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var licenseValidator = new LicenseValidator(factory); var license = new License(); license.IsConfigured.ShouldBeFalse(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages .ShouldContain(log => log.Level == LogLevel.Warning); } [Fact] public void Should_return_valid_when_community() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var licenseValidator = new LicenseValidator(factory); var license = new License( new Claim("account_id", Guid.NewGuid().ToString()), new Claim("customer_id", Guid.NewGuid().ToString()), new Claim("sub_id", Guid.NewGuid().ToString()), new Claim("iat", DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds().ToString()), new Claim("exp", DateTimeOffset.UtcNow.AddDays(1).ToUnixTimeSeconds().ToString()), new Claim("edition", nameof(Edition.Community)), new Claim("type", nameof(ProductType.Bundle))); license.IsConfigured.ShouldBeTrue(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages.ShouldNotContain(log => log.Level == LogLevel.Error || log.Level == LogLevel.Warning || log.Level == LogLevel.Critical); } [Fact] public void Should_return_invalid_when_not_correct_type() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var licenseValidator = new LicenseValidator(factory); var license = new License( new Claim("account_id", Guid.NewGuid().ToString()), new Claim("customer_id", Guid.NewGuid().ToString()), new Claim("sub_id", Guid.NewGuid().ToString()), new Claim("iat", DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds().ToString()), new Claim("exp", DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeSeconds().ToString()), new Claim("edition", nameof(Edition.Professional)), new Claim("type", nameof(ProductType.AutoMapper))); license.IsConfigured.ShouldBeTrue(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages .ShouldContain(log => log.Level == LogLevel.Error); } [Fact] public void Should_return_invalid_when_expired() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var licenseValidator = new LicenseValidator(factory); var license = new License( new Claim("account_id", Guid.NewGuid().ToString()), new Claim("customer_id", Guid.NewGuid().ToString()), new Claim("sub_id", Guid.NewGuid().ToString()), new Claim("iat", DateTimeOffset.UtcNow.AddYears(-1).ToUnixTimeSeconds().ToString()), new Claim("exp", DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds().ToString()), new Claim("edition", nameof(Edition.Professional)), new Claim("type", nameof(ProductType.MediatR))); license.IsConfigured.ShouldBeTrue(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages .ShouldContain(log => log.Level == LogLevel.Error); } [Fact] public void Should_allow_perpetual_license_when_build_date_before_expiration() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var buildDate = DateTimeOffset.UtcNow.AddDays(-30); var licenseValidator = new LicenseValidator(factory, buildDate); var license = new License( new Claim("account_id", Guid.NewGuid().ToString()), new Claim("customer_id", Guid.NewGuid().ToString()), new Claim("sub_id", Guid.NewGuid().ToString()), new Claim("iat", DateTimeOffset.UtcNow.AddYears(-1).ToUnixTimeSeconds().ToString()), new Claim("exp", DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds().ToString()), new Claim("edition", nameof(Edition.Professional)), new Claim("type", nameof(ProductType.MediatR)), new Claim("perpetual", "true")); license.IsConfigured.ShouldBeTrue(); license.IsPerpetual.ShouldBeTrue(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages.ShouldNotContain(log => log.Level == LogLevel.Error); logMessages.ShouldContain(log => log.Level == LogLevel.Information && log.Message.Contains("perpetual")); } [Fact] public void Should_reject_perpetual_license_when_build_date_after_expiration() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var buildDate = DateTimeOffset.UtcNow.AddDays(-5); // Build date in past, after expiration var licenseValidator = new LicenseValidator(factory, buildDate); var license = new License( new Claim("account_id", Guid.NewGuid().ToString()), new Claim("customer_id", Guid.NewGuid().ToString()), new Claim("sub_id", Guid.NewGuid().ToString()), new Claim("iat", DateTimeOffset.UtcNow.AddYears(-1).ToUnixTimeSeconds().ToString()), new Claim("exp", DateTimeOffset.UtcNow.AddDays(-10).ToUnixTimeSeconds().ToString()), new Claim("edition", nameof(Edition.Professional)), new Claim("type", nameof(ProductType.MediatR)), new Claim("perpetual", "true")); license.IsConfigured.ShouldBeTrue(); license.IsPerpetual.ShouldBeTrue(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages.ShouldContain(log => log.Level == LogLevel.Error && log.Message.Contains("expired")); } [Fact] public void Should_handle_missing_perpetual_claim() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var licenseValidator = new LicenseValidator(factory); var license = new License( new Claim("account_id", Guid.NewGuid().ToString()), new Claim("customer_id", Guid.NewGuid().ToString()), new Claim("sub_id", Guid.NewGuid().ToString()), new Claim("iat", DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds().ToString()), new Claim("exp", DateTimeOffset.UtcNow.AddDays(1).ToUnixTimeSeconds().ToString()), new Claim("edition", nameof(Edition.Community)), new Claim("type", nameof(ProductType.Bundle))); license.IsConfigured.ShouldBeTrue(); license.IsPerpetual.ShouldBeFalse(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages.ShouldNotContain(log => log.Level == LogLevel.Error || log.Level == LogLevel.Warning || log.Level == LogLevel.Critical); } [Fact] public void Should_fall_back_to_expiration_error_when_perpetual_and_build_date_is_null() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var licenseValidator = new LicenseValidator(factory, (DateTimeOffset?)null); var license = new License( new Claim("account_id", Guid.NewGuid().ToString()), new Claim("customer_id", Guid.NewGuid().ToString()), new Claim("sub_id", Guid.NewGuid().ToString()), new Claim("iat", DateTimeOffset.UtcNow.AddDays(-10).ToUnixTimeSeconds().ToString()), new Claim("exp", DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds().ToString()), new Claim("edition", nameof(Edition.Community)), new Claim("type", nameof(ProductType.Bundle)), new Claim("perpetual", "true")); license.IsConfigured.ShouldBeTrue(); license.IsPerpetual.ShouldBeTrue(); // Validator was created with null buildDate - perpetual licensing cannot be applied. licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages.ShouldContain(log => log.Level == LogLevel.Warning && log.Message.Contains("perpetual")); logMessages.ShouldContain(log => log.Level == LogLevel.Error); } [Fact(Skip = "Needs license")] public void Should_return_valid_for_actual_valid_license() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); var config = new MediatRServiceConfiguration { LicenseKey = "<>" }; var licenseAccessor = new LicenseAccessor(config, factory); var licenseValidator = new LicenseValidator(factory); var license = licenseAccessor.Current; license.IsConfigured.ShouldBeTrue(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages .ShouldNotContain(log => log.Level == LogLevel.Error); } [Fact(Skip = "Needs license")] public void Should_return_valid_for_actual_valid_license_via_static_property() { var factory = new LoggerFactory(); var provider = new FakeLoggerProvider(); factory.AddProvider(provider); Mediator.LicenseKey = "<>"; var licenseAccessor = new LicenseAccessor(factory); var licenseValidator = new LicenseValidator(factory); var license = licenseAccessor.Current; license.IsConfigured.ShouldBeTrue(); licenseValidator.Validate(license); var logMessages = provider.Collector.GetSnapshot(); logMessages .ShouldNotContain(log => log.Level == LogLevel.Error); } } ================================================ FILE: test/MediatR.Tests/MediatR.Tests.csproj ================================================  net10.0 enable $(NoWarn);CS8002; false true ..\..\MediatR.snk $(TargetFrameworks);net462 all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/AssemblyResolutionTests.cs ================================================ using Microsoft.Extensions.DependencyInjection; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Shouldly; using Xunit; public class AssemblyResolutionTests { private readonly IServiceProvider _provider; public AssemblyResolutionTests() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(new Logger()); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); cfg.RegisterGenericHandlers = true; }); _provider = services.BuildServiceProvider(); } [Fact] public void ShouldResolveMediator() { _provider.GetService().ShouldNotBeNull(); } [Fact] public void ShouldResolveRequestHandler() { _provider.GetService>().ShouldNotBeNull(); } [Fact] public void ShouldResolveInternalHandler() { _provider.GetService>().ShouldNotBeNull(); } [Fact] public void ShouldResolveNotificationHandlers() { _provider.GetServices>().Count().ShouldBe(4); } [Fact] public void ShouldResolveStreamHandlers() { _provider.GetService>().ShouldNotBeNull(); } [Fact] public void ShouldRequireAtLeastOneAssembly() { var services = new ServiceCollection(); Action registration = () => services.AddMediatR(_ => { }); registration.ShouldThrow(); } [Fact] public void ShouldResolveGenericVoidRequestHandler() { _provider.GetService>>().ShouldNotBeNull(); } [Fact] public void ShouldResolveGenericReturnTypeRequestHandler() { _provider.GetService, string>>().ShouldNotBeNull(); } [Fact] public void ShouldResolveGenericPingRequestHandler() { _provider.GetService, Pong>>().ShouldNotBeNull(); } [Fact] public void ShouldResolveVoidGenericPingRequestHandler() { _provider.GetService>>().ShouldNotBeNull(); } [Fact] public void ShouldNotThrowWhenAssemblyThrowsReflectionTypeLoadException() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(new Logger()); Action registration = () => services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Ping).Assembly, new BrokenAssembly()); }); registration.ShouldNotThrow(); var provider = services.BuildServiceProvider(); provider.GetService>().ShouldNotBeNull(); } private class BrokenAssembly : Assembly { public override IEnumerable DefinedTypes => throw new ReflectionTypeLoadException( new Type?[] { typeof(string), null, typeof(int) }, new Exception?[] { null, new Exception("ByRef-like type cannot be loaded"), null }); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/BaseGenericRequestHandlerTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace MediatR.Tests.MicrosoftExtensionsDI { public abstract class BaseGenericRequestHandlerTests { protected static Assembly GenerateTypesClosingExceedsMaximumAssembly() => CreateAssemblyModuleBuilder("ExceedsMaximumTypesClosingAssembly", 201, 1, CreateHandlerForExceedsMaximumClassesTest); protected static Assembly GenerateHandlerRegistrationsExceedsMaximumAssembly() => CreateAssemblyModuleBuilder("ExceedsMaximumHandlerRegistrationsAssembly", 500, 10, CreateHandlerForExceedsMaximumHandlerRegistrationsTest); protected static Assembly GenerateGenericTypeParametersExceedsMaximumAssembly() => CreateAssemblyModuleBuilder("ExceedsMaximumGenericTypeParametersAssembly", 1, 1, CreateHandlerForExceedsMaximumGenericTypeParametersTest); protected static Assembly GenerateTimeoutOccursAssembly() => CreateAssemblyModuleBuilder("TimeOutOccursAssembly", 400, 3, CreateHandlerForTimeoutOccursTest); protected static Assembly GenerateOptOutAssembly() => CreateAssemblyModuleBuilder("OptOutAssembly", 2, 2, CreateHandlerForOptOutTest); protected static void CreateHandlerForOptOutTest(ModuleBuilder moduleBuilder) => CreateRequestHandler(moduleBuilder, "OptOutRequest", 2); protected static void CreateHandlerForMissingConstraintsTest(ModuleBuilder moduleBuilder) => CreateRequestHandler(moduleBuilder, "MissingConstraintsRequest", 3, 0, false); protected static void CreateHandlerForExceedsMaximumClassesTest(ModuleBuilder moduleBuilder) => CreateRequestHandler(moduleBuilder, "ExceedsMaximumTypesClosingRequest", 1); protected static void CreateHandlerForExceedsMaximumHandlerRegistrationsTest(ModuleBuilder moduleBuilder) => CreateRequestHandler(moduleBuilder, "ExceedsMaximumHandlerRegistrationsRequest", 4); protected static void CreateHandlerForExceedsMaximumGenericTypeParametersTest(ModuleBuilder moduleBuilder) => CreateRequestHandler(moduleBuilder, "ExceedsMaximumGenericTypeParametersRequest", 11, 1); protected static void CreateHandlerForTimeoutOccursTest(ModuleBuilder moduleBuilder) => CreateRequestHandler(moduleBuilder, "TimeoutOccursRequest", 3); protected static void CreateHandlerForCombinationsTest(ModuleBuilder moduleBuilder, int numberOfGenericParameters) => CreateRequestHandler(moduleBuilder, "DynamicRequest", numberOfGenericParameters); protected static void CreateClass(ModuleBuilder moduleBuilder, string className, Type interfaceType) { TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public); typeBuilder.AddInterfaceImplementation(interfaceType); typeBuilder.CreateTypeInfo(); } protected static Type CreateInterface(ModuleBuilder moduleBuilder, string interfaceName) { TypeBuilder interfaceBuilder = moduleBuilder.DefineType(interfaceName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract); return interfaceBuilder.CreateTypeInfo().AsType(); } protected static AssemblyBuilder CreateAssemblyModuleBuilder(string name, int classes, int interfaces, Action handlerCreation) { AssemblyName assemblyName = new AssemblyName(name); AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); CreateTestClassesAndInterfaces(moduleBuilder, classes, interfaces); handlerCreation.Invoke(moduleBuilder); return assemblyBuilder; } protected static AssemblyBuilder GenerateCombinationsTestAssembly(int classes, int interfaces, int genericParameters) { AssemblyName assemblyName = new AssemblyName("DynamicAssembly"); AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); CreateTestClassesAndInterfaces(moduleBuilder, classes, interfaces); CreateHandlerForCombinationsTest(moduleBuilder, genericParameters); return assemblyBuilder; } protected static string[] GetGenericParameterNames(int numberOfTypeParameters) => Enumerable.Range(1, numberOfTypeParameters).Select(i => $"T{i}").ToArray(); protected static void CreateRequestHandler(ModuleBuilder moduleBuilder, string requestName, int numberOfTypeParameters, int numberOfInterfaces = 0, bool includeConstraints = true) { if(numberOfInterfaces == 0) { numberOfInterfaces = numberOfTypeParameters; } // Define the dynamic request class var handlerTypeBuilder = moduleBuilder!.DefineType($"{requestName}Handler", TypeAttributes.Public); var requestTypeBuilder = moduleBuilder!.DefineType(requestName, TypeAttributes.Public); // Define the generic parameters string[] genericParameterNames = GetGenericParameterNames(numberOfTypeParameters); var handlerGenericParameters = handlerTypeBuilder.DefineGenericParameters(genericParameterNames); var requestGenericParameters = requestTypeBuilder.DefineGenericParameters(genericParameterNames); requestTypeBuilder.AddInterfaceImplementation(typeof(IRequest)); if(includeConstraints) { for (int i = 0; i < numberOfTypeParameters; i++) { int interfaceIndex = i % numberOfInterfaces + 1; var constraintType = moduleBuilder.Assembly.GetType($"ITestInterface{interfaceIndex}"); handlerGenericParameters[i].SetInterfaceConstraints(constraintType!); requestGenericParameters[i].SetInterfaceConstraints(constraintType!); } } var requestType = requestTypeBuilder.CreateTypeInfo().AsType(); handlerTypeBuilder.AddInterfaceImplementation(typeof(IRequestHandler<>).MakeGenericType(requestType)); // Define the Handle method MethodBuilder handleMethodBuilder = handlerTypeBuilder.DefineMethod( "Handle", MethodAttributes.Public | MethodAttributes.Virtual, typeof(Task), new[] { requestType, typeof(CancellationToken) }); ILGenerator ilGenerator = handleMethodBuilder.GetILGenerator(); ilGenerator.Emit(OpCodes.Ret); // Implement the interface method handlerTypeBuilder.DefineMethodOverride(handleMethodBuilder, typeof(IRequestHandler<>).MakeGenericType(requestType).GetMethod("Handle")!); // Create the dynamic request class handlerTypeBuilder.CreateTypeInfo(); } protected static void CreateTestClassesAndInterfaces(ModuleBuilder moduleBuilder, int numberOfClasses, int numberOfInterfaces) { Type[] interfaces = new Type[numberOfInterfaces]; for (int i = 1; i <= numberOfInterfaces; i++) { string interfaceName = $"ITestInterface{i}"; interfaces[i - 1] = CreateInterface(moduleBuilder, interfaceName); } for (int i = 1; i <= numberOfClasses; i++) { string className = $"TestClass{i}"; Type interfaceType = interfaces[(i - 1) % numberOfInterfaces]; CreateClass(moduleBuilder, className, interfaceType); } } protected List GenerateCombinations(Type[] types, int interfaces) { var groups = new List[interfaces]; for (int i = 0; i < interfaces; i++) { groups[i] = types.Where((t, index) => index % interfaces == i).ToList(); } return GenerateCombinationsRecursive(groups, 0); } protected List GenerateCombinationsRecursive(List[] groups, int currentGroup) { var result = new List(); if (currentGroup == groups.Length) { result.Add(Array.Empty()); return result; } foreach (var type in groups[currentGroup]) { foreach (var subCombination in GenerateCombinationsRecursive(groups, currentGroup + 1)) { result.Add(new[] { type }.Concat(subCombination).ToArray()); } } return result; } protected static int CalculateTotalCombinations(int numberOfClasses, int numberOfInterfaces, int numberOfTypeParameters) { var testClasses = Enumerable.Range(1, numberOfClasses) .Select(i => $"TestClass{i}") .ToArray(); var groups = new List[numberOfInterfaces]; for (int i = 0; i < numberOfInterfaces; i++) { groups[i] = testClasses.Where((t, index) => index % numberOfInterfaces == i).ToList(); } return groups .Take(numberOfTypeParameters) .Select(group => group.Count) .Aggregate(1, (a, b) => a * b); } } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/CustomMediatorTests.cs ================================================ using Microsoft.Extensions.DependencyInjection; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using System; using System.Linq; using Shouldly; using Xunit; public class CustomMediatorTests { private readonly IServiceProvider _provider; public CustomMediatorTests() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(new Logger()); services.AddMediatR(cfg => { cfg.MediatorImplementationType = typeof(MyCustomMediator); cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); }); _provider = services.BuildServiceProvider(); } [Fact] public void ShouldResolveMediator() { _provider.GetService().ShouldNotBeNull(); _provider.GetRequiredService().GetType().ShouldBe(typeof(MyCustomMediator)); } [Fact] public void ShouldResolveRequestHandler() { _provider.GetService>().ShouldNotBeNull(); } [Fact] public void ShouldResolveNotificationHandlers() { _provider.GetServices>().Count().ShouldBe(4); } [Fact] public void Can_Call_AddMediatr_multiple_times() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.MediatorImplementationType = typeof(MyCustomMediator); cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); }); // Call AddMediatr again, this should NOT override our custom mediatr (With MS DI, last registration wins) services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests))); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); mediator.GetType().ShouldBe(typeof(MyCustomMediator)); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/DerivingRequestsTests.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Xunit; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; public class DerivingRequestsTests { private readonly IServiceProvider _provider; private readonly IMediator _mediator; public DerivingRequestsTests() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(new Logger()); services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining(typeof(Ping))); _provider = services.BuildServiceProvider(); _mediator = _provider.GetRequiredService(); } [Fact] public async Task ShouldReturnPingPong() { Pong pong = await _mediator.Send(new Ping() { Message = "Ping" }); pong.Message.ShouldBe("Ping Pong"); } [Fact] public async Task ShouldReturnDerivedPingPong() { Pong pong = await _mediator.Send(new DerivedPing() { Message = "Ping" }); pong.Message.ShouldBe("DerivedPing Pong"); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/DuplicateAssemblyResolutionTests.cs ================================================ using Microsoft.Extensions.DependencyInjection; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using System; using System.Linq; using Shouldly; using Xunit; public class DuplicateAssemblyResolutionTests { private readonly IServiceProvider _provider; public DuplicateAssemblyResolutionTests() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(typeof(Ping).Assembly, typeof(Ping).Assembly)); _provider = services.BuildServiceProvider(); } [Fact] public void ShouldResolveNotificationHandlersOnlyOnce() { _provider.GetServices>().Count().ShouldBe(4); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/Handlers.cs ================================================ using System; using System.Runtime.CompilerServices; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests { using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; public class Ping : IRequest { public string? Message { get; init; } public Action? ThrowAction { get; init; } } public class DerivedPing : Ping { } public class Pong { public string? Message { get; init; } } public class Zing : IRequest { public string? Message { get; init; } } public class Zong { public string? Message { get; init; } } public class Ding : IRequest { public string? Message { get; init; } } public class Pinged : INotification { } class InternalPing : IRequest { } public class StreamPing : IStreamRequest { public string? Message { get; init; } } public class GenericHandler : INotificationHandler { public Task Handle(INotification notification, CancellationToken cancellationToken) { return Task.FromResult(0); } } public class DingAsyncHandler : IRequestHandler { public Task Handle(Ding message, CancellationToken cancellationToken) => Task.CompletedTask; } public class PingedHandler : INotificationHandler { public Task Handle(Pinged notification, CancellationToken cancellationToken) { return Task.CompletedTask; } } public class PingedAlsoHandler : INotificationHandler { public Task Handle(Pinged notification, CancellationToken cancellationToken) { return Task.CompletedTask; } } public class PingedAlsoOpenHandler : INotificationHandler where TNotification : Pinged { public Task Handle(TNotification notification, CancellationToken cancellationToken) => Task.CompletedTask; } public class Logger { public IList Messages { get; } = new List(); } public class PingHandler : IRequestHandler { private readonly Logger _logger; public PingHandler(Logger logger) { _logger = logger; } public Task Handle(Ping message, CancellationToken cancellationToken) { _logger.Messages.Add("Handler"); message.ThrowAction?.Invoke(message); return Task.FromResult(new Pong { Message = message.Message + " Pong" }); } } public class DerivedPingHandler : IRequestHandler { private readonly Logger _logger; public DerivedPingHandler(Logger logger) { _logger = logger; } public Task Handle(DerivedPing message, CancellationToken cancellationToken) { _logger.Messages.Add("Handler"); return Task.FromResult(new Pong { Message = $"Derived{message.Message} Pong" }); } } public class ZingHandler : IRequestHandler { private readonly Logger _output; public ZingHandler(Logger output) { _output = output; } public Task Handle(Zing message, CancellationToken cancellationToken) { _output.Messages.Add("Handler"); return Task.FromResult(new Zong { Message = message.Message + " Zong" }); } } public class PingStreamHandler : IStreamRequestHandler { private readonly Logger _output; public PingStreamHandler(Logger output) { _output = output; } public async IAsyncEnumerable Handle(StreamPing request, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Handler"); yield return await Task.Run(() => new Pong { Message = request.Message + " Pang" }, cancellationToken); } } public class DuplicateTest : IRequest { } public class DuplicateHandler1 : IRequestHandler { public Task Handle(DuplicateTest message, CancellationToken cancellationToken) { return Task.FromResult(nameof(DuplicateHandler1)); } } public class DuplicateHandler2 : IRequestHandler { public Task Handle(DuplicateTest message, CancellationToken cancellationToken) { return Task.FromResult(nameof(DuplicateHandler2)); } } class InternalPingHandler : IRequestHandler { public Task Handle(InternalPing request, CancellationToken cancellationToken) => Task.CompletedTask; } class MyCustomMediator : IMediator { public Task Send(object request, CancellationToken cancellationToken = new()) { throw new System.NotImplementedException(); } public IAsyncEnumerable CreateStream(IStreamRequest request, CancellationToken cancellationToken = new()) { throw new NotImplementedException(); } public IAsyncEnumerable CreateStream(object request, CancellationToken cancellationToken = new()) { throw new NotImplementedException(); } public Task Publish(object notification, CancellationToken cancellationToken = new()) { throw new System.NotImplementedException(); } public Task Publish(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { throw new System.NotImplementedException(); } public Task Send(IRequest request, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); } public Task Send(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest { throw new System.NotImplementedException(); } } interface ITypeArgument { } class ConcreteTypeArgument : ITypeArgument { } class OpenGenericVoidRequest : IRequest where T : class, ITypeArgument { } class OpenGenericVoidRequestHandler : IRequestHandler> where T : class, ITypeArgument { public Task Handle(OpenGenericVoidRequest request, CancellationToken cancellationToken) => Task.CompletedTask; } class OpenGenericReturnTypeRequest : IRequest where T : class, ITypeArgument { } class OpenGenericReturnTypeRequestHandler : IRequestHandler, string> where T : class, ITypeArgument { public Task Handle(OpenGenericReturnTypeRequest request, CancellationToken cancellationToken) => Task.FromResult(nameof(request)); } public class GenericPing : IRequest where T : Pong { public T? Pong { get; set; } } public class GenericPingHandler : IRequestHandler, T> where T : Pong { public Task Handle(GenericPing request, CancellationToken cancellationToken) => Task.FromResult(request.Pong!); } public class VoidGenericPing : IRequest where T : Pong { } public class VoidGenericPingHandler : IRequestHandler> where T : Pong { public Task Handle(VoidGenericPing request, CancellationToken cancellationToken) => Task.CompletedTask; } } namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included { using System.Threading; using System.Threading.Tasks; public class Foo : IRequest { public string? Message { get; init; } public Action? ThrowAction { get; init; } } public class Bar { public string? Message { get; init; } } public class FooHandler : IRequestHandler { private readonly Logger _logger; public FooHandler(Logger logger) { _logger = logger; } public Task Handle(Foo message, CancellationToken cancellationToken) { _logger.Messages.Add("Handler"); message.ThrowAction?.Invoke(message); return Task.FromResult(new Bar { Message = message.Message + " Bar" }); } } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/Issue1118Tests.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Xunit; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; /// /// Regression tests for https://github.com/LuckyPennySoftware/MediatR/issues/1118 /// /// When E2 derives from E1, a handler for E1 (C1) would be called twice when publishing E2 /// because some DI containers that support variance (e.g. DryIoc) return C1 both from /// the explicit INotificationHandler<E2> registration AND from their own contravariant /// resolution of INotificationHandler<E1>. /// public class Issue1118Tests { public class CallLog { public List Entries { get; } = new(); } public class BaseEvent : INotification { } public class DerivedEvent : BaseEvent { } // IServiceProvider is always injectable, so CallLog becomes an optional soft dependency. // This prevents PipelineTests (which uses ValidateOnBuild=true on the same assembly) from // failing due to CallLog not being registered in its container. public class BaseEventHandler : INotificationHandler { private readonly IServiceProvider _serviceProvider; public BaseEventHandler(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; public Task Handle(BaseEvent notification, CancellationToken cancellationToken) { _serviceProvider.GetService()?.Entries.Add($"BaseEventHandler:{notification.GetType().Name}"); return Task.CompletedTask; } } public class DerivedEventHandler : INotificationHandler { private readonly IServiceProvider _serviceProvider; public DerivedEventHandler(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; public Task Handle(DerivedEvent notification, CancellationToken cancellationToken) { _serviceProvider.GetService()?.Entries.Add($"DerivedEventHandler:{notification.GetType().Name}"); return Task.CompletedTask; } } private static IServiceProvider BuildProvider() { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Issue1118Tests).Assembly)); return services.BuildServiceProvider(); } [Fact] public async Task Publishing_BaseEvent_Should_Call_BaseEventHandler_Once() { var provider = BuildProvider(); var log = provider.GetRequiredService(); await provider.GetRequiredService().Publish(new BaseEvent()); log.Entries.Count(e => e.StartsWith("BaseEventHandler")).ShouldBe(1); } [Fact] public async Task Publishing_DerivedEvent_Should_Call_BaseEventHandler_ExactlyOnce() { var provider = BuildProvider(); var log = provider.GetRequiredService(); await provider.GetRequiredService().Publish(new DerivedEvent()); log.Entries.Count(e => e.StartsWith("BaseEventHandler")).ShouldBe(1, "BaseEventHandler should be called exactly once for DerivedEvent, not duplicated"); } [Fact] public async Task Publishing_DerivedEvent_Should_Call_DerivedEventHandler_Once() { var provider = BuildProvider(); var log = provider.GetRequiredService(); await provider.GetRequiredService().Publish(new DerivedEvent()); log.Entries.Count(e => e.StartsWith("DerivedEventHandler")).ShouldBe(1); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/NotificationPublisherTests.cs ================================================ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediatR.NotificationPublishers; using Shouldly; using Xunit; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; public class NotificationPublisherTests { public class MockPublisher : INotificationPublisher { public int CallCount { get; set; } public async Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) { foreach (var handlerExecutor in handlerExecutors) { await handlerExecutor.HandlerCallback(notification, cancellationToken); CallCount++; } } } [Fact] public void ShouldResolveDefaultPublisher() { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(new Logger()); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetService(); mediator.ShouldNotBeNull(); var publisher = provider.GetService(); publisher.ShouldNotBeNull(); } [Fact] public async Task ShouldSubstitutePublisherInstance() { var publisher = new MockPublisher(); var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); cfg.NotificationPublisher = publisher; }); var provider = services.BuildServiceProvider(); var mediator = provider.GetService(); mediator.ShouldNotBeNull(); await mediator.Publish(new Pinged()); publisher.CallCount.ShouldBeGreaterThan(0); } [Fact] public async Task ShouldSubstitutePublisherServiceType() { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); cfg.NotificationPublisherType = typeof(MockPublisher); cfg.Lifetime = ServiceLifetime.Singleton; }); var provider = services.BuildServiceProvider(); var mediator = provider.GetService(); var publisher = provider.GetService(); mediator.ShouldNotBeNull(); publisher.ShouldNotBeNull(); await mediator.Publish(new Pinged()); var mock = publisher.ShouldBeOfType(); mock.CallCount.ShouldBeGreaterThan(0); } [Fact] public async Task ShouldSubstitutePublisherServiceTypeWithWhenAll() { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(typeof(CustomMediatorTests)); cfg.NotificationPublisherType = typeof(TaskWhenAllPublisher); cfg.Lifetime = ServiceLifetime.Singleton; }); var provider = services.BuildServiceProvider(); var mediator = provider.GetService(); var publisher = provider.GetService(); mediator.ShouldNotBeNull(); publisher.ShouldNotBeNull(); await Should.NotThrowAsync(mediator.Publish(new Pinged())); publisher.ShouldBeOfType(); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/PipeLineMultiCallToConstructorTest.cs ================================================ using System.Threading; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using System.Reflection; using System.Threading.Tasks; using Shouldly; using Xunit; public class PipelineMultiCallToConstructorTests { public class ConstructorTestBehavior : IPipelineBehavior where TRequest : IRequest { private readonly Logger _output; public ConstructorTestBehavior(Logger output) => _output = output; public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("ConstructorTestBehavior before"); var response = await next(); _output.Messages.Add("ConstructorTestBehavior after"); return response; } } public class ConstructorTestRequest : IRequest { public string? Message { get; set; } } public class ConstructorTestResponse { public string? Message { get; set; } } public class ConstructorTestHandler : IRequestHandler { private static volatile object _lockObject = new(); private readonly Logger _logger; private static int _constructorCallCount; public static int ConstructorCallCount => _constructorCallCount; public static void ResetCallCount() { lock (_lockObject) { _constructorCallCount = 0; } } public ConstructorTestHandler(Logger logger) { _logger = logger; lock (_lockObject) { _constructorCallCount++; } } public Task Handle(ConstructorTestRequest request, CancellationToken cancellationToken) { _logger.Messages.Add("Handler"); return Task.FromResult(new ConstructorTestResponse { Message = request.Message + " ConstructorPong" }); } } [Fact] public async Task Should_not_call_constructor_multiple_times_when_using_a_pipeline() { ConstructorTestHandler.ResetCallCount(); ConstructorTestHandler.ConstructorCallCount.ShouldBe(0); var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstructorTestBehavior<,>)); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); cfg.AddOpenBehavior(typeof(ConstructorTestBehavior<,>)); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var response = await mediator.Send(new ConstructorTestRequest { Message = "ConstructorPing" }); response.Message.ShouldBe("ConstructorPing ConstructorPong"); output.Messages.ShouldBe(new[] { "ConstructorTestBehavior before", "Handler", "ConstructorTestBehavior after" }); ConstructorTestHandler.ConstructorCallCount.ShouldBe(1); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/PipelineTests.cs ================================================ using System.Runtime.CompilerServices; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Pipeline; using Shouldly; using Xunit; public class PipelineTests { public class OuterBehavior : IPipelineBehavior { private readonly Logger _output; public OuterBehavior(Logger output) { _output = output; } public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Outer before"); var response = await next(); _output.Messages.Add("Outer after"); return response; } } public class InnerBehavior : IPipelineBehavior { private readonly Logger _output; public InnerBehavior(Logger output) { _output = output; } public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Inner before"); var response = await next(); _output.Messages.Add("Inner after"); return response; } } public class OuterStreamBehavior : IStreamPipelineBehavior { private readonly Logger _output; public OuterStreamBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(Ping request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Outer before"); await foreach (var item in next().WithCancellation(cancellationToken)) { yield return item; } _output.Messages.Add("Outer after"); } } public class InnerStreamBehavior : IStreamPipelineBehavior { private readonly Logger _output; public InnerStreamBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(Ping request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Inner before"); await foreach (var item in next().WithCancellation(cancellationToken)) { yield return item; } _output.Messages.Add("Inner after"); } } public class InnerBehavior : IPipelineBehavior where TRequest : notnull { private readonly Logger _output; public InnerBehavior(Logger output) { _output = output; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Inner generic before"); var response = await next(); _output.Messages.Add("Inner generic after"); return response; } } public class OuterBehavior : IPipelineBehavior where TRequest : notnull { private readonly Logger _output; public OuterBehavior(Logger output) { _output = output; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Outer generic before"); var response = await next(); _output.Messages.Add("Outer generic after"); return response; } } public class ConstrainedBehavior : IPipelineBehavior where TRequest : notnull where TResponse : Pong { private readonly Logger _output; public ConstrainedBehavior(Logger output) { _output = output; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Constrained before"); var response = await next(); _output.Messages.Add("Constrained after"); return response; } } public class FirstPreProcessor : IRequestPreProcessor where TRequest : notnull { private readonly Logger _output; public FirstPreProcessor(Logger output) { _output = output; } public Task Process(TRequest request, CancellationToken cancellationToken) { _output.Messages.Add("First pre processor"); return Task.FromResult(0); } } public class FirstConcretePreProcessor : IRequestPreProcessor { private readonly Logger _output; public FirstConcretePreProcessor(Logger output) { _output = output; } public Task Process(Ping request, CancellationToken cancellationToken) { _output.Messages.Add("First concrete pre processor"); return Task.FromResult(0); } } public class NextPreProcessor : IRequestPreProcessor where TRequest : notnull { private readonly Logger _output; public NextPreProcessor(Logger output) { _output = output; } public Task Process(TRequest request, CancellationToken cancellationToken) { _output.Messages.Add("Next pre processor"); return Task.FromResult(0); } } public class NextConcretePreProcessor : IRequestPreProcessor { private readonly Logger _output; public NextConcretePreProcessor(Logger output) { _output = output; } public Task Process(Ping request, CancellationToken cancellationToken) { _output.Messages.Add("Next concrete pre processor"); return Task.FromResult(0); } } public class FirstPostProcessor : IRequestPostProcessor where TRequest : notnull { private readonly Logger _output; public FirstPostProcessor(Logger output) { _output = output; } public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) { _output.Messages.Add("First post processor"); return Task.FromResult(0); } } public class FirstConcretePostProcessor : IRequestPostProcessor { private readonly Logger _output; public FirstConcretePostProcessor(Logger output) { _output = output; } public Task Process(Ping request, Pong response, CancellationToken cancellationToken) { _output.Messages.Add("First concrete post processor"); return Task.FromResult(0); } } public class NextPostProcessor : IRequestPostProcessor where TRequest : notnull { private readonly Logger _output; public NextPostProcessor(Logger output) { _output = output; } public Task Process(TRequest request, TResponse response, CancellationToken cancellationToken) { _output.Messages.Add("Next post processor"); return Task.FromResult(0); } } public class NextConcretePostProcessor : IRequestPostProcessor { private readonly Logger _output; public NextConcretePostProcessor(Logger output) { _output = output; } public Task Process(Ping request, Pong response, CancellationToken cancellationToken) { _output.Messages.Add("Next concrete post processor"); return Task.FromResult(0); } } public class PingPongGenericExceptionAction : IRequestExceptionAction { private readonly Logger _output; public PingPongGenericExceptionAction(Logger output) => _output = output; public Task Execute(Ping request, Exception exception, CancellationToken cancellationToken) { _output.Messages.Add("Logging generic exception"); return Task.CompletedTask; } } public class PingPongApplicationExceptionAction : IRequestExceptionAction { private readonly Logger _output; public PingPongApplicationExceptionAction(Logger output) => _output = output; public Task Execute(Ping request, ApplicationException exception, CancellationToken cancellationToken) { _output.Messages.Add("Logging ApplicationException exception"); return Task.CompletedTask; } } public class PingPongExceptionActionForType1 : IRequestExceptionAction { private readonly Logger _output; public PingPongExceptionActionForType1(Logger output) => _output = output; public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) { _output.Messages.Add("Logging exception 1"); return Task.CompletedTask; } } public class PingPongExceptionActionForType2 : IRequestExceptionAction { private readonly Logger _output; public PingPongExceptionActionForType2(Logger output) => _output = output; public Task Execute(Ping request, SystemException exception, CancellationToken cancellationToken) { _output.Messages.Add("Logging exception 2"); return Task.CompletedTask; } } public class PingPongExceptionHandlerForType : IRequestExceptionHandler { public Task Handle(Ping request, ApplicationException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { state.SetHandled(new Pong { Message = exception.Message + " Handled by Specific Type" }); return Task.CompletedTask; } } public class PingPongGenericExceptionHandler : IRequestExceptionHandler { private readonly Logger _output; public PingPongGenericExceptionHandler(Logger output) => _output = output; public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { _output.Messages.Add(exception.Message + " Logged by Generic Type"); return Task.CompletedTask; } } public class NotAnOpenBehavior : IPipelineBehavior { public Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) => next(); } public class ThrowingBehavior : IPipelineBehavior { public Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) => throw new Exception(request.Message); } public class NotAnOpenStreamBehavior : IStreamPipelineBehavior { public IAsyncEnumerable Handle(Ping request, StreamHandlerDelegate next, CancellationToken cancellationToken) => next(); } public class OpenBehavior : IPipelineBehavior where TRequest : notnull { public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) => next(); } public class OpenStreamBehavior : IStreamPipelineBehavior where TRequest : notnull { public IAsyncEnumerable Handle(TRequest request, StreamHandlerDelegate next, CancellationToken cancellationToken) => next(); } public class MultiOpenBehavior : IPipelineBehavior, IStreamPipelineBehavior where TRequest : notnull { public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) => next(); public IAsyncEnumerable Handle(TRequest request, StreamHandlerDelegate next, CancellationToken cancellationToken) => next(); } [Fact] public async Task Should_wrap_with_behavior() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); cfg.AddBehavior, OuterBehavior>(); cfg.AddBehavior, InnerBehavior>(); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new[] { "Outer before", "Inner before", "Handler", "Inner after", "Outer after" }); } [Fact] public async Task Should_wrap_generics_with_behavior() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => { // Call these registration methods multiple times to prove we don't register a service if it is already registered for (var i = 0; i < 3; i++) { cfg.AddOpenBehavior(typeof(OuterBehavior<,>)); cfg.AddOpenBehavior(typeof(InnerBehavior<,>)); cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); } }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } [Fact] public async Task Should_register_pre_and_post_processors() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); cfg.AddRequestPreProcessor, FirstConcretePreProcessor>(); cfg.AddRequestPreProcessor, NextConcretePreProcessor>(); cfg.AddOpenRequestPreProcessor(typeof(FirstPreProcessor<>)); cfg.AddOpenRequestPreProcessor(typeof(NextPreProcessor<>)); cfg.AddRequestPostProcessor, FirstConcretePostProcessor>(); cfg.AddRequestPostProcessor, NextConcretePostProcessor>(); cfg.AddOpenRequestPostProcessor(typeof(FirstPostProcessor<,>)); cfg.AddOpenRequestPostProcessor(typeof(NextPostProcessor<,>)); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new[] { "First concrete pre processor", "Next concrete pre processor", "First pre processor", "Next pre processor", "Handler", "First concrete post processor", "Next concrete post processor", "First post processor", "Next post processor", }); } [Fact] public async Task Should_pick_up_specific_exception_behaviors() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(output); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly)); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var response = await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new ApplicationException(msg.Message + " Thrown")}); response.Message.ShouldBe("Ping Thrown Handled by Specific Type"); output.Messages.ShouldNotContain("Logging ApplicationException exception"); } [Fact] public void Should_pick_up_base_exception_behaviors() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly)); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new Exception(msg.Message + " Thrown")})); output.Messages.ShouldContain("Ping Thrown Logged by Generic Type"); output.Messages.ShouldContain("Logging generic exception"); } [Fact] public void Should_handle_exceptions_from_behaviors() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); cfg.AddBehavior(); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping"})); output.Messages.ShouldContain("Ping Logged by Generic Type"); output.Messages.ShouldContain("Logging generic exception"); } [Fact] public void Should_pick_up_exception_actions() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(output); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly)); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); Should.Throw(async () => await mediator.Send(new Ping {Message = "Ping", ThrowAction = msg => throw new SystemException(msg.Message + " Thrown")})); output.Messages.ShouldContain("Logging exception 1"); output.Messages.ShouldContain("Logging exception 2"); } [Fact] public async Task Should_handle_constrained_generics() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); cfg.AddOpenBehavior(typeof(OuterBehavior<,>)); cfg.AddOpenBehavior(typeof(InnerBehavior<,>)); cfg.AddOpenBehavior(typeof(ConstrainedBehavior<,>)); cfg.AddRequestPreProcessor, FirstConcretePreProcessor>(); cfg.AddRequestPreProcessor, NextConcretePreProcessor>(); cfg.AddOpenRequestPreProcessor(typeof(FirstPreProcessor<>)); cfg.AddOpenRequestPreProcessor(typeof(NextPreProcessor<>)); cfg.AddRequestPostProcessor, FirstConcretePostProcessor>(); cfg.AddRequestPostProcessor, NextConcretePostProcessor>(); cfg.AddOpenRequestPostProcessor(typeof(FirstPostProcessor<,>)); cfg.AddOpenRequestPostProcessor(typeof(NextPostProcessor<,>)); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new[] { "First concrete pre processor", "Next concrete pre processor", "First pre processor", "Next pre processor", "Outer generic before", "Inner generic before", "Constrained before", "Handler", "Constrained after", "Inner generic after", "Outer generic after", "First concrete post processor", "Next concrete post processor", "First post processor", "Next post processor" }); output.Messages.Clear(); var zingResponse = await mediator.Send(new Zing { Message = "Zing" }); zingResponse.Message.ShouldBe("Zing Zong"); output.Messages.ShouldBe(new[] { "First pre processor", "Next pre processor", "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", "First post processor", "Next post processor" }); } [Fact] public void Should_throw_when_adding_non_open_behavior() { Should.Throw(() => new MediatRServiceConfiguration().AddOpenBehavior(typeof(NotAnOpenBehavior))); } [Fact] public void Should_throw_when_adding_non_open_stream_behavior() { Should.Throw(() => new MediatRServiceConfiguration().AddOpenBehavior(typeof(NotAnOpenStreamBehavior))); } [Fact] public void Should_throw_when_adding_random_generic_type_as_open_behavior() { Should.Throw(() => new MediatRServiceConfiguration().AddOpenBehavior(typeof(List))); } [Fact] public void Should_handle_open_behavior_registration() { var cfg = new MediatRServiceConfiguration(); cfg.AddOpenBehavior(typeof(OpenBehavior<,>)); cfg.AddOpenStreamBehavior(typeof(OpenStreamBehavior<,>)); cfg.BehaviorsToRegister.Count.ShouldBe(1); cfg.StreamBehaviorsToRegister.Count.ShouldBe(1); cfg.BehaviorsToRegister[0].ServiceType.ShouldBe(typeof(IPipelineBehavior<,>)); cfg.BehaviorsToRegister[0].ImplementationType.ShouldBe(typeof(OpenBehavior<,>)); cfg.BehaviorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.BehaviorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.BehaviorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Transient); cfg.StreamBehaviorsToRegister[0].ServiceType.ShouldBe(typeof(IStreamPipelineBehavior<,>)); cfg.StreamBehaviorsToRegister[0].ImplementationType.ShouldBe(typeof(OpenStreamBehavior<,>)); cfg.StreamBehaviorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.StreamBehaviorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.StreamBehaviorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Transient); var services = new ServiceCollection(); cfg.RegisterServicesFromAssemblyContaining(); Should.NotThrow(() => { services.AddMediatR(cfg); services.BuildServiceProvider(); }); } [Fact] public void Should_handle_inferred_behavior_registration() { var cfg = new MediatRServiceConfiguration(); cfg.AddBehavior(); cfg.AddBehavior(typeof(OuterBehavior)); cfg.BehaviorsToRegister.Count.ShouldBe(2); cfg.BehaviorsToRegister[0].ServiceType.ShouldBe(typeof(IPipelineBehavior)); cfg.BehaviorsToRegister[0].ImplementationType.ShouldBe(typeof(InnerBehavior)); cfg.BehaviorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.BehaviorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.BehaviorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Transient); cfg.BehaviorsToRegister[1].ServiceType.ShouldBe(typeof(IPipelineBehavior)); cfg.BehaviorsToRegister[1].ImplementationType.ShouldBe(typeof(OuterBehavior)); cfg.BehaviorsToRegister[1].ImplementationFactory.ShouldBeNull(); cfg.BehaviorsToRegister[1].ImplementationInstance.ShouldBeNull(); cfg.BehaviorsToRegister[1].Lifetime.ShouldBe(ServiceLifetime.Transient); var services = new ServiceCollection(); cfg.RegisterServicesFromAssemblyContaining(); Should.NotThrow(() => { services.AddMediatR(cfg); services.BuildServiceProvider(); }); } [Fact] public void Should_handle_inferred_stream_behavior_registration() { var cfg = new MediatRServiceConfiguration(); cfg.AddStreamBehavior(); cfg.AddStreamBehavior(typeof(OuterStreamBehavior)); cfg.StreamBehaviorsToRegister.Count.ShouldBe(2); cfg.StreamBehaviorsToRegister[0].ServiceType.ShouldBe(typeof(IStreamPipelineBehavior)); cfg.StreamBehaviorsToRegister[0].ImplementationType.ShouldBe(typeof(InnerStreamBehavior)); cfg.StreamBehaviorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.StreamBehaviorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.StreamBehaviorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Transient); cfg.StreamBehaviorsToRegister[1].ServiceType.ShouldBe(typeof(IStreamPipelineBehavior)); cfg.StreamBehaviorsToRegister[1].ImplementationType.ShouldBe(typeof(OuterStreamBehavior)); cfg.StreamBehaviorsToRegister[1].ImplementationFactory.ShouldBeNull(); cfg.StreamBehaviorsToRegister[1].ImplementationInstance.ShouldBeNull(); cfg.StreamBehaviorsToRegister[1].Lifetime.ShouldBe(ServiceLifetime.Transient); var services = new ServiceCollection(); cfg.RegisterServicesFromAssemblyContaining(); Should.NotThrow(() => { services.AddMediatR(cfg); services.BuildServiceProvider(); }); } [Fact] public void Should_handle_inferred_pre_processor_registration() { var cfg = new MediatRServiceConfiguration(); cfg.AddRequestPreProcessor(); cfg.AddRequestPreProcessor(typeof(NextConcretePreProcessor)); cfg.RequestPreProcessorsToRegister.Count.ShouldBe(2); cfg.RequestPreProcessorsToRegister[0].ServiceType.ShouldBe(typeof(IRequestPreProcessor)); cfg.RequestPreProcessorsToRegister[0].ImplementationType.ShouldBe(typeof(FirstConcretePreProcessor)); cfg.RequestPreProcessorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.RequestPreProcessorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.RequestPreProcessorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Transient); cfg.RequestPreProcessorsToRegister[1].ServiceType.ShouldBe(typeof(IRequestPreProcessor)); cfg.RequestPreProcessorsToRegister[1].ImplementationType.ShouldBe(typeof(NextConcretePreProcessor)); cfg.RequestPreProcessorsToRegister[1].ImplementationFactory.ShouldBeNull(); cfg.RequestPreProcessorsToRegister[1].ImplementationInstance.ShouldBeNull(); cfg.RequestPreProcessorsToRegister[1].Lifetime.ShouldBe(ServiceLifetime.Transient); var services = new ServiceCollection(); cfg.RegisterServicesFromAssemblyContaining(); Should.NotThrow(() => { services.AddMediatR(cfg); services.BuildServiceProvider(); }); } [Fact] public void Should_handle_inferred_post_processor_registration() { var cfg = new MediatRServiceConfiguration(); cfg.AddRequestPostProcessor(); cfg.AddRequestPostProcessor(typeof(NextConcretePostProcessor)); cfg.RequestPostProcessorsToRegister.Count.ShouldBe(2); cfg.RequestPostProcessorsToRegister[0].ServiceType.ShouldBe(typeof(IRequestPostProcessor)); cfg.RequestPostProcessorsToRegister[0].ImplementationType.ShouldBe(typeof(FirstConcretePostProcessor)); cfg.RequestPostProcessorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.RequestPostProcessorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.RequestPostProcessorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Transient); cfg.RequestPostProcessorsToRegister[1].ServiceType.ShouldBe(typeof(IRequestPostProcessor)); cfg.RequestPostProcessorsToRegister[1].ImplementationType.ShouldBe(typeof(NextConcretePostProcessor)); cfg.RequestPostProcessorsToRegister[1].ImplementationFactory.ShouldBeNull(); cfg.RequestPostProcessorsToRegister[1].ImplementationInstance.ShouldBeNull(); cfg.RequestPostProcessorsToRegister[1].Lifetime.ShouldBe(ServiceLifetime.Transient); var services = new ServiceCollection(); cfg.RegisterServicesFromAssemblyContaining(); Should.NotThrow(() => { services.AddMediatR(cfg); services.BuildServiceProvider(); }); } [Fact] public void Should_handle_open_behaviors_registration_from_a_single_type() { var cfg = new MediatRServiceConfiguration(); cfg.AddOpenBehavior(typeof(MultiOpenBehavior<,>), ServiceLifetime.Singleton); cfg.AddOpenStreamBehavior(typeof(MultiOpenBehavior<,>), ServiceLifetime.Singleton); cfg.BehaviorsToRegister.Count.ShouldBe(1); cfg.StreamBehaviorsToRegister.Count.ShouldBe(1); cfg.BehaviorsToRegister[0].ServiceType.ShouldBe(typeof(IPipelineBehavior<,>)); cfg.BehaviorsToRegister[0].ImplementationType.ShouldBe(typeof(MultiOpenBehavior<,>)); cfg.BehaviorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.BehaviorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.BehaviorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Singleton); cfg.StreamBehaviorsToRegister[0].ServiceType.ShouldBe(typeof(IStreamPipelineBehavior<,>)); cfg.StreamBehaviorsToRegister[0].ImplementationType.ShouldBe(typeof(MultiOpenBehavior<,>)); cfg.StreamBehaviorsToRegister[0].ImplementationFactory.ShouldBeNull(); cfg.StreamBehaviorsToRegister[0].ImplementationInstance.ShouldBeNull(); cfg.StreamBehaviorsToRegister[0].Lifetime.ShouldBe(ServiceLifetime.Singleton); var services = new ServiceCollection(); cfg.RegisterServicesFromAssemblyContaining(); Should.NotThrow(() => { services.AddMediatR(cfg); services.BuildServiceProvider(); }); } [Fact] public void Should_auto_register_processors_when_configured_including_all_concrete_types() { var cfg = new MediatRServiceConfiguration { AutoRegisterRequestProcessors = true }; var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); cfg.RegisterServicesFromAssemblyContaining(); services.AddMediatR(cfg); var provider = services.BuildServiceProvider(); var preProcessors = provider.GetServices(typeof(IRequestPreProcessor)).ToList(); preProcessors.Count.ShouldBeGreaterThan(0); preProcessors.ShouldContain(p => p != null && p.GetType() == typeof(FirstConcretePreProcessor)); preProcessors.ShouldContain(p => p != null && p.GetType() == typeof(NextConcretePreProcessor)); var postProcessors = provider.GetServices(typeof(IRequestPostProcessor)).ToList(); postProcessors.Count.ShouldBeGreaterThan(0); postProcessors.ShouldContain(p => p != null && p.GetType() == typeof(FirstConcretePostProcessor)); postProcessors.ShouldContain(p => p != null && p.GetType() == typeof(NextConcretePostProcessor)); } public sealed record FooRequest : IRequest; public interface IBlogger { IList Messages { get; } } public class Blogger : IBlogger { private readonly Logger _logger; public Blogger(Logger logger) { _logger = logger; } public IList Messages => _logger.Messages; } public sealed class FooRequestHandler : IRequestHandler { public FooRequestHandler(IBlogger logger) { this.logger = logger; } readonly IBlogger logger; public Task Handle(FooRequest request, CancellationToken cancellationToken) { logger.Messages.Add("Invoked Handler"); return Task.CompletedTask; } } sealed class ClosedBehavior : IPipelineBehavior { public ClosedBehavior(IBlogger logger) { this.logger = logger; } readonly IBlogger logger; public Task Handle(FooRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { logger.Messages.Add("Invoked Closed Behavior"); return next(); } } sealed class Open2Behavior : IPipelineBehavior where TRequest : notnull { public Open2Behavior(IBlogger> logger) { this.logger = logger; } readonly IBlogger> logger; public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { logger.Messages.Add("Invoked Open Behavior"); return next(); } } [Fact] public async Task Should_register_correctly() { var services = new ServiceCollection(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(); cfg.AddBehavior(); cfg.AddOpenBehavior(typeof(Open2Behavior<,>)); }); services.AddFakeLogging(); var logger = new Logger(); services.AddSingleton(logger); services.AddSingleton(new MediatR.Tests.PipelineTests.Logger()); services.AddSingleton(new MediatR.Tests.StreamPipelineTests.Logger()); services.AddSingleton(new MediatR.Tests.SendTests.Dependency()); services.AddSingleton(new System.IO.StringWriter()); services.AddTransient(typeof(IBlogger<>), typeof(Blogger<>)); var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); var mediator = provider.GetRequiredService(); var request = new FooRequest(); await mediator.Send(request); logger.Messages.ShouldBe(new [] { "Invoked Closed Behavior", "Invoked Open Behavior", "Invoked Handler", }); } #region OpenBehaviorsForMultipleRegistration sealed class OpenBehaviorMultipleRegistration0 : IPipelineBehavior where TRequest : notnull { public OpenBehaviorMultipleRegistration0(IBlogger> logger) { this.logger = logger; } readonly IBlogger> logger; public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { logger.Messages.Add("Invoked OpenBehaviorMultipleRegistration0"); return next(); } } sealed class OpenBehaviorMultipleRegistration1 : IPipelineBehavior where TRequest : notnull { public OpenBehaviorMultipleRegistration1(IBlogger> logger) { this.logger = logger; } readonly IBlogger> logger; public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { logger.Messages.Add("Invoked OpenBehaviorMultipleRegistration1"); return next(); } } sealed class OpenBehaviorMultipleRegistration2 : IPipelineBehavior where TRequest : notnull { public OpenBehaviorMultipleRegistration2(IBlogger> logger) { this.logger = logger; } readonly IBlogger> logger; public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { logger.Messages.Add("Invoked OpenBehaviorMultipleRegistration2"); return next(); } } #endregion OpenBehaviorsForMultipleRegistration [Fact] public async Task Should_register_open_behaviors_correctly() { var behaviorTypeList = new List { typeof(OpenBehaviorMultipleRegistration0<,>), typeof(OpenBehaviorMultipleRegistration1<,>), typeof(OpenBehaviorMultipleRegistration2<,>) }; var services = new ServiceCollection(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(); cfg.AddOpenBehaviors(behaviorTypeList); }); services.AddFakeLogging(); var logger = new Logger(); services.AddSingleton(logger); services.AddSingleton(new MediatR.Tests.PipelineTests.Logger()); services.AddSingleton(new MediatR.Tests.StreamPipelineTests.Logger()); services.AddSingleton(new MediatR.Tests.SendTests.Dependency()); services.AddSingleton(new System.IO.StringWriter()); services.AddTransient(typeof(IBlogger<>), typeof(Blogger<>)); var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); var mediator = provider.GetRequiredService(); var request = new FooRequest(); await mediator.Send(request); logger.Messages.ShouldBe(new[] { "Invoked OpenBehaviorMultipleRegistration0", "Invoked OpenBehaviorMultipleRegistration1", "Invoked OpenBehaviorMultipleRegistration2", "Invoked Handler", }); } // Types for nested generic response type behavior test public class ListPing : IRequest> { } public class ListPingHandler : IRequestHandler> { private readonly Logger _output; public ListPingHandler(Logger output) => _output = output; public Task> Handle(ListPing request, CancellationToken cancellationToken) { _output.Messages.Add("Handler"); return Task.FromResult(new List { new() { Message = "Pong" } }); } } public class ListResponseBehavior : IPipelineBehavior> where TRequest : IRequest> { private readonly Logger _output; public ListResponseBehavior(Logger output) => _output = output; public async Task> Handle(TRequest request, RequestHandlerDelegate> next, CancellationToken cancellationToken) { _output.Messages.Add("Nested behavior before"); var result = await next(); _output.Messages.Add("Nested behavior after"); return result; } } [Fact] public async Task Should_apply_open_behavior_with_nested_generic_response_type() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(PipelineTests).Assembly); cfg.AddOpenBehavior(typeof(ListResponseBehavior<,>)); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var result = await mediator.Send(new ListPing()); result.ShouldNotBeEmpty(); output.Messages.ShouldBe(new[] { "Nested behavior before", "Handler", "Nested behavior after" }); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/StreamPipelineTests.cs ================================================ using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Pipeline; using Shouldly; using Xunit; public class StreamPipelineTests { public class OuterBehavior : IStreamPipelineBehavior { private readonly Logger _output; public OuterBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(StreamPing request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Outer before"); await foreach (var response in next().WithCancellation(cancellationToken)) { yield return response; } _output.Messages.Add("Outer after"); } } public class InnerBehavior : IStreamPipelineBehavior { private readonly Logger _output; public InnerBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(StreamPing request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Inner before"); await foreach (var response in next().WithCancellation(cancellationToken)) { yield return response; } _output.Messages.Add("Inner after"); } } [Fact] public async Task Should_wrap_with_behavior() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(output); services.AddTransient, OuterBehavior>(); services.AddTransient, InnerBehavior>(); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly)); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var stream = mediator.CreateStream(new StreamPing { Message = "Ping" }); await foreach (var response in stream) { response.Message.ShouldBe("Ping Pang"); } output.Messages.ShouldBe(new[] { "Outer before", "Inner before", "Handler", "Inner after", "Outer after" }); } [Fact] public async Task Should_register_and_wrap_with_behavior() { var output = new Logger(); IServiceCollection services = new ServiceCollection(); services.AddSingleton(output); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Ping).Assembly); cfg.AddStreamBehavior, OuterBehavior>(); cfg.AddStreamBehavior, InnerBehavior>(); }); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); var stream = mediator.CreateStream(new StreamPing { Message = "Ping" }); await foreach (var response in stream) { response.Message.ShouldBe("Ping Pang"); } output.Messages.ShouldBe(new[] { "Outer before", "Inner before", "Handler", "Inner after", "Outer after" }); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/TypeEvaluatorTests.cs ================================================ using System.Linq; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using Included; using MediatR.Pipeline; using Shouldly; using System; using Xunit; public class TypeEvaluatorTests { private readonly IServiceProvider _provider; private readonly IServiceCollection _services; public TypeEvaluatorTests() { _services = new ServiceCollection(); _services.AddFakeLogging(); _services.AddSingleton(new Logger()); _services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(typeof(Ping)); cfg.TypeEvaluator = t => t.Namespace == "MediatR.Extensions.Microsoft.DependencyInjection.Tests.Included"; }); _provider = _services.BuildServiceProvider(); } [Fact] public void ShouldResolveMediator() { _provider.GetService().ShouldNotBeNull(); } [Fact] public void ShouldOnlyResolveIncludedRequestHandlers() { _provider.GetService>().ShouldNotBeNull(); _provider.GetService>().ShouldBeNull(); } [Fact] public void ShouldNotRegisterUnNeededBehaviors() { _services.Any(service => service.ImplementationType == typeof(RequestPreProcessorBehavior<,>)) .ShouldBeFalse(); _services.Any(service => service.ImplementationType == typeof(RequestPostProcessorBehavior<,>)) .ShouldBeFalse(); _services.Any(service => service.ImplementationType == typeof(RequestExceptionActionProcessorBehavior<,>)) .ShouldBeFalse(); _services.Any(service => service.ImplementationType == typeof(RequestExceptionProcessorBehavior<,>)) .ShouldBeFalse(); } } ================================================ FILE: test/MediatR.Tests/MicrosoftExtensionsDI/TypeResolutionTests.cs ================================================ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Extensions.Microsoft.DependencyInjection.Tests; using System; using System.Linq; using System.Reflection; using MediatR.Pipeline; using Shouldly; using Xunit; public class TypeResolutionTests { private readonly IServiceProvider _provider; public TypeResolutionTests() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(new Logger()); services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining(typeof(Ping))); _provider = services.BuildServiceProvider(); } [Fact] public void ShouldResolveMediator() { _provider.GetService().ShouldNotBeNull(); } [Fact] public void ShouldResolveSender() { _provider.GetService().ShouldNotBeNull(); } [Fact] public void ShouldResolvePublisher() { _provider.GetService().ShouldNotBeNull(); } [Fact] public void ShouldResolveRequestHandler() { _provider.GetService>().ShouldNotBeNull(); } [Fact] public void ShouldResolveVoidRequestHandler() { _provider.GetService>().ShouldNotBeNull(); } [Fact] public void ShouldResolveNotificationHandlers() { _provider.GetServices>().Count().ShouldBe(4); } [Fact] public void ShouldNotThrowWithMissingEnumerables() { Should.NotThrow(() => _provider.GetRequiredService>>()); } [Fact] public void ShouldResolveFirstDuplicateHandler() { _provider.GetService>().ShouldNotBeNull(); _provider.GetService>() .ShouldBeAssignableTo(); } [Fact] public void ShouldResolveIgnoreSecondDuplicateHandler() { _provider.GetServices>().Count().ShouldBe(1); } [Fact] public void ShouldHandleKeyedServices() { IServiceCollection services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(new Logger()); services.AddKeyedSingleton("Foo", "Foo"); services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining(typeof(Ping))); var serviceProvider = services.BuildServiceProvider(); var mediator = serviceProvider.GetRequiredService(); mediator.ShouldNotBeNull(); } } ================================================ FILE: test/MediatR.Tests/NotificationHandlerTests.cs ================================================ using System.IO; using System.Text; using System.Threading.Tasks; using Shouldly; using Xunit; namespace MediatR.Tests; public class NotificationHandlerTests { public class Ping : INotification { public string? Message { get; set; } } public class PongChildHandler : NotificationHandler { private readonly TextWriter _writer; public PongChildHandler(TextWriter writer) { _writer = writer; } protected override void Handle(Ping notification) { _writer.WriteLine(notification.Message + " Pong"); } } [Fact] public async Task Should_call_abstract_handle_method() { var builder = new StringBuilder(); var writer = new StringWriter(builder); INotificationHandler handler = new PongChildHandler(writer); await handler.Handle( new Ping() { Message = "Ping" }, default ); var result = builder.ToString(); result.ShouldContain("Ping Pong"); } } ================================================ FILE: test/MediatR.Tests/NotificationPublisherTests.cs ================================================ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using MediatR.NotificationPublishers; using Microsoft.Extensions.DependencyInjection; using Shouldly; using Xunit; using Xunit.Abstractions; namespace MediatR.Tests; public class NotificationPublisherTests { private readonly ITestOutputHelper _output; public NotificationPublisherTests(ITestOutputHelper output) => _output = output; public class Notification : INotification { } public class FirstHandler : INotificationHandler { public async Task Handle(Notification notification, CancellationToken cancellationToken) => await Task.Delay(500, cancellationToken); } public class SecondHandler : INotificationHandler { public async Task Handle(Notification notification, CancellationToken cancellationToken) => await Task.Delay(250, cancellationToken); } [Fact] public async Task Should_handle_sequentially_by_default() { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(); }); var serviceProvider = services.BuildServiceProvider(); var mediator = serviceProvider.GetRequiredService(); var timer = new Stopwatch(); timer.Start(); await mediator.Publish(new Notification()); timer.Stop(); var sequentialElapsed = timer.ElapsedMilliseconds; services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(); cfg.NotificationPublisherType = typeof(TaskWhenAllPublisher); }); serviceProvider = services.BuildServiceProvider(); mediator = serviceProvider.GetRequiredService(); timer.Restart(); await mediator.Publish(new Notification()); timer.Stop(); var parallelElapsed = timer.ElapsedMilliseconds; sequentialElapsed.ShouldBeGreaterThan(parallelElapsed); } } ================================================ FILE: test/MediatR.Tests/Pipeline/RequestExceptionActionTests.cs ================================================ using MediatR.Pipeline; namespace MediatR.Tests.Pipeline; public class RequestExceptionActionTests { public class Ping : IRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public abstract class PingPongException : Exception { protected PingPongException(string? message) : base(message + " Thrown") { } } public class PingException : PingPongException { public PingException(string? message) : base(message) { } } public class PongException : PingPongException { public PongException(string message) : base(message) { } } public class PingHandler : IRequestHandler { public Task Handle(Ping request, CancellationToken cancellationToken) { throw new PingException(request.Message); } } public class GenericExceptionAction : IRequestExceptionAction where TRequest : notnull { public int ExecutionCount { get; private set; } public Task Execute(TRequest request, Exception exception, CancellationToken cancellationToken) { ExecutionCount++; return Task.CompletedTask; } } public class PingPongExceptionAction : IRequestExceptionAction where TRequest : notnull { public bool Executed { get; private set; } public Task Execute(TRequest request, PingPongException exception, CancellationToken cancellationToken) { Executed = true; return Task.CompletedTask; } } public class PingExceptionAction : IRequestExceptionAction { public bool Executed { get; private set; } public Task Execute(Ping request, PingException exception, CancellationToken cancellationToken) { Executed = true; return Task.CompletedTask; } } public class PongExceptionAction : IRequestExceptionAction { public bool Executed { get; private set; } public Task Execute(Ping request, PongException exception, CancellationToken cancellationToken) { Executed = true; return Task.CompletedTask; } } [Fact] public async Task Should_run_all_exception_actions_that_match_base_type() { var pingExceptionAction = new PingExceptionAction(); var pongExceptionAction = new PongExceptionAction(); var pingPongExceptionAction = new PingPongExceptionAction(); var container = TestContainer.Create(cfg => { cfg.AddTransient, PingHandler>(); cfg.AddTransient>(_ => pingExceptionAction); cfg.AddTransient>(_ => pingPongExceptionAction); cfg.AddTransient>(_ => pongExceptionAction); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var request = new Ping { Message = "Ping!" }; await Assert.ThrowsAsync(() => mediator.Send(request)); pingExceptionAction.Executed.ShouldBeTrue(); pingPongExceptionAction.Executed.ShouldBeTrue(); pongExceptionAction.Executed.ShouldBeFalse(); } [Fact] public async Task Should_run_matching_exception_actions_only_once() { var genericExceptionAction = new GenericExceptionAction(); var container = TestContainer.Create(cfg => { cfg.AddTransient, PingHandler>(); cfg.AddTransient>(_ => genericExceptionAction); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var request = new Ping { Message = "Ping!" }; await Assert.ThrowsAsync(() => mediator.Send(request)); genericExceptionAction.ExecutionCount.ShouldBe(1); } } ================================================ FILE: test/MediatR.Tests/Pipeline/RequestExceptionHandlerTests.cs ================================================ namespace MediatR.Tests.Pipeline; using System; using System.Threading; using System.Threading.Tasks; using MediatR.Pipeline; using Shouldly; using Xunit; public class RequestExceptionHandlerTests { public class Ping : IRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public class PingException : Exception { public PingException(string? message) : base(message + " Thrown") { } } public class PingHandler : IRequestHandler { public Task Handle(Ping request, CancellationToken cancellationToken) { throw new PingException(request.Message); } } public class GenericPingExceptionHandler : IRequestExceptionHandler { public int ExecutionCount { get; private set; } public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { ExecutionCount++; return Task.CompletedTask; } } public class PingPongExceptionHandlerForType : IRequestExceptionHandler { public Task Handle(Ping request, PingException exception, RequestExceptionHandlerState state, CancellationToken cancellationToken) { state.SetHandled(new Pong() { Message = exception.Message + " Handled by Type" }); return Task.CompletedTask; } } public class PingPongExceptionHandler : IRequestExceptionHandler { public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken token) { state.SetHandled(new Pong() { Message = exception.Message + " Handled"}); return Task.CompletedTask; } } public class PingPongExceptionHandlerNotHandled : IRequestExceptionHandler { public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken token) { request.Message = exception.Message + " Not Handled"; return Task.CompletedTask; } } public class PingPongThrowingExceptionHandler : IRequestExceptionHandler { public Task Handle(Ping request, Exception exception, RequestExceptionHandlerState state, CancellationToken token) { throw new ApplicationException("Surprise!"); } } [Fact] public async Task Should_run_exception_handler_and_allow_for_exception_not_to_throw() { var container = TestContainer.Create(cfg => { cfg.AddTransient, PingHandler>(); cfg.AddTransient, PingPongExceptionHandler>(); cfg.AddTransient, PingPongExceptionHandlerForType>(); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Thrown Handled by Type"); } [Fact] public async Task Should_run_exception_handler_and_allow_for_exception_to_be_still_thrown() { var container = TestContainer.Create(cfg => { cfg.AddTransient, PingHandler>(); cfg.AddTransient, PingPongExceptionHandlerNotHandled>(); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var request = new Ping { Message = "Ping" }; await Should.ThrowAsync(async () => { await mediator.Send(request); }); request.Message.ShouldBe("Ping Thrown Not Handled"); } [Fact] public async Task Should_run_exception_handler_and_unwrap_expections_thrown_in_the_handler() { var container = TestContainer.Create(cfg => { cfg.AddTransient, PingHandler>(); cfg.AddTransient, PingPongThrowingExceptionHandler>(); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var request = new Ping { Message = "Ping" }; await Should.ThrowAsync(async () => { await mediator.Send(request); }); } [Fact] public async Task Should_run_matching_exception_handlers_only_once() { var genericPingExceptionHandler = new GenericPingExceptionHandler(); var container = TestContainer.Create(cfg => { cfg.AddTransient, PingHandler>(); cfg.AddSingleton>(genericPingExceptionHandler); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var request = new Ping { Message = "Ping" }; await Should.ThrowAsync(async () => { await mediator.Send(request); }); genericPingExceptionHandler.ExecutionCount.ShouldBe(1); } } ================================================ FILE: test/MediatR.Tests/Pipeline/RequestPostProcessorTests.cs ================================================ using System.Threading; namespace MediatR.Tests.Pipeline; using System.Threading.Tasks; using MediatR.Pipeline; using Shouldly; using Xunit; public class RequestPostProcessorTests { public class Ping : IRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public class PingHandler : IRequestHandler { public Task Handle(Ping request, CancellationToken cancellationToken) { return Task.FromResult(new Pong { Message = request.Message + " Pong" }); } } public class PingPongPostProcessor : IRequestPostProcessor { public Task Process(Ping request, Pong response, CancellationToken cancellationToken) { response.Message = response.Message + " " + request.Message; return Task.FromResult(0); } } [Fact] public async Task Should_run_postprocessors() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf()).AsImplementedInterfaces(); }); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong Ping"); } } ================================================ FILE: test/MediatR.Tests/Pipeline/RequestPreProcessorTests.cs ================================================ using System.Threading; namespace MediatR.Tests.Pipeline; using System.Threading.Tasks; using MediatR.Pipeline; using Shouldly; using Xunit; public class RequestPreProcessorTests { public class Ping : IRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public class PingHandler : IRequestHandler { public Task Handle(Ping request, CancellationToken cancellationToken) { return Task.FromResult(new Pong { Message = request.Message + " Pong" }); } } public class PingPreProcessor : IRequestPreProcessor { public Task Process(Ping request, CancellationToken cancellationToken) { request.Message = request.Message + " Ping"; return Task.FromResult(0); } } [Fact] public async Task Should_run_preprocessors() { var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf()).AsImplementedInterfaces(); }); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Ping Pong"); } } ================================================ FILE: test/MediatR.Tests/Pipeline/Streams/StreamPipelineBehaviorTests.cs ================================================ using System.Threading; using MediatR.Registration; using Microsoft.Extensions.DependencyInjection; namespace MediatR.Tests.Pipeline.Streams; using Shouldly; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Xunit; public class StreamPipelineBehaviorTests { public class Sing : IStreamRequest { public string? Message { get; set; } } public class Song { public string? Message { get; set; } } public class SingHandler : IStreamRequestHandler { public async IAsyncEnumerable Handle(Sing request, [EnumeratorCancellation] CancellationToken cancellationToken) { yield return await Task.Run(() => new Song { Message = request.Message + " Song" }); yield return await Task.Run(() => new Song { Message = request.Message + " Sang" }); yield return await Task.Run(() => new Song { Message = request.Message + " Seng" }); } } public class SingSongPipelineBehavior : IStreamPipelineBehavior { public async IAsyncEnumerable Handle(Sing request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { yield return new Song { Message = "Start behaving..." }; await foreach (var item in next().WithCancellation(cancellationToken).ConfigureAwait(false)) { yield return item; } yield return new Song { Message = "...Ready behaving" }; } } [Fact] public async Task Should_run_pipeline_behavior() { var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(opts => { opts.RegisterServicesFromAssemblyContaining(); opts.AddStreamBehavior(); }); var container = services.BuildServiceProvider(); var mediator = container.GetRequiredService(); var responses = mediator.CreateStream(new Sing { Message = "Sing" }); int i = 0; await foreach (var response in responses) { if (i == 0) { response.Message.ShouldBe("Start behaving..."); } else if (i == 1) { response.Message.ShouldBe("Sing Song"); } else if (i == 2) { response.Message.ShouldBe("Sing Sang"); } else if (i == 3) { response.Message.ShouldBe("Sing Seng"); } else if (i == 4) { response.Message.ShouldBe("...Ready behaving"); } (++i).ShouldBeLessThanOrEqualTo(5); } } } ================================================ FILE: test/MediatR.Tests/PipelineTests.cs ================================================ using System.Threading; namespace MediatR.Tests; using System.Collections.Generic; using System.Threading.Tasks; using Shouldly; using Xunit; public class PipelineTests { public class Ping : IRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public class VoidPing : IRequest { public string? Message { get; set; } } public class Zing : IRequest { public string? Message { get; set; } } public class Zong { public string? Message { get; set; } } public class PingHandler : IRequestHandler { private readonly Logger _output; public PingHandler(Logger output) { _output = output; } public Task Handle(Ping request, CancellationToken cancellationToken) { _output.Messages.Add("Handler"); return Task.FromResult(new Pong { Message = request.Message + " Pong" }); } } public class VoidPingHandler : IRequestHandler { private readonly Logger _output; public VoidPingHandler(Logger output) { _output = output; } public Task Handle(VoidPing request, CancellationToken cancellationToken) { _output.Messages.Add("Handler"); return Task.CompletedTask; } } public class ZingHandler : IRequestHandler { private readonly Logger _output; public ZingHandler(Logger output) { _output = output; } public Task Handle(Zing request, CancellationToken cancellationToken) { _output.Messages.Add("Handler"); return Task.FromResult(new Zong { Message = request.Message + " Zong" }); } } public class OuterBehavior : IPipelineBehavior { private readonly Logger _output; public OuterBehavior(Logger output) { _output = output; } public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Outer before"); var response = await next(); _output.Messages.Add("Outer after"); return response; } } public class OuterVoidBehavior : IPipelineBehavior { private readonly Logger _output; public OuterVoidBehavior(Logger output) { _output = output; } public async Task Handle(VoidPing request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Outer before"); var response = await next(); _output.Messages.Add("Outer after"); return response; } } public class InnerBehavior : IPipelineBehavior { private readonly Logger _output; public InnerBehavior(Logger output) { _output = output; } public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Inner before"); var response = await next(); _output.Messages.Add("Inner after"); return response; } } public class InnerVoidBehavior : IPipelineBehavior { private readonly Logger _output; public InnerVoidBehavior(Logger output) { _output = output; } public async Task Handle(VoidPing request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Inner before"); var response = await next(); _output.Messages.Add("Inner after"); return response; } } public class InnerBehavior : IPipelineBehavior where TRequest : notnull { private readonly Logger _output; public InnerBehavior(Logger output) { _output = output; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Inner generic before"); var response = await next(); _output.Messages.Add("Inner generic after"); return response; } } public class OuterBehavior : IPipelineBehavior where TRequest : notnull { private readonly Logger _output; public OuterBehavior(Logger output) { _output = output; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Outer generic before"); var response = await next(); _output.Messages.Add("Outer generic after"); return response; } } public class ConstrainedBehavior : IPipelineBehavior where TRequest : Ping where TResponse : Pong { private readonly Logger _output; public ConstrainedBehavior(Logger output) { _output = output; } public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Constrained before"); var response = await next(); _output.Messages.Add("Constrained after"); return response; } } public class ConcreteBehavior : IPipelineBehavior { private readonly Logger _output; public ConcreteBehavior(Logger output) { _output = output; } public async Task Handle(Ping request, RequestHandlerDelegate next, CancellationToken cancellationToken) { _output.Messages.Add("Concrete before"); var response = await next(); _output.Messages.Add("Concrete after"); return response; } } public class Logger { public IList Messages { get; } = new List(); } [Fact] public async Task Should_wrap_with_behavior() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient, OuterBehavior>(); cfg.AddTransient, InnerBehavior>(); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new [] { "Outer before", "Inner before", "Handler", "Inner after", "Outer after" }); } [Fact] public async Task Should_wrap_void_with_behavior() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient, OuterVoidBehavior>(); cfg.AddTransient, InnerVoidBehavior>(); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await mediator.Send(new VoidPing { Message = "Ping" }); output.Messages.ShouldBe(new [] { "Outer before", "Inner before", "Handler", "Inner after", "Outer after" }); } [Fact] public async Task Should_wrap_generics_with_behavior() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } [Fact] public async Task Should_wrap_void_generics_with_behavior() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await mediator.Send(new VoidPing { Message = "Ping" }); output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } [Fact] public async Task Should_handle_constrained_generics() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(ConstrainedBehavior<,>)); cfg.AddTransient(); }); //container.GetAllInstances>(); var mediator = container.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Constrained before", "Handler", "Constrained after", "Inner generic after", "Outer generic after", }); output.Messages.Clear(); var zingResponse = await mediator.Send(new Zing { Message = "Zing" }); zingResponse.Message.ShouldBe("Zing Zong"); output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } [Fact(Skip = "Lamar does not mix concrete and open generics. Use constraints instead.")] public async Task Should_handle_concrete_and_open_generics() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf()).AsImplementedInterfaces() .AddClasses(t => t.AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(OuterBehavior<,>)); cfg.AddTransient(typeof(IPipelineBehavior<,>), typeof(InnerBehavior<,>)); cfg.AddTransient(typeof(IPipelineBehavior), typeof(ConcreteBehavior)); cfg.AddTransient(); }); //container.GetAllInstances>(); var mediator = container.GetRequiredService(); var response = await mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Concrete before", "Handler", "Concrete after", "Inner generic after", "Outer generic after", }); output.Messages.Clear(); var zingResponse = await mediator.Send(new Zing { Message = "Zing" }); zingResponse.Message.ShouldBe("Zing Zong"); output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } } ================================================ FILE: test/MediatR.Tests/PublishTests.cs ================================================ using System.Threading; namespace MediatR.Tests; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Shouldly; using Xunit; public class PublishTests { public class Ping : INotification { public string? Message { get; set; } } public class PongHandler : INotificationHandler { private readonly TextWriter _writer; public PongHandler(TextWriter writer) { _writer = writer; } public Task Handle(Ping notification, CancellationToken cancellationToken) { return _writer.WriteLineAsync(notification.Message + " Pong"); } } public class PungHandler : INotificationHandler { private readonly TextWriter _writer; public PungHandler(TextWriter writer) { _writer = writer; } public Task Handle(Ping notification, CancellationToken cancellationToken) { return _writer.WriteLineAsync(notification.Message + " Pung"); } } [Fact] public async Task Should_resolve_main_handler() { var builder = new StringBuilder(); var writer = new StringWriter(builder); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(INotificationHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(writer); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await mediator.Publish(new Ping { Message = "Ping" }); var result = builder.ToString().Split(new [] {Environment.NewLine}, StringSplitOptions.None); result.ShouldContain("Ping Pong"); result.ShouldContain("Ping Pung"); } [Fact] public async Task Should_resolve_main_handler_when_object_is_passed() { var builder = new StringBuilder(); var writer = new StringWriter(builder); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(INotificationHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(writer); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); object message = new Ping { Message = "Ping" }; await mediator.Publish(message); var result = builder.ToString().Split(new [] {Environment.NewLine}, StringSplitOptions.None); result.ShouldContain("Ping Pong"); result.ShouldContain("Ping Pung"); } public class SequentialMediator : Mediator { public SequentialMediator(IServiceProvider serviceProvider) : base(serviceProvider) { } protected override async Task PublishCore(IEnumerable allHandlers, INotification notification, CancellationToken cancellationToken) { foreach (var handler in allHandlers) { await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); } } } public class SequentialPublisher : INotificationPublisher { public int CallCount { get; set; } public async Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken) { foreach (var handler in handlerExecutors) { await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false); CallCount++; } } } [Fact] public async Task Should_override_with_sequential_firing() { var builder = new StringBuilder(); var writer = new StringWriter(builder); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(INotificationHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(writer); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await mediator.Publish(new Ping { Message = "Ping" }); var result = builder.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); result.ShouldContain("Ping Pong"); result.ShouldContain("Ping Pung"); } [Fact] public async Task Should_override_with_sequential_firing_through_injection() { var builder = new StringBuilder(); var writer = new StringWriter(builder); var publisher = new SequentialPublisher(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(INotificationHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(writer); cfg.AddSingleton(publisher); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await mediator.Publish(new Ping { Message = "Ping" }); var result = builder.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); result.ShouldContain("Ping Pong"); result.ShouldContain("Ping Pung"); publisher.CallCount.ShouldBe(2); } [Fact] public async Task Should_resolve_handlers_given_interface() { var builder = new StringBuilder(); var writer = new StringWriter(builder); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(INotificationHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(writer); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); // wrap notifications in an array, so this test won't break on a 'replace with var' refactoring var notifications = new INotification[] { new Ping { Message = "Ping" } }; await mediator.Publish(notifications[0]); var result = builder.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); result.ShouldContain("Ping Pong"); result.ShouldContain("Ping Pung"); } [Fact] public async Task Should_resolve_main_handler_by_specific_interface() { var builder = new StringBuilder(); var writer = new StringWriter(builder); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(INotificationHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(writer); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await mediator.Publish(new Ping { Message = "Ping" }); var result = builder.ToString().Split(new [] {Environment.NewLine}, StringSplitOptions.None); result.ShouldContain("Ping Pong"); result.ShouldContain("Ping Pung"); } } ================================================ FILE: test/MediatR.Tests/SendTests.cs ================================================ using System.Threading; using System; using System.Threading.Tasks; using Shouldly; using Xunit; using Microsoft.Extensions.DependencyInjection; using System.Reflection; using MediatR.Pipeline; namespace MediatR.Tests; public class SendTests { private readonly IServiceProvider _serviceProvider; private Dependency _dependency; private readonly IMediator _mediator; public SendTests() { _dependency = new Dependency(); var services = new ServiceCollection(); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(typeof(Ping).Assembly); cfg.AddOpenBehavior(typeof(TimeoutBehavior<,>), ServiceLifetime.Transient); cfg.RegisterGenericHandlers = true; }); services.AddSingleton(_dependency); _serviceProvider = services.BuildServiceProvider(); _mediator = _serviceProvider.GetService()!; } public class Ping : IRequest { public string? Message { get; set; } } public class VoidPing : IRequest { } public class Pong { public string? Message { get; set; } } public class PingHandler : IRequestHandler { public Task Handle(Ping request, CancellationToken cancellationToken) { return Task.FromResult(new Pong { Message = request.Message + " Pong" }); } } public class Dependency { public bool Called { get; set; } public bool CalledSpecific { get; set; } } public class VoidPingHandler : IRequestHandler { private readonly Dependency _dependency; public VoidPingHandler(Dependency dependency) => _dependency = dependency; public Task Handle(VoidPing request, CancellationToken cancellationToken) { _dependency.Called = true; return Task.CompletedTask; } } public class GenericPing : IRequest where T : Pong { public T? Pong { get; set; } } public class GenericPingHandler : IRequestHandler, T> where T : Pong { private readonly Dependency _dependency; public GenericPingHandler(Dependency dependency) => _dependency = dependency; public Task Handle(GenericPing request, CancellationToken cancellationToken) { _dependency.Called = true; request.Pong!.Message += " Pong"; return Task.FromResult(request.Pong!); } } public class VoidGenericPing : IRequest where T : Pong { } public class VoidGenericPingHandler : IRequestHandler> where T : Pong { private readonly Dependency _dependency; public VoidGenericPingHandler(Dependency dependency) => _dependency = dependency; public Task Handle(VoidGenericPing request, CancellationToken cancellationToken) { _dependency.Called = true; return Task.CompletedTask; } } public class PongExtension : Pong { } public class TestClass1PingRequestHandler : IRequestHandler> { private readonly Dependency _dependency; public TestClass1PingRequestHandler(Dependency dependency) => _dependency = dependency; public Task Handle(VoidGenericPing request, CancellationToken cancellationToken) { _dependency.CalledSpecific = true; return Task.CompletedTask; } } public interface ITestInterface1 { } public interface ITestInterface2 { } public interface ITestInterface3 { } public class TestClass1 : ITestInterface1 { } public class TestClass2 : ITestInterface2 { } public class TestClass3 : ITestInterface3 { } public class MultipleGenericTypeParameterRequest : IRequest where T1 : ITestInterface1 where T2 : ITestInterface2 where T3 : ITestInterface3 { public int Foo { get; set; } } public class MultipleGenericTypeParameterRequestHandler : IRequestHandler, int> where T1 : ITestInterface1 where T2 : ITestInterface2 where T3 : ITestInterface3 { private readonly Dependency _dependency; public MultipleGenericTypeParameterRequestHandler(Dependency dependency) => _dependency = dependency; public Task Handle(MultipleGenericTypeParameterRequest request, CancellationToken cancellationToken) { _dependency.Called = true; return Task.FromResult(1); } } public class TimeoutBehavior : IPipelineBehavior where TRequest : notnull { public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { using (var cts = new CancellationTokenSource(500)) { return await next(cts.Token); } } } public class TimeoutRequest : IRequest { } public class TimeoutRequest2 : IRequest { } public class TimeoutRequestHandler : IRequestHandler { private readonly Dependency _dependency; public TimeoutRequestHandler(Dependency dependency) => _dependency = dependency; public async Task Handle(TimeoutRequest request, CancellationToken cancellationToken) { await Task.Delay(2000, cancellationToken); _dependency.Called = true; } } public class TimeoutRequest2Handler : IRequestHandler { private readonly Dependency _dependency; public TimeoutRequest2Handler(Dependency dependency) => _dependency = dependency; public async Task Handle(TimeoutRequest2 request, CancellationToken cancellationToken) { await Task.Delay(2000, cancellationToken); _dependency.Called = true; return 1; } } [Fact] public async Task Should_resolve_main_handler() { var response = await _mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); } [Fact] public async Task Should_resolve_main_void_handler() { await _mediator.Send(new VoidPing()); _dependency.Called.ShouldBeTrue(); } [Fact] public async Task Should_resolve_main_handler_via_dynamic_dispatch() { object request = new Ping { Message = "Ping" }; var response = await _mediator.Send(request); var pong = response.ShouldBeOfType(); pong.Message.ShouldBe("Ping Pong"); } [Fact] public async Task Should_resolve_main_void_handler_via_dynamic_dispatch() { object request = new VoidPing(); var response = await _mediator.Send(request); response.ShouldBeOfType(); _dependency.Called.ShouldBeTrue(); } [Fact] public async Task Should_resolve_main_handler_by_specific_interface() { var response = await _mediator.Send(new Ping { Message = "Ping" }); response.Message.ShouldBe("Ping Pong"); } [Fact] public async Task Should_resolve_main_handler_by_given_interface() { // wrap requests in an array, so this test won't break on a 'replace with var' refactoring var requests = new IRequest[] { new VoidPing() }; await _mediator.Send(requests[0]); _dependency.Called.ShouldBeTrue(); } [Fact] public Task Should_raise_execption_on_null_request() => Should.ThrowAsync(async () => await _mediator.Send(default!)); [Fact] public async Task Should_resolve_generic_handler() { var request = new GenericPing { Pong = new Pong { Message = "Ping" } }; var result = await _mediator.Send(request); var pong = result.ShouldBeOfType(); pong.Message.ShouldBe("Ping Pong"); _dependency.Called.ShouldBeTrue(); } [Fact] public async Task Should_resolve_generic_void_handler() { var request = new VoidGenericPing(); await _mediator.Send(request); _dependency.Called.ShouldBeTrue(); } [Fact] public async Task Should_resolve_multiple_type_parameter_generic_handler() { var request = new MultipleGenericTypeParameterRequest(); await _mediator.Send(request); _dependency.Called.ShouldBeTrue(); } [Fact] public async Task Should_resolve_closed_handler_if_defined() { var dependency = new Dependency(); var services = new ServiceCollection(); services.AddSingleton(dependency); services.AddFakeLogging(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(Assembly.GetExecutingAssembly()); cfg.RegisterGenericHandlers = true; }); services.AddTransient>,TestClass1PingRequestHandler>(); var serviceProvider = services.BuildServiceProvider(); var mediator = serviceProvider.GetService()!; var request = new VoidGenericPing(); await mediator.Send(request); dependency.Called.ShouldBeFalse(); dependency.CalledSpecific.ShouldBeTrue(); } [Fact] public async Task Should_resolve_open_handler_if_not_defined() { var dependency = new Dependency(); var services = new ServiceCollection(); services.AddFakeLogging(); services.AddSingleton(dependency); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblies(Assembly.GetExecutingAssembly()); cfg.RegisterGenericHandlers = true; }); services.AddTransient>, TestClass1PingRequestHandler>(); var serviceProvider = services.BuildServiceProvider(); var mediator = serviceProvider.GetService()!; var request = new VoidGenericPing(); await mediator.Send(request); dependency.Called.ShouldBeTrue(); dependency.CalledSpecific.ShouldBeFalse(); } [Fact] public async Task TimeoutBehavior_Void_Should_Cancel_Long_Running_Task_And_Throw_Exception() { var request = new TimeoutRequest(); var exception = await Should.ThrowAsync(() => _mediator.Send(request)); exception.ShouldNotBeNull(); exception.ShouldBeAssignableTo(); _dependency.Called.ShouldBeFalse(); } [Fact] public async Task TimeoutBehavior_NonVoid_Should_Cancel_Long_Running_Task_And_Throw_Exception() { var request = new TimeoutRequest2(); int result = 0; var exception = await Should.ThrowAsync(async () => { result = await _mediator.Send(request); }); exception.ShouldNotBeNull(); exception.ShouldBeAssignableTo(); _dependency.Called.ShouldBeFalse(); result.ShouldBe(0); } } ================================================ FILE: test/MediatR.Tests/SendVoidInterfaceTests.cs ================================================ using System.Threading; namespace MediatR.Tests; using System.IO; using System.Text; using System.Threading.Tasks; using Shouldly; using Xunit; public class SendVoidInterfaceTests { public class Ping : IRequest { public string? Message { get; set; } } public class PingHandler : IRequestHandler { private readonly TextWriter _writer; public PingHandler(TextWriter writer) => _writer = writer; public Task Handle(Ping request, CancellationToken cancellationToken) => _writer.WriteAsync(request.Message + " Pong"); } [Fact] public async Task Should_resolve_main_void_handler() { var builder = new StringBuilder(); var writer = new StringWriter(builder); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IRequestHandler<>))).AsImplementedInterfaces(); }); cfg.AddSingleton(writer); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await mediator.Send(new Ping { Message = "Ping" }); builder.ToString().ShouldBe("Ping Pong"); } } ================================================ FILE: test/MediatR.Tests/ServiceFactoryTests.cs ================================================ using System; using System.Collections; using System.Linq; using System.Threading.Tasks; using MediatR.Licensing; using MediatR.Registration; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace MediatR.Tests; [CollectionDefinition(nameof(ServiceFactoryCollectionBehavior), DisableParallelization = true)] public class ServiceFactoryCollectionBehavior {} [Collection(nameof(ServiceFactoryCollectionBehavior))] public class ServiceFactoryTests { public class Ping : IRequest { } public class Pong { public string? Message { get; set; } } [Fact] public async Task Should_throw_given_no_handler() { MediatRServiceCollectionExtensions.LicenseChecked = false; var serviceCollection = new ServiceCollection(); ServiceRegistrar.AddRequiredServices(serviceCollection, new MediatRServiceConfiguration()); serviceCollection.AddFakeLogging(); var serviceProvider = serviceCollection.BuildServiceProvider(); var mediator = new Mediator(serviceProvider); await Assert.ThrowsAsync( () => mediator.Send(new Ping()) ); } [Fact] public void Should_not_throw_with_manual_registration() { MediatRServiceCollectionExtensions.LicenseChecked = false; var services = new ServiceCollection(); services.AddFakeLogging(); services.AddTransient(); var config = new MediatRServiceConfiguration(); services.AddSingleton(config); services.AddSingleton(); services.AddSingleton(); var container = services.BuildServiceProvider(); Should.NotThrow(() => container.GetRequiredService()); } [Fact] public void Should_throw_when_missing_required_configuration() { MediatRServiceCollectionExtensions.LicenseChecked = false; var services = new ServiceCollection(); services.AddFakeLogging(); services.AddTransient(); var container = services.BuildServiceProvider(); Should.Throw(() => container.GetRequiredService()); } } ================================================ FILE: test/MediatR.Tests/StreamPipelineTests.cs ================================================ using System.Threading; namespace MediatR.Tests; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Shouldly; using Xunit; public class StreamPipelineTests { public class Ping : IStreamRequest { public string? Message { get; set; } } public class Pong { public string? Message { get; set; } } public class Zing : IStreamRequest { public string? Message { get; set; } } public class Zong { public string? Message { get; set; } } public class PingHandler : IStreamRequestHandler { private readonly Logger _output; public PingHandler(Logger output) { _output = output; } public async IAsyncEnumerable Handle(Ping request, [EnumeratorCancellation]CancellationToken cancellationToken) { _output.Messages.Add("Handler"); yield return await Task.FromResult(new Pong { Message = request.Message + " Pong" }); } } public class ZingHandler : IStreamRequestHandler { private readonly Logger _output; public ZingHandler(Logger output) { _output = output; } public async IAsyncEnumerable Handle(Zing request, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Handler"); yield return await Task.FromResult(new Zong { Message = request.Message + " Zong" }); } } public class OuterBehavior : IStreamPipelineBehavior { private readonly Logger _output; public OuterBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(Ping request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Outer before"); await foreach (var result in next()) { yield return result; } _output.Messages.Add("Outer after"); } } public class InnerBehavior : IStreamPipelineBehavior { private readonly Logger _output; public InnerBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(Ping request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Inner before"); await foreach (var result in next()) { yield return result; } _output.Messages.Add("Inner after"); } } public class InnerBehavior : IStreamPipelineBehavior where TRequest : IStreamRequest { private readonly Logger _output; public InnerBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(TRequest request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Inner generic before"); await foreach (var result in next()) { yield return result; } _output.Messages.Add("Inner generic after"); } } public class OuterBehavior : IStreamPipelineBehavior where TRequest : IStreamRequest { private readonly Logger _output; public OuterBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(TRequest request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Outer generic before"); await foreach (var result in next()) { yield return result; } _output.Messages.Add("Outer generic after"); } } public class ConstrainedBehavior : IStreamPipelineBehavior where TRequest : Ping, IStreamRequest where TResponse : Pong { private readonly Logger _output; public ConstrainedBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(TRequest request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Constrained before"); await foreach (var result in next()) { yield return result; } _output.Messages.Add("Constrained after"); } } public class ConcreteBehavior : IStreamPipelineBehavior { private readonly Logger _output; public ConcreteBehavior(Logger output) { _output = output; } public async IAsyncEnumerable Handle(Ping request, StreamHandlerDelegate next, [EnumeratorCancellation] CancellationToken cancellationToken) { _output.Messages.Add("Concrete before"); await foreach (var result in next()) { yield return result; } _output.Messages.Add("Concrete after"); } } public class Logger { public IList Messages { get; } = new List(); } [Fact] public async Task Should_wrap_with_behavior() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IStreamRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient, OuterBehavior>(); cfg.AddTransient, InnerBehavior>(); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await foreach(var response in mediator.CreateStream(new Ping { Message = "Ping" })) { response.Message.ShouldBe("Ping Pong"); } output.Messages.ShouldBe(new [] { "Outer before", "Inner before", "Handler", "Inner after", "Outer after" }); } [Fact] public async Task Should_wrap_generics_with_behavior() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IStreamRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient(typeof(IStreamPipelineBehavior<,>), typeof(OuterBehavior<,>)); cfg.AddTransient(typeof(IStreamPipelineBehavior<,>), typeof(InnerBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await foreach (var response in mediator.CreateStream(new Ping { Message = "Ping" })) { response.Message.ShouldBe("Ping Pong"); } output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } [Fact] public async Task Should_handle_constrained_generics() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf().AssignableTo(typeof(IStreamRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient(typeof(IStreamPipelineBehavior<,>), typeof(OuterBehavior<,>)); cfg.AddTransient(typeof(IStreamPipelineBehavior<,>), typeof(InnerBehavior<,>)); cfg.AddTransient(typeof(IStreamPipelineBehavior<,>), typeof(ConstrainedBehavior<,>)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await foreach (var response in mediator.CreateStream(new Ping { Message = "Ping" })) { response.Message.ShouldBe("Ping Pong"); } output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Constrained before", "Handler", "Constrained after", "Inner generic after", "Outer generic after", }); output.Messages.Clear(); await foreach (var response in mediator.CreateStream(new Zing { Message = "Zing" })) { response.Message.ShouldBe("Zing Zong"); } output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } [Fact(Skip = "Lamar does not mix concrete and open generics. Use constraints instead.")] public async Task Should_handle_concrete_and_open_generics() { var output = new Logger(); var container = TestContainer.Create(cfg => { cfg.Scan(scanner => { scanner.FromAssemblyOf() .AddClasses(t => t.InNamespaceOf()).AsImplementedInterfaces() .AddClasses(t => t.AssignableTo(typeof(IStreamRequestHandler<,>))).AsImplementedInterfaces(); }); cfg.AddSingleton(output); cfg.AddTransient(typeof(IStreamPipelineBehavior<,>), typeof(OuterBehavior<,>)); cfg.AddTransient(typeof(IStreamPipelineBehavior<,>), typeof(InnerBehavior<,>)); cfg.AddTransient(typeof(IStreamPipelineBehavior), typeof(ConcreteBehavior)); cfg.AddTransient(); }); var mediator = container.GetRequiredService(); await foreach (var response in mediator.CreateStream(new Ping { Message = "Ping" })) { response.Message.ShouldBe("Ping Pong"); } output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Concrete before", "Handler", "Concrete after", "Inner generic after", "Outer generic after", }); output.Messages.Clear(); await foreach (var response in mediator.CreateStream(new Zing { Message = "Zing" })) { response.Message.ShouldBe("Zing Zong"); } output.Messages.ShouldBe(new[] { "Outer generic before", "Inner generic before", "Handler", "Inner generic after", "Outer generic after", }); } } ================================================ FILE: test/MediatR.Tests/TestContainer.cs ================================================ using MediatR.Licensing; using MediatR.Registration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace MediatR.Tests; public static class TestContainer { public static IServiceProvider Create(Action config) { var services = new ServiceCollection(); ConfigAction(services); var container = services.BuildServiceProvider(); return container; void ConfigAction(ServiceCollection cfg) { cfg.AddSingleton(); ServiceRegistrar.AddRequiredServices(cfg, new MediatRServiceConfiguration()); config(cfg); } } } ================================================ FILE: test/MediatR.Tests/UnitTests.cs ================================================ using System; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace MediatR.Tests; public class UnitTests { [Fact] public async Task Should_be_equal_to_each_other() { var unit1 = Unit.Value; var unit2 = await Unit.Task; Assert.Equal(unit1, unit2); Assert.True(unit1 == unit2); Assert.False(unit1 != unit2); } [Fact] public void Should_be_equitable() { var dictionary = new Dictionary { {new Unit(), "value"}, }; Assert.Equal("value", dictionary[default]); } [Fact] public void Should_tostring() { var unit = Unit.Value; Assert.Equal("()", unit.ToString()); } [Fact] public void Should_compareto_as_zero() { var unit1 = new Unit(); var unit2 = new Unit(); Assert.Equal(0, unit1.CompareTo(unit2)); } public static object[][] ValueData() { return new[] { new object[] {new object(), false}, new object[] {"", false}, new object[] {"()", false}, new object[] {null!, false}, new object[] {new Uri("https://www.google.com"), false}, new object[] {new Unit(), true}, new object[] {Unit.Value, true}, new object[] {Unit.Task.Result, true}, new object[] {default(Unit), true}, }; } public static object[][] CompareToValueData() => ValueData().Select(objects => new[] { objects[0] }).ToArray(); [Theory] [MemberData(nameof(ValueData))] public void Should_be_equal(object value, bool isEqual) { var unit1 = Unit.Value; if (isEqual) Assert.True(unit1.Equals(value)); else Assert.False(unit1.Equals(value)); } [Theory] [MemberData(nameof(CompareToValueData))] public void Should_compareto_value_as_zero(object value) { var unit1 = new Unit(); Assert.Equal(0, ((IComparable)unit1).CompareTo(value)); } }