NarcolepticFox.Crisp.Core 1.0.0

Suggested Alternatives

Crisp

Additional Details

Moving over to new package name

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package NarcolepticFox.Crisp.Core --version 1.0.0
                    
NuGet\Install-Package NarcolepticFox.Crisp.Core -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NarcolepticFox.Crisp.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NarcolepticFox.Crisp.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="NarcolepticFox.Crisp.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NarcolepticFox.Crisp.Core --version 1.0.0
                    
#r "nuget: NarcolepticFox.Crisp.Core, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NarcolepticFox.Crisp.Core@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NarcolepticFox.Crisp.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=NarcolepticFox.Crisp.Core&version=1.0.0
                    
Install as a Cake Tool

<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

Build Status NuGet NuGet Downloads License Target Frameworks Test Coverage

Table of Contents

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:

  1. Buffers events in a bounded channel with capacity for 10,000 events
  2. Processes events using 8 concurrent consumers
  3. Waits up to 5 seconds when the channel is full
  4. Processes events in parallel within each consumer
  5. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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