aws / aws/aws-dotnet-messaging
HandlerInvoker does not support explicit interface implementation of IMessageHandler<T>.HandleAsync
- Dominant language
- C#
- Stars
- 143
- Forks
- 27
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 4
Description
### Description
When a message handler implements `IMessageHandler.HandleAsync` as an explicit interface implementation, the framework fails at runtime with:
> Unable to resolve a compatible HandleAsync method for {HandlerType} while handling message ID {MessageEnvelopeId}.
The handler is successfully resolved from DI — the issue is that `HandlerInvoker` uses reflection (`Type.GetMethod`) to find `HandleAsync`, which only searches public methods by default. Explicit interface implementations are private on the implementing type.
### Steps to reproduce
```csharp
public sealed class MyHandler(ILogger logger) : IMessageHandler
{
// Explicit interface implementation — fails at runtime
async Task IMessageHandler.HandleAsync(
MessageEnvelope messageEnvelope,
CancellationToken cancellation)
{
logger.LogInformation("Handling {Id}", messageEnvelope.Message.Id);
return MessageProcessStatus.Success();
}
}
```
Registration:
```csharp
messageBus.AddMessageHandler("MyEvent");
```
### Expected behavior
The handler should work regardless of whether `HandleAsync` is a public method or an explicit interface implementation. Both are valid C# patterns for implementing an interface.
### Actual behavior
`HandlerInvoker` logs an error and the message is never processed. The handler silently fails on every message.
### Root cause
In [`HandlerInvoker.cs`](https://github.com/aws/aws-dotnet-messaging/blob/main/src/AWS.Messaging/Services/HandlerInvoker.cs), the method lookup uses:
```csharp
subscriberMapping.HandlerType.GetMethod(
nameof(IMessageHandler.HandleAsync),
new Type[] { messageEnvelope.GetType(), typeof(CancellationToken) });
```
`Type.GetMethod(string, Type[])` with no `BindingFlags` only searches `Public | Instance` members. Explicit interface implementations are non-public on the implementing type — they're only accessible via the interface.
### Suggested fix
Since the framework already knows the interface type (`IMessageHandler`), it could invoke through the interface directly instead of using reflection on the concrete type:
```csharp
var handler = (IMessageHandler)scope.ServiceProvider.GetRequiredService(subscriberMapping.HandlerType);
await handler.HandleAsync(messageEnvelope, token);
```
This would make the reflection lookup unnecessary and support both implicit and explicit implementations.
### Workaround
Change from explicit to implicit (public) implementation:
```csharp
// Before (broken)
async Task IMessageHandler.HandleAsync(...)
// After (works)
public async Task HandleAsync(...)
```
### Additional context
- This is not documented anywhere — all samples use public implicit implementation, but nothing warns against explicit implementation
- The error message ("Unable to resolve a compatible HandleAsync method") is misleading — the handler IS resolved from DI, the method just isn't found via reflection
- Related to #166, which fixed the handler resolution (`GetService` → `GetRequiredService`) but didn't address the method lookup
### Environment
- `AWS.Messaging` version: 1.2.0
- .NET 10.0
Contributor guide
Assessment
This issue has not been assessed yet.