CaseR.SourceGenerator 0.4.0

dotnet add package CaseR.SourceGenerator --version 0.4.0
                    
NuGet\Install-Package CaseR.SourceGenerator -Version 0.4.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="CaseR.SourceGenerator" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CaseR.SourceGenerator" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="CaseR.SourceGenerator" />
                    
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 CaseR.SourceGenerator --version 0.4.0
                    
#r "nuget: CaseR.SourceGenerator, 0.4.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 CaseR.SourceGenerator@0.4.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=CaseR.SourceGenerator&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=CaseR.SourceGenerator&version=0.4.0
                    
Install as a Cake Tool

CaseR

CaseR is not another MediatR clone, but tries to solve the same problem in a different way (in context .NET 10).

The main task of this library is to model use cases in an application and separate cross-cutting concerns (like logging, caching, monitoring, transactions,...) and support vertical slice architecture in ASP.NET Core 8+ applications, even with support for AOT compilation.

CaseR is a conceptual project.
The following criticism does not only apply to the MediatR library, but also to its clones and some similar libraries. In my opinion, MediatR showed a good direction, which was due to the time of its creation. The CaseR project tries to show a different principle but solve the same problems.

I tried to solve mainly these problems:

  • Interface IMediator is too generic, it's like injecting an IServiceProvider, so class dependencies or MinimalAPI don't make it clear what the class requires.
  • In a project using a mediator, it is difficult to navigate because it is not easy to get to the handler implementation.
  • MediatR is not type-safe in compile time. It is possible to call IMediator.Send() with a request for which there is no handler.
  • The necessity to use IRequest<> and IResponse, we understand why these interfaces are in MediatR, but it bothers me a bit.

After a few projects where I used MediatR I realized a few things. Developers actually use MediatR to implement their use cases.

Therefore, I decided to create a library that uses the correct terminology for Use Case (and interactor from Clean Architecture).

Key Features

  • Modeling use cases
  • Built entirely on top of Dependency Injection
  • Zero runtime reflection after registration
    • CaseR.SourceGenerator eliminate reflection at registration
  • Direct code reference to business logic (no problem with trimming and orientation in codebase - F12 work)
  • Compile-time type safety (using generic constraints and source generator)
  • Interaction interceptor pipeline
  • Supports keyed pipelines
  • Supports domain events publisher

Get started

1. Install nugets

Install nugets into project using:

dotnet add package CaseR

dotnet add package CaseR.SourceGenerator

2. Register CaseR services

builder.Services.AddCaseR();
builder.Services.AddCaseRInteractors();

3. Create interactor

public record GetTodoInteractorRequest();

public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false);

public class GetTodoInteractor : IUseCaseInterceptor<GetTodoInteractorRequest, Todo[]>
{
    public GetTodoInteractor()
    {
        
    }

    public Task<Todo[]> Execute(GetTodoInteractorRequest request, CancellationToken cancellationToken)
    {
        Todo[] sampleTodos = new Todo[] 
        {
                new Todo(1, "Walk the dog"),
                new Todo(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
                new Todo(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
                new Todo(4, "Clean the bathroom"),
                new Todo(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
        };

        return Task.FromResult(sampleTodos);
    }
}

4. Use use case in minimal API

var todosApi = app.MapGroup("/todos");
            todosApi.MapGet("/", async (IUseCase<GetTodoInteractor> getTodoInteractor,
                CancellationToken cancellationToken) =>
            {
                WebAppExample.Todo.UseCases.Todo[] todos = await getTodoInteractor.Execute(new GetTodoInteractorRequest(), cancellationToken);
                return todos;
            });

4. Use use case in background worker

Use IAutoScopedUseCase<> for automatically create scope for use case execution in background worker.

public class TimerLoggingService : BackgroundService
{
    private readonly IAutoScopedUseCase<TimeLoggerInteractor> timeLoggerInteractor;

    public TimerLoggingService(IAutoScopedUseCase<TimeLoggerInteractor> timeLoggerInteractor)
    {
        this.timeLoggerInteractor = timeLoggerInteractor;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        long counter = 0;
        using PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromSeconds(15));
        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            await this.timeLoggerInteractor.Execute(new TimeLoggerRequest(DateTimeOffset.UtcNow, counter), stoppingToken);
        
            counter++;
        }
    }
}

Domain events

1. Create domain event

public record GetTodoItemInteractorEvent(int Id) : IDomainEvent;

2. Create domain event handlers

public class GetTodoItemInteractorEventHandler : IDomainEventHandler<GetTodoItemInteractorEvent>
{
    public Task Handle(GetTodoItemInteractorEvent domainEvent, CancellationToken cancellationToken)
    {
        //...
        return Task.CompletedTask;
    }
}

3. Publish event

Inject IDomainEventPublisher into your interactor or handler and use it to publish events.

IDomainEventPublisher domainEventPublisher

Publish event:

