7

I'm using IServiceCollection/IServiceProvider from Microsoft.Extensions.DependencyInjection.

I want to inject a delegate into a class:

public delegate ValidationResult ValidateAddressFunction(Address address);

public class OrderSubmitHandler
{
    private readonly ValidateAddressFunction _validateAddress;

    public OrderSubmitHandler(ValidateAddressFunction validateAddress)
    {
        _validateAddress = validateAddress;
    }

    public void SubmitOrder(Order order)
    {
        var addressValidation = _validateAddress(order.ShippingAddress);
        if(!addressValidation.IsValid)
            throw new Exception("Your address is invalid!");
    }
}

The implementation of ValidateAddressFunction I want to inject comes from a class that must be resolved from the container because it has dependencies of its own:

public class OrderValidator
{
    private readonly ISomeDependency _dependency;

    public OrderValidator(ISomeDependency dependency)
    {
        _dependency = dependency;
    }

    public ValidationResult ValidateAddress(Address address)
    {
        // use _dependency
        // validate the order
        // return a result
        return new ValidationResult();
    }
}

In this example I'm using a delegate, but I could just as well be injecting Func<Address, ValidationResult>.

I could just inject OrderValidator, but I'd rather not create an interface with just one method. If all my class needs is one method then I'd rather depend directly on that.

How do I register the delegate or Func in such a way that when it's resolved, the class that contains the method will be resolved, and then I can use the method from the resolved instance?

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62

1 Answers1

14

Register the delegate or Func<Address, ValidationResult> using a factory method that resolves the type which provides the method, and then returns the method.

In your example you would want to resolve OrderValidator and return its ValidateAddress method.

// register the class that provides the method and its dependencies.
services.AddSingleton<OrderValidator>();
services.AddSingleton<ISomeDependency, SomeDependency>();

// register the delegate using a factory method that resolves 
// the type that provides the method and then returns the method.
services.AddSingleton<ValidateAddressFunction>(serviceProvider =>
{
    var validator = serviceProvider.GetRequiredService<OrderValidator>();
    return validator.ValidateAddress;
});

This would work exactly the same way if you were registering Func<Address, ValidationResult> instead of a delegate:

services.AddSingleton<Func<Address,ValidationResult>>(serviceProvider =>
{
    var validator = serviceProvider.GetRequiredService<OrderValidator>();
    return validator.ValidateAddress;
});

You can simplify the registration using an extension. It's not that much shorter, but it still helps if you're likely to have multiple such registrations. It might also help to express your intent so it's perfectly clear that you're registering a delegate to be injected as opposed to a class instance or interface implementation:

public static class ServiceCollectionDelegateExtensions
{
    public static IServiceCollection RegisterDelegateFromService<TService, TDelegate>(
        this IServiceCollection serviceCollection,
        Func<TService, TDelegate> getDelegateFromService)
        where TDelegate : Delegate
    {
        return serviceCollection.AddTransient(serviceProvider =>
            getDelegateFromService(serviceProvider.GetRequiredService<TService>()));
    }
}

In this case the delegate is registered using AddTransient because the lifetime of the class is really determined when the class is registered.

Now the registration would look like this:

services.RegisterDelegateFromService<OrderValidator, ValidateAddressFunction>
    (validator => validator.ValidateAddress);
Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • 1
    Thanks. The reason why this appeals to me is that interfaces are easier to pollute. I find myself creating lots of single-method interfaces and I don't want random stuff getting added into them. Using a delegate helps to steer away from that. It's ultimate interface segregation. – Scott Hannen Jun 04 '19 at 15:50
  • I can see why you would use this thanks to your comment, I have this bookmarked in my browser now, thanks again! :) – Ryan Wilson Jun 04 '19 at 15:55
  • @ScottHannen Was just about to answer this when I saw you answered it yourself. Yep good work. Nice design. – Nkosi Jun 04 '19 at 16:45