NarcolepticFox.Crisp.Core
1.0.0
Moving over to new package name
dotnet add package NarcolepticFox.Crisp.Core --version 1.0.0
NuGet\Install-Package NarcolepticFox.Crisp.Core -Version 1.0.0
<PackageReference Include="NarcolepticFox.Crisp.Core" Version="1.0.0" />
<PackageVersion Include="NarcolepticFox.Crisp.Core" Version="1.0.0" />
<PackageReference Include="NarcolepticFox.Crisp.Core" />
paket add NarcolepticFox.Crisp.Core --version 1.0.0
#r "nuget: NarcolepticFox.Crisp.Core, 1.0.0"
#:package NarcolepticFox.Crisp.Core@1.0.0
#addin nuget:?package=NarcolepticFox.Crisp.Core&version=1.0.0
#tool nuget:?package=NarcolepticFox.Crisp.Core&version=1.0.0
<p align="center"> <img src="assets/icon.png" alt="CRISP Framework Logo" width="200" height="200"/> </p>
<p align="center"> <img src="assets/comapny_logo.png" alt="Company Logo" height="50"/> </p>
CRISP: Core Reusable Infrastructure for Structured Programming
Table of Contents
- CRISP: Core Reusable Infrastructure for Structured Programming
Overview
CRISP is a lightweight, modular .NET framework designed to provide a clean architecture foundation for building scalable and maintainable applications. It implements modern software architecture patterns like Mediator, CQRS (Command Query Responsibility Segregation), Domain Events, and Resilience Patterns to help you build robust applications with minimal boilerplate.
Key Features
- Mediator Pattern: Decouple components through a central messaging system
- CQRS Implementation: Separate command and query responsibilities
- Domain Events: Enable event-driven architecture with loose coupling
- Resilience Patterns: Add robustness with retry, circuit breaker, and timeout strategies
- Validation Pipeline: Automatic validation of requests using a pipeline behavior
- Modular Architecture: Organize code into cohesive modules
- Configurable Options: Fine-tune framework behavior through comprehensive options
- Minimal Dependencies: Focused core with minimal external dependencies
- Channel-Based Event Processing: High-throughput event processing using System.Threading.Channels
- Multi-Targeting: Supports both .NET 9.0 and .NET Standard 2.1
Prerequisites
- .NET 9.0 SDK or later for .NET 9.0 target
- .NET Core 3.0 SDK or later for .NET Standard 2.1 target
Getting Started
Installation
Add the CRISP.Core package to your project:
dotnet add package CRISP.Core
Quick Start
// 1. Add using statement
using CRISP.Core.Extensions;
// 2. Register CRISP in your DI container
services.AddCrispFromAssemblies(typeof(Program).Assembly);
// 3. Inject and use the mediator
public class MyController
{
private readonly IMediator _mediator;
public MyController(IMediator mediator)
{
_mediator = mediator;
}
public async Task<IActionResult> GetUser(Guid id)
{
var result = await _mediator.Send(new GetUserQuery { UserId = id });
return Ok(result);
}
}
Basic Setup
Register CRISP services in your application:
// Program.cs or Startup.cs
using CRISP.Core.Extensions;
// Add CRISP with default options
services.AddCrispFromAssemblies(typeof(Program).Assembly);
// Or with custom options
services.AddCrispFromAssemblies(options => {
options.ConfigureResilience(resilience => {
resilience.Retry.MaxRetryAttempts = 5;
resilience.CircuitBreaker.FailureThreshold = 3;
});
options.ConfigureEvents(events => {
events.ProcessEventsInParallel = true;
events.MaxDegreeOfParallelism = 4;
});
},
typeof(Program).Assembly);
Framework Compatibility
CRISP.Core supports multiple target frameworks:
| Framework | Version |
|---|---|
| .NET | 9.0+ |
| .NET Standard | 2.1+ |
| .NET Core | 3.0+ |
| Mono | 6.4+ |
| Xamarin | iOS 12.16+, Android 10.0+ |
This multi-targeting approach ensures that CRISP can be used in a wide range of applications, from the latest .NET 9.0 projects to older .NET Core 3.x applications.
Usage Examples
Creating Commands
public class CreateUserCommand : Command
{
public string Username { get; set; }
public string Email { get; set; }
}
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand>
{
private readonly IUserRepository _userRepository;
private readonly IEventDispatcher _eventDispatcher;
public CreateUserCommandHandler(
IUserRepository userRepository,
IEventDispatcher eventDispatcher)
{
_userRepository = userRepository;
_eventDispatcher = eventDispatcher;
}
public async ValueTask Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var user = new User(request.Username, request.Email);
await _userRepository.AddAsync(user, cancellationToken);
await _eventDispatcher.Dispatch(new UserCreatedEvent(user.Id), cancellationToken);
}
}
Creating Queries
public class GetUserQuery : Query<UserDto>
{
public Guid UserId { get; set; }
}
public class GetUserQueryHandler : IRequestHandler<GetUserQuery, UserDto>
{
private readonly IUserRepository _userRepository;
public GetUserQueryHandler(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public async ValueTask<UserDto> Handle(GetUserQuery request, CancellationToken cancellationToken)
{
var user = await _userRepository.GetByIdAsync(request.UserId, cancellationToken);
return user != null
? new UserDto { Id = user.Id, Username = user.Username, Email = user.Email }
: null;
}
}
Using Channel-Based Event Processing
For applications that need high-throughput event processing, you can use CRISP's channel-based event dispatcher, which leverages System.Threading.Channels for efficient asynchronous event handling:
// Program.cs or Startup.cs
using CRISP.Core.Extensions;
services.AddCrispFromAssemblies(options => {
// Enable channel-based event processing with custom configuration
options.UseChannelEventProcessing(channelOptions => {
// Configure a bounded channel with capacity of 10,000 events
channelOptions.ChannelCapacity = 10000;
// Set the number of consumers processing events from the channel
// (Default is Environment.ProcessorCount)
channelOptions.ConsumerCount = 8;
// Configure how long to wait when the channel is full (in milliseconds)
channelOptions.FullChannelWaitTimeMs = 5000;
// Wait for all events to be processed during application shutdown
channelOptions.WaitForChannelDrainOnDispose = true;
channelOptions.ChannelDrainTimeoutMs = 30000; // 30 seconds
});
// Additional event configuration
options.ConfigureEvents(events => {
// Enable parallel processing of events within each consumer
events.ProcessEventsInParallel = true;
events.MaxDegreeOfParallelism = 4;
});
}, typeof(Program).Assembly);
This configuration creates a high-performance event processing pipeline that:
- Buffers events in a bounded channel with capacity for 10,000 events
- Processes events using 8 concurrent consumers
- Waits up to 5 seconds when the channel is full
- Processes events in parallel within each consumer
- Ensures events are fully processed during application shutdown
The channel-based event dispatcher is ideal for scenarios with high event volume or when you need to decouple event dispatching from processing for better performance.
Adding Validation
public class CreateUserCommandValidator : IValidator<CreateUserCommand>
{
public ValidationResult Validate(CreateUserCommand request)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(request.Username))
{
errors.Add(new ValidationError("Username", "Username is required"));
}
if (string.IsNullOrWhiteSpace(request.Email))
{
errors.Add(new ValidationError("Email", "Email is required"));
}
else if (!IsValidEmail(request.Email))
{
errors.Add(new ValidationError("Email", "Email is not valid"));
}
return errors.Count > 0
? ValidationResult.Failure(errors.ToArray())
: ValidationResult.Success();
}
private bool IsValidEmail(string email)
{
try
{
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch
{
return false;
}
}
}
Using Domain Events
public class UserCreatedEvent : DomainEvent
{
public Guid UserId { get; }
public UserCreatedEvent(Guid userId)
{
UserId = userId;
}
}
public class EmailNotificationHandler : IEventHandler<UserCreatedEvent>
{
private readonly IEmailService _emailService;
public EmailNotificationHandler(IEmailService emailService)
{
_emailService = emailService;
}
public async ValueTask Handle(UserCreatedEvent @event, CancellationToken cancellationToken)
{
await _emailService.SendWelcomeEmailAsync(@event.UserId, cancellationToken);
}
}
Using Resilience Patterns
public class ExternalApiService
{
private readonly IResilienceStrategy _resilienceStrategy;
public ExternalApiService(IResilienceStrategy resilienceStrategy)
{
_resilienceStrategy = resilienceStrategy;
}
public async Task<ApiResult> CallExternalApiAsync(string endpoint, CancellationToken cancellationToken)
{
return await _resilienceStrategy.Execute(async (ct) => {
// Make an HTTP call that might fail transiently
using var client = new HttpClient();
var response = await client.GetAsync(endpoint, ct);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync(ct);
return JsonSerializer.Deserialize<ApiResult>(content);
}, cancellationToken);
}
}
Organizing code into modules
public class UsersModule : ModuleBase
{
public override void RegisterServices(IServiceCollection services)
{
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserService, UserService>();
}
}
Architecture
CRISP follows a clean, modular architecture that separates concerns into distinct components:
- Commands: Write operations that change state
- Queries: Read operations that retrieve data
- Events: Notifications that something has occurred
- Handlers: Business logic for processing commands, queries, and events
- Behaviors: Cross-cutting concerns like validation and logging
- Resilience: Patterns to handle transient failures and exceptions
- Modules: Organizational units that group related functionality
Extending CRISP
CRISP is designed to be extensible. You can create custom behaviors, validators, or event handlers to add new functionality.
Creating a Custom Behavior
public class AuditBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IAuditService _auditService;
public AuditBehavior(IAuditService auditService)
{
_auditService = auditService;
}
public async ValueTask<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
await _auditService.RecordRequest(request, cancellationToken);
var response = await next();
await _auditService.RecordResponse(response, cancellationToken);
return response;
}
}
License
This project is licensed under the MIT License
Acknowledgments
CRISP draws inspiration from several established patterns and libraries, including MediatR, CQRS, and the Resilience pattern. It aims to provide a lightweight, focused implementation that adheres to .NET best practices.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.4)
- Microsoft.Extensions.DependencyInjection (>= 9.0.4)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.4)
- Microsoft.Extensions.Options (>= 9.0.4)
- System.Text.Json (>= 9.0.4)
- System.Threading.Channels (>= 9.0.4)
-
net9.0
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.4)
- Microsoft.Extensions.DependencyInjection (>= 9.0.4)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.4)
- Microsoft.Extensions.Options (>= 9.0.4)
- System.Threading.Channels (>= 9.0.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|