 await this.domainEventPublisher.Publish(new GetTodoItemInteractorEvent(request), cancellationToken);

Use Case interceptors

Interceptors are used to create a pipeline that wraps the interactor call.

public class ElapsedTimeInterceptor<TRequest, TResponse> : IUseCaseInterceptor<TRequest, TResponse>
{
    private readonly ILogger<ElapsedTimeInterceptor<TRequest, TResponse>> logger;

    public ElapsedTimeInterceptor(ILogger<ElapsedTimeInterceptor<TRequest, TResponse>> logger)
    {
        this.logger = logger;
    }

    public async Task<TResponse> InterceptExecution(IUseCaseInteractor<TRequest, TResponse> useCaseInteractor, TRequest request, UseCasePerformDelegate<TRequest, TResponse> next, CancellationToken cancellationToken)
    {
        long timestamp = Stopwatch.GetTimestamp();
        try
        {
            return await next(request).ConfigureAwait(false);
        }
        finally
        {
            TimeSpan elapsedTime = Stopwatch.GetElapsedTime(timestamp);
            this.logger.LogCritical("Elapsed {ElapsedTime}ms in {InteractorName}.", elapsedTime.TotalMilliseconds, useCaseInteractor.GetType().Name);
        }
    }
}

And register interceptors in registration:

builder.Services.AddCaseR(options =>
  {
    options.AddGenericInterceptor(typeof(ElapsedTimeInterceptor<,>));
    // register another interceptors
  });
builder.Services.AddCaseRInteractors();

Or use keyed piplines:

builder.Services.AddKeyedCaseR("GrpcPipeline", options =>
  {
    options.AddGenericInterceptor(typeof(AnotherInterceptor<,>));
    options.AddGenericInterceptor(typeof(NestedLogInterceptor<,>));
  });

builder.Services.AddKeyedCaseR("MinimalApiPipeline", options =>
  {
    options.AddGenericInterceptor(typeof(AnotherInterceptor<,>));
    options.AddGenericInterceptor(typeof(NestedLogInterceptor<,>));
  });

Usage:

var todosApi = app.MapGroup("/todos");
            todosApi.MapGet("/", async ([FromKeyedServices("MinimalApiPipeline")] IUseCase<GetTodoInteractor> getTodoInteractor,
                CancellationToken cancellationToken) =>
            {
                WebAppExample.Todo.UseCases.Todo[] todos = await getTodoInteractor.Execute(new GetTodoInteractorRequest(), cancellationToken);
                return todos;
            });

Notes

  • Using attribute ExcludeFromRegistration it is possible to suppress automatic class registration into DI using .AddCaseRInteractors(...) methods.

CaseR

NuGet Status The CaseR library contains all the logic and can be used without a source generator.

However, it has two limitations:

  • Registering interactors is done using reflection builder.Services.AddCaseRInteractors(typeof(Program));,
  • In the execute method, generic parameters must be passed when calling await useCase.Execute<TInteractor, TRequest, TResponse>(request, cancellationToken);.

CaseR.SourceGenerator

NuGet Status

The CaseR.SourceGenerator library is a source generator that generates the necessary code for the CaseR library to work without reflection and typed Execute method.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

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
0.4.0 486 7/22/2025
0.3.0 143 7/3/2025
0.2.0 138 6/29/2025
0.1.0 138 6/29/2025