Interlink is a lightweight and modern mediator library for .NET, designed to decouple your code through request/response and notification patterns. Built with simplicity and performance in mind, it helps streamline communication between components while maintaining a clean architecture.
- π§© Simple mediator pattern for request/response
- π Publish/Subscribe notification system
- π§ Pipeline behaviors (logging, validation, etc.)
- π§ Clean separation of concerns via handlers
- πͺ Dependency injection support out of the box
- π Decouples logic using handlers
- π§© Easy registration with AddInterlink()
- π Lightweight, fast, and no external dependencies
- π Pre and Post Processors for enhanced lifecycle control
- β Compatible with .NET 8 and .NET 9
- π Assembly scanning for automatic handler registration
- π§ͺ Custom service factory injection
- π Pipeline ordering via attributes or configuration
- π Handler resolution caching (delegate-based)
- Clean, intuitive API
- No bloat β just powerful mediation
- Perfect for CQRS, Clean Architecture, Modular Design
- Highly extensible with behaviors and notifications
Install Interlink via NuGet:
dotnet add package Interlink
Register Interlink in your Startup.cs
or Program.cs
builder.Services.AddInterlink();
You can optionally pass an Assembly:
builder.Services.AddInterlink(typeof(MyHandler).Assembly);
public class GetAllPets
{
public record Query : IRequest<List<string>>;
public class Handler : IRequestHandler<Query, List<string>>
{
public Task<List<string>> Handle(Query request, CancellationToken cancellationToken)
{
var pets = new List<string> { "Dog", "Cat", "Fish" };
return Task.FromResult(pets);
}
}
}
[ApiController]
[Route("api/[controller]")]
public class PetController(ISender sender) : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAllPets(CancellationToken cancellationToken)
{
var pets = await sender.Send(new GetAllPets.Query(), cancellationToken);
return Ok(pets);
}
}
public class UserCreated(string userName) : INotification
{
public string UserName { get; } = userName;
}
public class SendWelcomeEmail : INotificationHandler<UserCreated>
{
public Task Handle(UserCreated notification, CancellationToken cancellationToken)
{
Console.WriteLine($"Welcome email sent to {notification.UserName}");
return Task.CompletedTask;
}
}
public class AccountService(IPublisher publisher)
{
public async Task RegisterUser(string username)
{
// Save to DB...
await publisher.Publish(new UserCreated(username));
}
}
Useful for logging, validation, performance monitoring, etc.
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
Console.WriteLine($"Handling {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"Handled {typeof(TRequest).Name}");
return response;
}
}
Pipeline behaviors can be manually registered like this:
builder.Services.AddInterlink(config =>
{
config.AddBehavior(typeof(LoggingBehavior<,>));
config.AddBehavior(typeof(LoggingBehavior2<,>)); // Add more behaviors as needed
});
Pipeline ordering can be controlled via attributes or configuration.
[PipelineOrder(1)]
public class FirstBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
Console.WriteLine("First behavior executed");
return await next();
}
}
[PipelineOrder(2)]
public class SecondBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
Console.WriteLine("Second behavior executed");
return await next();
}
}
You can also use the AddInterlink()
method to specify the order of pipeline behaviors:
builder.Services.AddInterlink(config =>
{
config.AddBehavior(typeof(FirstBehavior<,>));
config.AddBehavior(typeof(SecondBehavior<,>));
});
public class MyRequestPreProcessor : IRequestPreProcessor<GetAllPets.Query>
{
public Task Process(GetAllPets.Query request, CancellationToken cancellationToken)
{
Console.WriteLine("[PreProcessor] Processing GetAllPets request...");
return Task.CompletedTask;
}
}
public class MyRequestPostProcessor : IRequestPostProcessor<GetAllPets.Query, List<Pet>>
{
public Task Process(GetAllPets.Query request, List<Pet> response, CancellationToken cancellationToken)
{
Console.WriteLine("[PostProcessor] Processing GetAllPets response...");
return Task.CompletedTask;
}
}
Pre and Post Processors are automatically registered when you call AddInterlink().
public interface IRequest<TResponse> { }
public interface IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}
public interface INotification { }
public interface INotificationHandler<TNotification>
where TNotification : INotification
{
Task Handle(TNotification notification, CancellationToken cancellationToken);
}
public interface ISender
{
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
}
public interface IPublisher
{
Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
where TNotification : INotification;
}
public delegate Task<TResponse> RequestHandlerDelegate<TResponse>();
public interface IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next);
}
public interface IRequestPreProcessor<TRequest>
{
Task Process(TRequest request, CancellationToken cancellationToken);
}
public interface IRequestPostProcessor<TRequest, TResponse>
{
Task Process(TRequest request, TResponse response, CancellationToken cancellationToken);
}
- CQRS: Use
IRequest<TResponse>
for Queries and Commands - Event-Driven architecture: Use
INotification
for broadcasting domain events - Middleware-style behaviors: Add
IPipelineBehavior
for logging, validation, caching, etc.
- Basic
IRequest<TResponse>
andIRequestHandler<TRequest, TResponse>
ISender
for sending requestsAddInterlink()
for automatic DI registration- Clean, lightweight design
- Only .NET 9 support
- Basic
IRequest<TResponse>
andIRequestHandler<TRequest, TResponse>
ISender
for sending requestsAddInterlink()
for automatic DI registration- Clean, lightweight design
- .NET 8+ support
INotification
andINotificationHandler<TNotification>
IPublisher
for event broadcastingIPipelineBehavior<TRequest, TResponse>
support- Enhanced
AddInterlink()
with scanning and registration for notifications and pipelines - Updated documentation and examples
- .NET 8+ support
IRequestPreProcessor<TRequest>
interfaceIRequestPostProcessor<TRequest, TResponse>
interface- Pre and post hooks for request lifecycle
- Optional unit-of-work behaviors
- Fix critical bugs in
IPipelineBehavior<TRequest, TResponse>
- Handler resolution caching (delegate-based)
- Custom service factory injection support
- Pipeline ordering via attributes or configuration
- Assembly scanning filters by namespace or attribute
Interlink.Extensions.Logging
β built-in logging behaviorInterlink.Extensions.Validation
β integration with FluentValidationInterlink.AspNetCore
β model binding & filters for ASP.NET Core
- Source generator / Roslyn analyzer for missing handler detection
- Code snippets and templates for common patterns
- Custom exception types (e.g.,
HandlerNotFoundException
)
- Request cancellation and timeout behaviors
- Metrics collection and tracing support
- Dynamic or externalized pipeline config (e.g., JSON-based)
MIT License Β© ManuHub