I'm using Autofac. 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?
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.
var builder = new ContainerBuilder();
// register the class that provides the method and its dependencies.
builder.RegisterType<OrderValidator>();
builder.RegisterType<SomeDependency>().As<ISomeDependency>();
// register the delegate using a factory method that resolves
// the type that provides the method and then returns the method.
builder.Register<ValidateAddressFunction>(context =>
{
var validator = context.Resolve<OrderValidator>();
return validator.ValidateAddress;
});
This would work exactly the same way if you were registering Func<Address, ValidationResult> instead of a delegate:
builder.Register<Func<Address, ValidationResult>>(context =>
{
var validator = context.Resolve<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 AutofacDelegateExtensions
{
public static IRegistrationBuilder<TDelegate, SimpleActivatorData, SingleRegistrationStyle> RegisterDelegateFromService<TService, TDelegate>(
this ContainerBuilder builder,
Func<TService, TDelegate> getDelegateFromService,
string sourceComponentName = null)
where TDelegate : class
{
var registrationFunction = new Func<IComponentContext, TDelegate>(context =>
{
var source = sourceComponentName == null
? context.Resolve<TService>()
: context.ResolveNamed<TService>(sourceComponentName);
return getDelegateFromService(source);
});
return builder.Register(registrationFunction);
}
}
Autofac registrations are transient by default, so the lifetime of the class that provides the delegate is determined by the registration of that class.
Now the registration would look like this:
builder.RegisterDelegateFromService<OrderValidator, ValidateAddressFunction>(validator =>
validator.ValidateAddress);
Or, if the dependency which provides the method - in this case OrderValidator is registered with a name and we must resolve it using that name:
builder.RegisterDelegateFromService<OrderValidator, ValidateAddressFunction>(validator =>
validator.ValidateAddress, "RegistrationName");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With