Troubleshooting
September 19, 2023 ยท View on GitHub
In this section, you'll find descriptions of exceptions that may arise while using 'Pipelines' and guidance on how to debug the source generated dispatcher.
Table of Content
-
1. Pipeline Builder Exceptions
- ProvidedTypeIsNotInterfaceException
- HandlerMethodNotFoundException
- MultipleHandlerMethodsException
- MethodShouldHaveAtLeastOneParameterException
- GenericArgumentsNotFoundException
- HandlerInputTypeMismatchException
- InvalidConstraintLengthException
- ExpectedMethodWithResultException
- ResultTypeCountMismatchException
- GenericTypeCountMismatchException
- GenericTypeMismatchException
- IsGenericMismatchException
- TypeMismatchException
- DispatcherMethodInputTypeMismatchException
- ParameterCountMismatchException
- ParameterTypeMismatchException
- TaskReturnTypeMismatchException
- VoidAndValueMethodMismatchException
- InterfaceImplementationException
- ConstructorValidationException
1. Pipeline Builder Exceptions
ProvidedTypeIsNotInterfaceException
What happened?
One of the types provided to the pipeline is not an interface.
Bad example
public class SampleClass{}
services
.AddPipeline()
.AddInput(typeof(SampleClass))
.AddHandler(typeof(IHandler<>), handlersAssembly)
.AddDispatcher<IDispatcher>(dispatcherAssembly)
.Build();
...
How to fix
- Ensure all provided types are interfaces.
- Always use typeof when providing types.
public interface IInput{}
services
.AddPipeline()
.AddInput(typeof(IInput))
.AddHandler(typeof(IHandler<>), handlersAssembly)
.AddDispatcher<IDispatcher>(dispatcherAssembly)
.Build();
...
HandlerMethodNotFoundException
What happened?
A provided type doesn't define a handle method.
Bad example
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{}
...
How to fix
Define a Handle method in the handler.
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}
...
MultipleHandlerMethodsException
What happened?
Multiple methods were defined in the provided type. Each interface must contain only a single method.
Bad example
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
public Task<TResult> HandleAsync(TInput input, CancellationToken token);
public Task<TResult> HandleAsync(TInput input);
}
How to fix
Remove extra methods from the interface.
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}
MethodShouldHaveAtLeastOneParameterException
What happened?
The defined Handle method does not have any parameters.
Bad example
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
public Task<TResult> HandleAsync();
}
How to fix
Ensure the method has at least one parameter, which should be of the Input Type.
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}
GenericArgumentsNotFoundException
What happened?
The Handler lacks generic arguments. At least one generic argument is required, specifically for Input.
Bad example
public interface IHandler
{
Task HandleAsync(IInput input, CancellationToken token);
}
How to fix
Ensure the Handler has at least one generic argument representing the Input.
public interface IHandler<in TInput> where TInput : IInput
{
Task HandleAsync(TInput input, CancellationToken token);
}
HandlerInputTypeMismatchException
What happened?
The first generic argument doesn't have the Input type as its constraint.
Bad example
public interface IInput
{ }
public interface IHandlerWithResult<in TInput, TResult>
where TInput : IInputWithResult<TResult>
where TResult : class
{
Task<TResult> HandleAsync(TInput input, CancellationToken token);
}
How to fix
The first generic argument of the handler must use the Input type, specified in the AddInput() method, as its constraint.
public interface IInput
{ }
public interface IHandler<in TInput> where TInput : IInput
{
Task HandleAsync(TInput input, CancellationToken token);
}
InvalidConstraintLengthException
What happened?
The first generic argument lacks constraints. At least one constraint is required, specifically for the Input type.
Bad example
public interface IInput
{ }
public interface IHandler<in TInput>
{
public Task HandleAsync(TInput input, CancellationToken token);
}
How to fix
Ensure the first generic argument of the handler has the Input type as its constraint.
public interface IInput
{ }
public interface IHandler<in TInput> where TInput : IInput
{
Task HandleAsync(TInput input, CancellationToken token);
}
ExpectedMethodWithResultException
What happened?
Given the Input generic arguments, it was anticipated that the Handler/Dispatcher would have a method returning a result, but a void was found instead.
Bad example
public interface IInput<TResult>
{}
public interface IDispatcher
{
public Task SendAsync<TResult>(IInput<TResult> request, CancellationToken token);
}
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult>
{
public Task HandleAsync(TInput command, CancellationToken token);
}
How to fix
Ensure that result types align with the Input generic arguments.
public interface IInput<TResult>
{}
public interface IDispatcher
{
public Task<TResult> SendAsync<TResult>(IInput<TResult> request, CancellationToken token);
}
public interface IHandler<in TInput, TResult> where TInput : IInput<TResult>
{
public Task<TResult> HandleAsync(TInput command, CancellationToken token);
}
ResultTypeCountMismatchException
What happened?
The number of result types defined by the Input generic arguments does not match the number of result types found in the Dispatcher/Handler method.
Bad example
public interface IInputWithResult<TResult>
{ }
public interface IHandler<in TInput, TResult>
where TInput : IInputWithResult<TResult> where TResult : class
{
public Task<(TResult, bool)> HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<(TResult, TResult2)> SendAsync<TResult,TResult2>(IInputWithResult<TResult> request, CancellationToken token);
}
How to fix
Ensure that the Dispatcher/Handler method's result types align with the Input generic arguments.
public interface IInputWithResult<TResult>
{ }
public interface IHandler<in TInput, TResult>
where TInput : IInputWithResult<TResult> where TResult : class
{
public Task<TResult> HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<TResult> SendAsync<TResult>(IInputWithResult<TResult> request, CancellationToken token);
}
GenericTypeCountMismatchException
What happened?
The number of constraints in the result types defined in the Handler or Dispatcher does not match.
Bad example
public interface IInputType
{}
public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
where TResultOne : IResultOne
where TResultTwo : IResultTwo
{
public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType)
where TResult : IResultOne;
}
How to fix
Ensure that the number of constraints on result types in the Handler or Dispatcher matches.
public interface IInputType
{}
public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
where TResultOne : IResultOne
where TResultTwo : IResultTwo
{
public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType)
where TResult : IResultOne
where TResultTwo : IResultTwo;
}
GenericTypeMismatchException
What happened?
There is a mismatch in the generic type constraints between the Handler and the Dispatcher, or between the Input result types constraint and the Handler or Dispatcher.
Bad example
public interface IInputType
{ }
public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
where TResultOne : IResultOne
where TResultTwo : IResultTwo
{
public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType, CancellationToken token)
where TResult : IResultOne where TResultTwo : struct;
}
How to fix
Ensure that both the Handler and Dispatcher are consistent in their return types, and their generic type constraints match accordingly.
public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
where TResultOne : IResultOne
where TResultTwo : IResultTwo
{
public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType, CancellationToken token)
where TResult : IResultOne where TResultTwo : IResultTwo;
}
IsGenericMismatchException
What happened?
There's a mismatch between the result types in the Handler and Dispatcher: one is generic, while the other is not.
Bad example
public interface IHandler<in TInput, TResult> where TInput : IInputType
where TResult : IResult
{
public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType, CancellationToken token);
}
How to fix
Ensure that both the Handler and Dispatcher return the same type of result.
public interface IHandler<in TInput, TResult> where TInput : IInputType
where TResult : IResult
{
public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}
public interface IDispatcher
{
public Task<TResult> SendAsync<TResult>(IInputType inputType, CancellationToken token);
}
TypeMismatchException
There's a mismatch between the non generic result types in the Handler and Dispatcher.
What happened?
Bad example
public interface IHandler<in TInput> where TInput : IInputType
{
public int HandleAsync(TInput input, CancellationToken token);
}
public interface IDispatcher
{
public string SendAsync(IInputType input);
}
How to fix
Ensure that both the Handler and Dispatcher return the same type of result.
public interface IHandler<in TInput> where TInput : IInputType
{
public int HandleAsync(TInput input, CancellationToken token);
}
public interface IDispatcher
{
public int SendAsync(IInputType input);
}
DispatcherMethodInputTypeMismatchException
What happened?
The Dispatcher's Handle method does not use the expected Input type as its first parameter.
Bad example
public interface IInput
{ }
public interface IDispatcher
{
public Task SendAsync(int request, CancellationToken token);
}
How to fix
Ensure that the Input type is used as the first parameter in the method.
public interface IInput
{ }
public interface IDispatcher
{
public Task SendAsync(IInput request, CancellationToken token);
}
ParameterCountMismatchException
What happened?
The Handler and Dispatcher methods have a mismatch in the number of parameters.
Bad example
public interface IHandler<in TInput> where TInput : IInputType
{
public Task<string> HandleAsync(TInput input, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType, int index, CancellationToken token);
}
How to fix
Ensure that the Handler and Dispatcher methods have the same number and type of parameters.
public interface IHandler<in TInput> where TInput : IInputType
{
public Task<string> HandleAsync(TInput input, int index, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType, int index, CancellationToken token);
}
ParameterTypeMismatchException
What happened?
There's a mismatch in the type of a parameter between the Handler and Dispatcher methods.
Bad example
public interface IHandler<in TInput> where TInput : IInputType
{
public Task<string> HandleAsync(TInput command, int index, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType, string text, CancellationToken token);
}
How to fix
Ensure that the parameters in both the Handler and Dispatcher methods are of the same type and order.
public interface IHandler<in TInput> where TInput : IInputType
{
public Task<string> HandleAsync(TInput command, string text, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType, string text, CancellationToken token);
}
TaskReturnTypeMismatchException
What happened?
There's a mismatch in the return type of the Handler and Dispatcher methods. One method returns a Task<> type, while the other does not.
Bad example
public interface IHandler<in TInput> where TInput : IInputType
{
public string HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType, CancellationToken token);
}
How to fix
Ensure that both the Handler and Dispatcher methods have consistent return types. If one returns a Task<>, the other should too.
public interface IHandler<in TInput> where TInput : IInputType
{
public Task<string> HandleAsync(TInput command, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType, CancellationToken token);
}
VoidAndValueMethodMismatchException
What happened?
There's an inconsistency in the return types between the Handler and Dispatcher methods. One method returns a value, while the other returns void.
Bad example
public interface IHandler<in TInput> where TInput : IInputType
{
public string HandleAsync(TInput input, CancellationToken token);
}
public interface IDispatcherVoid
{
public void SendAsync(IInputType inputType);
}
How to fix
Ensure both the Handler and Dispatcher methods have matching return types. Either both should return a value, or both should return void.
public interface IHandler<in TInput> where TInput : IInputType
{
public Task<string> HandleAsync(TInput input, CancellationToken token);
}
public interface IDispatcher
{
public Task<string> SendAsync(IInputType inputType);
}
InterfaceImplementationException
What happened?
The decorator class is not implementing the expected interface, leading to a type mismatch between the expected and actual generic types.
Bad example
public interface IHandler<in TInput> where TInput : IInput
{
public Task HandleAsync(TInput input, CancellationToken token);
}
public class Decorator : IDifferentHandler<InputWithResult, Result>
{
private readonly IDifferentHandler<InputWithResult, Result> _handler;
public Decorator(IDifferentHandler<InputWithResult, Result> handler)
{
_handler = handler;
}
public async Task<Result> HandleAsync(InputWithResult input, CancellationToken token)
{
return await _handler.HandleAsync(input, token);
}
}
How to fix
Ensure that the decorator class implements the correct interface, which it's intended to decorate. The generic type parameters of the decorator should align with those of the interface it should implement.
public class Decorator : IHandler<IInput, Result>
{
private readonly IHandler<IInput, Result> _handler;
public Decorator(IHandler<IInput, Result> handler)
{
_handler = handler;
}
public async Task<Result> HandleAsync(IInput input, CancellationToken token)
{
return await _handler.HandleAsync(input, token);
}
}
ConstructorValidationException
What happened?
The decorator's constructor does not have the required handler dependency (either it's missing or invalid).
Bad example
public interface IHandler<in TInput> where TInput : IInput
{
public Task HandleAsync(TInput input, CancellationToken token);
}
public class Decorator : IHandler<IInput, Result>
{
public Decorator()
{ }
public async Task<Result> HandleAsync(IInput input, CancellationToken token)
{
return Task.CompletedTask;
}
}
How to fix
Include the required handler dependency in the decorator's constructor and use it in the HandleAsync method:
public class Decorator : IHandler<IInput, Result>
{
private readonly IHandler<IInput, Result> _handler;
public Decorator(IHandler<IInput, Result> handler)
{
_handler = handler;
}
public async Task<Result> HandleAsync(IInput input, CancellationToken token)
{
return await _handler.HandleAsync(input, token);
}
}
2. Runtime Exceptions
DispatcherNotRegisteredException
What happened?
The dispatcher registration is missing from the Dependency Injection Container. By default, the AddDispatcher() method use the Dispatcher Generated by Source generator. The primary reason the Generated Dispatcher isn't found is due to the absence of package in project Pipelines.WrapperDispatcherGenerator.
How to fix
Add package DumplingsDevs.Pipelines.WrapperDispatcherGenerator or change Dispatcher options to UseReflectionProxyImplementation=true
dotnet add package DumplingsDevs.Pipelines.WrapperDispatcherGenerator
or
services
.AddPipeline()
.AddInput(typeof(ICommand<>))
.AddHandler(typeof(ICommandHandler<,>), assembly)
.AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true)
.Build()
Additional Possibility:
What happened?
Another possible issue could be related to problems during the generation of the dispatcher. In this situation, we recommend creating an issue on our GitHub repository: https://github.com/DumplingsDevs/Pipelines/issues. Please provide a detailed description of your Pipelines configuration to help us assist you better.
HandlerNotRegisteredException
What happened?
The handler implementation for the provided input was not found. In most cases, this suggests that while the input has been defined, its corresponding handler implementation is missing.
...
Bad example
public record ExampleCommand2(string Value) : IInput<ExampleCommandResult>;
...
var request = new ExampleCommand2("My test request");
var result = await _dispatcher.SendAsync(request, new CancellationToken());
How to fix
For every defined Input, ensure that a corresponding Handler is implemented.
public record ExampleCommand2(string Value) : IInput<ExampleCommandResult>;
public class ExampleHandler : IHandler<ExampleCommand2, ExampleCommandResult>
{
public Task<ExampleCommandResult> HandleAsync(ExampleCommand2 input, CancellationToken token)
{
return Task.FromResult(new ExampleCommandResult(input.Value));
}
}
...
var request = new ExampleCommand2("My test request");
var result = await _dispatcher.SendAsync(request, new CancellationToken());
InputNullReferenceException
What happened?
The dispatcher was invoked with a null value instead of an actual input object.
Bad example
var result = await _dispatcher.SendAsync(null, new CancellationToken());
How to fix
Ensure that you provide a valid input object to the method and avoid passing null values.
var request = new ExampleCommand2("My test request");
var result = await _dispatcher.SendAsync(request, new CancellationToken());
CannotCreateDispatcherWrapperException
What happened?
Something went wrong with source generator and generated Dispatcher. Please create issue on Github with types and builder description.
AssemblyNotProvidedException
What happened?
At least one assembly is not provided to AddHandler() method or WithDecorators
Bad example
services
.AddPipeline()
.AddInput(typeof(ICommand<>))
.AddHandler(typeof(ICommandHandler<,>))
.AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true,dispatcherAssembly))
.Build()
services
.AddPipeline()
.AddInput(typeof(ICommand<>))
.AddHandler(typeof(ICommandHandler<,>), assembly)
.AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true, dispatcherAssembly))
.WithDecorators(x => x.WithNameContaining("Attribute"))
.Build()
How to fix
Ensure that you provided at least one assembly to AddHandler() or WithDecorators.
services
.AddPipeline()
.AddInput(typeof(ICommand<>))
.AddHandler(typeof(ICommandHandler<,>), handlersAssembly)
.AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true,dispatcherAssembly))
.Build()
services
.AddPipeline()
.AddInput(typeof(ICommand<>))
.AddHandler(typeof(ICommandHandler<,>), handlersAssembly)
.AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true, dispatcherAssembly))
.WithDecorators(x => x.WithNameContaining("Attribute")), decoratorsAssembly)
.Build()
3. Cannot build project - how to debug
What happened?
In some cases, the Pipelines source generator may generate code that cannot be compiled. This can happen when users create pipelines builders with objects that are not supported by the Pipelines library. If you suspect that this is the issue you're facing, there are specific symptoms to look out for and steps to diagnose and resolve the problem.
Symptoms of Generated Dispatcher Issues:
- Build Result Contains Errors in "Pipelines.WrapperDispatcherGenerator" Files:
- One indication of a problem with the generated dispatcher is the presence of build errors that are logged in files with "Pipelines.WrapperDispatcherGenerator" in the file path. These errors typically relate to issues within the generated code.
- Build Errors Disappear After Removing the "Pipelines.WrapperDispatcherGenerator" NuGet Package:
- Another significant clue is that when you remove the "Pipelines.WrapperDispatcherGenerator" NuGet package from your project, the build errors related to it no longer appear. This suggests that the issue is indeed connected to the generated dispatcher.
Additionally, if the problem you're facing is a blocker for your project, a workaround is to remove the "Pipelines.WrapperDispatcherGenerator" package and utilize the built-in ProxyImplementation that uses reflection to handle dispatcher functionality. To enable the proxy, set the option UseReflectionProxyImplementation to true. Here's a code example:
services
.AddPipeline()
.AddInput(typeof(ICommand<>))
.AddHandler(typeof(ICommandHandler<,>), assembly)
.AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true)
.Build()
We encourage you to report any bug as a GitHub issue on our repository at https://github.com/DumplingsDevs/Pipelines/issues. Your feedback helps us improve the library and assists other users who may encounter similar problems.