Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This repository offers a wide collection of .NET packages for use in microservic
- [NHibernate](#NHibernate)
- [Notifications](#Notifications)
- [Notifications Audit](#Notifications-Audit)
- [Mediation](#Custom-Mediation)

## RabbitMQ

Expand Down Expand Up @@ -531,3 +532,37 @@ services

Thats all - db schema and tables will be generated on application start (you can customize schema and table names on DI
step).

# Custom Mediation

## Purpose
A small in-process mediator. Replaces MediatR-style usage with a minimal API surface.

Exposed types:
- `IMediator`
- `IRequest<TResponse>`
- `IRequestHandler<TRequest, TResponse>`
- `IPipelineBehavior<TRequest, TResponse>`
- One DI entry point: `AddMediation(...)`

Goal: presentation layer only does `await mediator.Send(request)` and never touches application logic.
## DI registration

### Add mediation once (composition root)
Call `AddMediation` . Pass assemblies that contain handlers and pipeline behaviors.

```C#
builder.Services.AddMediation(typeof(DependencyInjection).Assembly);
```
### Pipeline behaviors

Register pipeline behaviors as open generics. Order matters.

```C#
// Outer → inner
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ExceptionMappingBehavior<,>));
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));
```
~~~~
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
10 changes: 10 additions & 0 deletions src/Bss.Platform.Mediation.Abstractions/IMediator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Bss.Platform.Mediation.Abstractions;

public interface IMediator
{
Task<TResult> Send<TRequest, TResult>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest<TResult>;

Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest;
}
17 changes: 17 additions & 0 deletions src/Bss.Platform.Mediation.Abstractions/IPipelineBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Bss.Platform.Mediation.Abstractions;

public interface IPipelineBehavior<TRequest, TResult>
{
Task<TResult> Handle(
TRequest request,
CancellationToken ct,
Func<TRequest, CancellationToken, Task<TResult>> next);
}

public interface IPipelineBehavior<TRequest>
{
Task Handle(
TRequest request,
CancellationToken ct,
Func<TRequest, CancellationToken, Task> next);
}
4 changes: 4 additions & 0 deletions src/Bss.Platform.Mediation.Abstractions/IRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace Bss.Platform.Mediation.Abstractions;

public interface IRequest<out TResult>;
Comment thread
ivan-luxoft marked this conversation as resolved.
public interface IRequest;
13 changes: 13 additions & 0 deletions src/Bss.Platform.Mediation.Abstractions/IRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Bss.Platform.Mediation.Abstractions;

public interface IRequestHandler<in TRequest, TResult>
where TRequest : IRequest<TResult>
{
Task<TResult> Handle(TRequest request, CancellationToken cancellationToken);
}

public interface IRequestHandler<in TRequest>
where TRequest : IRequest
{
Task Handle(TRequest request, CancellationToken cancellationToken);
}
18 changes: 18 additions & 0 deletions src/Bss.Platform.Mediation/Bss.Platform.Mediation.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Bss.Platform.Mediation.Abstractions\Bss.Platform.Mediation.Abstractions.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Scrutor" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions src/Bss.Platform.Mediation/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Reflection;

using Bss.Platform.Mediation.Abstractions;

using Microsoft.Extensions.DependencyInjection;

namespace Bss.Platform.Mediation;

public static class DependencyInjection
{
public static IServiceCollection AddMediation(
this IServiceCollection services,
params Assembly[] assemblies)
{
services.AddScoped<IMediator, Mediator>();

services.Scan(s => s
.FromAssemblies(assemblies)
.AddClasses(c => c.AssignableTo(typeof(IRequestHandler<,>)))
.AsImplementedInterfaces()
.WithScopedLifetime()
.AddClasses(c => c.AssignableTo(typeof(IPipelineBehavior<,>)))
.AsImplementedInterfaces()
.WithScopedLifetime());

return services;
}
}
47 changes: 47 additions & 0 deletions src/Bss.Platform.Mediation/Mediator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.Extensions.DependencyInjection;

using Bss.Platform.Mediation.Abstractions;

namespace Bss.Platform.Mediation;

public record Mediator(IServiceProvider ServiceProvider) : IMediator
{
public Task<TResult> Send<TRequest, TResult>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest<TResult>
{
var handler = this.ServiceProvider.GetRequiredService<IRequestHandler<TRequest, TResult>>();
var behaviors = this.GetBehaviors<IPipelineBehavior<TRequest, TResult>>();

Func<TRequest, CancellationToken, Task<TResult>> next =
(r, ct) => handler.Handle(r, ct);
foreach (var behavior in behaviors)
{
var prev = next;
next = (r, ct) => behavior.Handle(r, ct, prev);
}

return next(request, cancellationToken);
}

public Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default)
where TRequest : IRequest
{
var handler = this.ServiceProvider.GetRequiredService<IRequestHandler<TRequest>>();
var behaviors = this.GetBehaviors<IPipelineBehavior<TRequest>>();

Func<TRequest, CancellationToken, Task> next = (r, ct) => handler.Handle(r, ct);
foreach (var behavior in behaviors)
{
var prev = next;
next = (r, ct) => behavior.Handle(r, ct, prev);
}

return next(request, cancellationToken);
}

private TInterface[] GetBehaviors<TInterface>() =>
this.ServiceProvider
.GetServices<TInterface>()
.Reverse()
.ToArray();
}
16 changes: 16 additions & 0 deletions src/Bss.Platform.sln
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bss.Platform.Notifications.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bss.Platform.RabbitMq.JsonSchemaGenerator", "Bss.Platform.RabbitMq.JsonSchemaGenerator\Bss.Platform.RabbitMq.JsonSchemaGenerator.csproj", "{0FC70E24-62A9-44BF-A433-804C98514A09}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mediation", "Mediation", "{40F06493-58CE-4E9D-BBB4-771BD26A9658}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bss.Platform.Mediation", "Bss.Platform.Mediation\Bss.Platform.Mediation.csproj", "{393C3A3D-E55F-4006-81A7-ED1CB3FAC1FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bss.Platform.Mediation.Abstractions", "Bss.Platform.Mediation.Abstractions\Bss.Platform.Mediation.Abstractions.csproj", "{9EE0DF52-2C67-4DD1-AA9D-64ABC5BD00D7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -61,6 +67,8 @@ Global
{A97421A9-58B7-4948-8231-2028B5D11F36} = {94640F4D-8BC6-407F-835E-73B454972CAD}
{03CD2FDC-2BF1-497D-BF18-F15170C0AD85} = {94640F4D-8BC6-407F-835E-73B454972CAD}
{0FC70E24-62A9-44BF-A433-804C98514A09} = {0F54786D-AB46-46AC-88DF-BEB789A62C1F}
{393C3A3D-E55F-4006-81A7-ED1CB3FAC1FF} = {40F06493-58CE-4E9D-BBB4-771BD26A9658}
{9EE0DF52-2C67-4DD1-AA9D-64ABC5BD00D7} = {40F06493-58CE-4E9D-BBB4-771BD26A9658}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{860BFBD8-26EA-44F9-980E-21B828FC8F72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -115,5 +123,13 @@ Global
{0FC70E24-62A9-44BF-A433-804C98514A09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FC70E24-62A9-44BF-A433-804C98514A09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FC70E24-62A9-44BF-A433-804C98514A09}.Release|Any CPU.Build.0 = Release|Any CPU
{393C3A3D-E55F-4006-81A7-ED1CB3FAC1FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{393C3A3D-E55F-4006-81A7-ED1CB3FAC1FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{393C3A3D-E55F-4006-81A7-ED1CB3FAC1FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{393C3A3D-E55F-4006-81A7-ED1CB3FAC1FF}.Release|Any CPU.Build.0 = Release|Any CPU
{9EE0DF52-2C67-4DD1-AA9D-64ABC5BD00D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9EE0DF52-2C67-4DD1-AA9D-64ABC5BD00D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9EE0DF52-2C67-4DD1-AA9D-64ABC5BD00D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9EE0DF52-2C67-4DD1-AA9D-64ABC5BD00D7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
2 changes: 2 additions & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<PackageVersion Include="DotNetCore.CAP.Dashboard" Version="8.2.0" />
<PackageVersion Include="DotNetCore.CAP.RabbitMQ" Version="8.2.0" />
<PackageVersion Include="DotNetCore.CAP.SqlServer" Version="8.2.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.12" />
<PackageVersion Include="Microsoft.SqlServer.SqlManagementObjects" Version="172.52.0" />
<PackageVersion Include="NJsonSchema" Version="11.5.1" />
<PackageVersion Include="Savorboard.CAP.InMemoryMessageQueue" Version="8.2.1" />
Expand All @@ -24,6 +25,7 @@
<PackageVersion Include="NHibernate" Version="5.5.2" />
<PackageVersion Include="RabbitMQ.Client" Version="6.8.1" />
<PackageVersion Include="Polly" Version="8.5.0" />
<PackageVersion Include="Scrutor" Version="7.0.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.2.0" />
</ItemGroup>
Expand Down
8 changes: 4 additions & 4 deletions src/__SolutionItems/CommonAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[assembly: AssemblyProduct("BSS Platform Services")]
[assembly: AssemblyCompany("Luxoft")]
[assembly: AssemblyCopyright("Copyright © Luxoft 2025")]
[assembly: AssemblyCopyright("Copyright © Luxoft 2026")]

[assembly: AssemblyVersion("1.6.1.0")]
[assembly: AssemblyFileVersion("1.6.1.0")]
[assembly: AssemblyInformationalVersion("1.6.1.0")]
[assembly: AssemblyVersion("1.6.2.0")]
[assembly: AssemblyFileVersion("1.6.2.0")]
[assembly: AssemblyInformationalVersion("1.6.2.0")]

#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
Expand Down