jbogard / MediatR.Extensions.Microsoft.DependencyInjection

MediatR extensions for Microsoft.Extensions.DependencyInjection
MIT License
326 stars 90 forks source link

Unhandled exception when resolving open generic IRequestExceptionHandler implementation #79

Open lokki opened 4 years ago

lokki commented 4 years ago

I'm trying to define essentially a catch-all request exception handler independent of message types, something like this:

public class GenericExceptionHandler<TRequest, TResponse> : IRequestExceptionHandler<TRequest, TResponse>

However, MS DI throws when MediatR is trying to get errors handlers:

System.ArgumentException: The number of generic arguments provided doesn't equal the arity of the generic type definition.
Parameter name: instantiation
   at System.RuntimeType.MakeGenericType(Type[] instantiation)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.GetExceptionHandlers(TRequest request, Type exceptionType, MethodInfo& handleMethodInfo)
   at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MediatR.Pipeline.RequestPostProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at MediatR.Pipeline.RequestPreProcessorBehavior`2.<Handle>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at MediatR.Extensions.Microsoft.DependencyInjection.Tests.PipelineTests.<>c__DisplayClass23_0.<<Should_pick_up_base_exception_behaviors>b__0>d.MoveNext() in C:\p\sym\MediatR.Extensions.Microsoft.DependencyInjection\test\MediatR.Extensions.Microsoft.DependencyInjection.Tests\PipelineTests.cs:line 457
   at Shouldly.Should.CompleteIn(Task actual, TimeSpan timeout, Func`1 customMessage, String what)
   at Shouldly.Should.CompleteIn(Task actual, TimeSpan timeout, Func`1 customMessage)
   at Shouldly.Should.RunAndWait(Func`1 actual, TimeSpan timeoutAfter, Func`1 customMessage)
   at Shouldly.Should.ThrowInternal[TException](Func`1 actual, TimeSpan timeoutAfter, Func`1 customMessage, String shouldlyMethod)

The only way to make it work that I found is to have the handler explicitly define the third generic parameter.

This works:

public class GenericExceptionHandler<TRequest, TResponse, TException> : IRequestExceptionHandler<TRequest, TResponse, TException> where TException : Exception

These don't:

public class GenericExceptionHandler<TRequest, TResponse> : IRequestExceptionHandler<TRequest, TResponse, Exception>
public class GenericExceptionHandler<TRequest, TResponse> : IRequestExceptionHandler<TRequest, TResponse>

Perhaps this is a limitation of MS DI, but at least MediatR makes this partially closed interface available IRequestExceptionHandler<TRequest, TResponse> so it is still possible to shoot oneself in the foot. Perhaps I'm doing something wrong?