This library aims to simplify the process of registering simple responders.
It is very common to have simple responders that have limited functionality, sometimes even just a single line of code.
One such example may look like this (C# 12):
public class MyResponder(MessageHandlerService messages) : IResponder<IMessageCreate>
{
public Task<Result> RespondAsync(IMessageCreate gatewayEvent, CancellationToken ct = default)
=> messages.HandleAsync(gatewayEvent, ct);
}
Shims, ad-hoc, stubs, whatever you wish to call them, this class's only purpose is to pump events to a service.
This requires having a class (and commonly, an extra file) just to provide this functionality.
Instead, this library offers an API that allows registering delegates to respond to events instead of full-fledged classes.
As a prerequisite, you will need to install the library, either as a project reference, or from NuGet.
Setting this library up only requires a single line of code. On a service collection, call the following method:
serviceCollection.AddDelegateResponders();
This call MUST be made before calls to .AddDelegateResponder<TEvent>
.
Here's how you'd add a responder:
serviceCollection.AddDelegateResponder<IMessageCreate>
(
async (IMessageCreate message, CancellationToken ct)
{
// Logic here
}
);
The library automatically coerces the following delegate types into the appropriate response type for Remora:
- Task (
async (TEvent t) => {}
) - Void (
(TEvent t) => {}
) - Result (
(TEvent t) => Result.FromSuccess()
) - Task (and any result-like type (
async (TEvent t) => Result.FromSuccess()
)) - ValueTask (Pass a static method, which is implicitly converted to a method group delegate)
- ValueTask (same as above).
You can also accept services in your delegate, similar to ASP.NET. The services MUST be:
- After the event parameter
- Before the
CancellationToken
parameter (if present) - Registered with the same DI container the delegate is registered in
- Resolvable from a DI scope
This is a valid delegate (assuming the service exists, for sake of example):
serviceCollection.AddDelegateResponders();
serviceCollection.AddDelegateResponder<IMessageCreate>
(
(IMessageCreate message, MessageHandlerService service, CancellationToken ct)
=> service.HandleAsync(message, ct);
);
Important
Delegates MUST be registered before the container is built.
There are a few outstanding issues in the library which stem from difficult decision making, or limitations of the C# type system itself.
Potential Solution: The C# STD library does not offer a method for awaiting ValueTask
s in parallel in the same manner as it does for its
reference-type based counterpart (Task
). Unfortunately this means that each delegate currently has to be await
ed in order, which causes its own set of problems.
One solution is to potentially check for completion in a loop as the Task methods do, but this likely has hidden implications, given that the BCL has not implemented something similar.
Potential solution: As it stands right now, exceptions bubble up through the callstack, breaking the iterative execution of these delegates.
This is simply solved by adding a catch statement, and packing these exceptions into an ExceptionError
to be handled like any other. This should be fixed soon.