Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Jul 16, 2024

Conversation

zachpainter77
Copy link
Contributor

@zachpainter77 zachpainter77 commented Jul 5, 2024

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.

  • If the user tries to register types that have more than 10 generic type arguments, the library will throw exception.
  • if the user tries to register a type that has no generic type constraints the library will throw exception.
  • if the user trues to register a type that has too many types that can close a specific parameter, the library will throw exception.
  • if the user tries to register a handler and somehow gets past the previous checks then the library will check the total combinations and throw exception if that number is greater than the max.
  • and last if the registration takes more time to complete than the timeout value in milliseconds the library will throw exception.

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,

  • Zach

Edit: I apologize for not getting this done sooner.. I have just started a new job and haven't had the time. :)

Edit: Added an opt out functionality.

  services.AddMediatR(cfg =>
  {
      //opt out flag set
      cfg.RegisterGenericHandlers = false;
      cfg.RegisterServicesFromAssembly(assembly);
  });

- created a feature flag that enables a user to opt out of registering generic handlers they have defined.

- created a test that ensures that, when the flag is set, the generic handlers defined are not registered.
@jbogard jbogard merged commit cac76be into jbogard:master Jul 16, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants