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
- π Assembly scanning for automatic handler registration
- π§ͺ Custom service factory injection
- π Pipeline ordering via attributes or configuration
- π Handler resolution caching (delegate-based)
- β Compatible with .NET Standard 2.0+ .NET 8 and .NET 9
- 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 InterlinkRegister 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<FirstBehavior<GetAllPets.Query, List<string>>>(1);
config.AddBehavior<SecondBehavior<GetAllPets.Query, List<string>>>(2);
});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
INotificationfor broadcasting domain events - Middleware-style behaviors: Add
IPipelineBehaviorfor logging, validation, caching, etc.
- Basic
IRequest<TResponse>andIRequestHandler<TRequest, TResponse> ISenderfor sending requestsAddInterlink()for automatic DI registration- Clean, lightweight design
- Only .NET 9 support
- Basic
IRequest<TResponse>andIRequestHandler<TRequest, TResponse> ISenderfor sending requestsAddInterlink()for automatic DI registration- Clean, lightweight design
- .NET 8+ support
INotificationandINotificationHandler<TNotification>IPublisherfor 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
- Support .NET Standard 2.0+ compatible (works in .NET Core, .NET 5+, .NET Framework 4.7.2+)
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
