jbogard / MediatR

Simple, unambitious mediator implementation in .NET
Apache License 2.0
11.06k stars 1.17k forks source link

Add Support For Generic Handlers With Multiple Generic Type Parameters #1048

Closed zachpainter77 closed 2 months ago

zachpainter77 commented 3 months ago

This PR adds support for handlers that contain multiple generic type parameters.

This PR is a direct fix for issues #1047 and #1038

Example:


public interface IZong{}
public class Zong : IZong {}
public interface IDong{}
public class Dong : IDong {}
public interface IPong { string Message? { get; } }
public class Pong : IPong
{
    string Message? { get; set; }
}

//generic request definition
public class GenericPing<TPong, TZong, TDong> : IRequest<TPong>
    where TPong : IPong
    where TZong : IZong
    where TDong : IDong
{
    public T? ThePong { get; set; }
}

//generic request handler
public class GenericPingHandler<TPong, TZong, TDong> : IRequestHandler<GenericPing<TPong, TZong, TDong>, TPong>
    where TPong : IPong
    where TZong : IZong
    where TDong : IDong
{
    public Task<TPong> Handle(GenericPing<TPong, TZong, TDong> request, CancellationToken cancellationToken) => Task.FromResult(request.ThePong!);
}

//usage
var pong = _mediator.Send(new GenericPing<Pong, Zong, Dong>{ Pong = new() { Message = "Ping Pong" } });
Console.WriteLine(pong.Message); //would output "Ping Pong"

The issue that this PR solves was caused from only attempting to close the first type parameter in a generic request handler implementation.

This PR fixes that by finding the types that close for all type parameter and storing those in a list of lists. Then all possible combinations of concrete implementations are generated from the lists and registered with the service provider.

Something that might need some discussion and thought will be what kinds of limitations we should put around this feature so that users are not misusing the library and creating a deadlock during service registration.

Along with this PR I added these configuration options with default values to the Configuraton object..

 /// <summary>
    /// Configure the maximum number of type parameters that a generic request handler can have. To Disable this constraint, set the value to 0.
    /// </summary>
    public int MaxGenericTypeParameters { get; set; } = 10;

    /// <summary>
    /// Configure the maximum number of types that can close a generic request type parameter constraint.  To Disable this constraint, set the value to 0.
    /// </summary>
    public int MaxTypesClosing { get; set; } = 100;

    /// <summary>
    /// Configure the Maximum Amount of Generic RequestHandler Types MediatR will try to register.  To Disable this constraint, set the value to 0.
    /// </summary>
    public int MaxGenericTypeRegistrations { get; set; } = 125000;

    /// <summary>
    /// Configure the Timeout in Milliseconds that the GenericHandler Registration Process will exit with error.  To Disable this constraint, set the value to 0.
    /// </summary>
    public int RegistrationTimeout { get; set; } = 15000;

For now these are the default values, but I would like some feedback regarding these values.

I've also added some pretty extensive test methods that confirm this is working as expected.

@jbogard , @hisuwh Please review this at your convenience.

Thanks,