Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving generic Decorators with Simple Injector

I am trying to build out a structure where I have a base IValidator<> interface that will be generated for our system based on some metadata. We want to give future developers the flexibility to 1) Regenerate the concrete implementations of IValidator<> if need be without disturbing any hand-written code and 2) Add decorators to IValidator<> to be able to extend the functionality without disturbing the auto-generated code.

I would like to have some way to resolve the generic decorators at runtime using the RegisterDecorator method of Simple Injector so our dev team does not need to go and update the composition root every time we add a decorator.

Here are some example classes/interfaces

public interface IValidator<T> where T : class
{
    void Validate(T entity);
}
public class ClientValidator : IValidator<Client>
{
    public void Validate(Client entity)
    {
        //Auto-generated
    }
}
public class UserValidator : IValidator<User>
{
    public void Validate(User entity)
    {
        //Auto-generated
    }
}
public class ClientValidatorDecorator : IValidator<Client> 
{
    private readonly IValidator<Client> clientValidator;

    public ClientValidatorDecorator(IValidator<Client> clientValidator)
    {
        this.clientValidator = clientValidator;
    }
    public void Validate(Client entity)
    {
        //New rules
        this.clientValidator.Validate(entity);
    }
}
public class UserValidatorDecorator : IValidator<User>
{
    private readonly IValidator<User> userValidator;

    public UserValidatorDecorator(IValidator<User> userValidator)
    {
        this.userValidator = userValidator;
    }
    public void Validate(User entity)
    {
        //New rules
        this.userValidator.Validate(entity);
    }
}
public class ValidationContext
{
    private readonly IValidator<Client> client;
    private readonly IValidator<User> user;

    public ValidationContext(IValidator<Client> client, IValidator<User> user)
    {
        this.client = client;
        this.user = user;
    }
}

We I am trying to do something like so:

public void RegisterServices(Container container)
{
    container.Register(typeof(IValidator<>), AssemblyManifest.GetAssemblies());
    container.RegisterDecorator(typeof(IValidator<>), GetType, Lifestyle.Transient, UseType);
}
private static Type GetType(DecoratorPredicateContext ctx)
{
    //Return appropriate Decorator
}
private static bool UseType(DecoratorPredicateContext ctx)
{
    //Predicate
}

Unfortunately, unless I resolve a concrete type RegisterDecorator throws an error, so resolving another generic seems out. I am not sure how to proceed. Is there a way to do something like this? Is there a better way to get the intended functionality without decorators? We were thinking partial classes, but that has its own set of issues.

Any help will be appreciated!

like image 606
Carson Avatar asked Feb 07 '23 16:02

Carson


2 Answers

Rather than plugging in decorators you could use a Composite Validator to enable the addition of IValidator<> implementations as required. This solution would allow the code to contain multiple IValidator<>'s for the same type.

Internally your code will still be able to depend on a single IValidator<T> which would resolve to the CompositeValidator that would call zero or more validators depending on what has been registered in the container at runtime.

The composite validator:

public class CompositeValidator<T> : IValidator<T>
{
    public readonly IEnumerable<IValidator<T>> validators;

    public CompositeValidator(IEnumerable<IValidator<T>> validators)
    {
        this.validators = validators;
    }

    public void Validate(T item)
    {
        foreach(var validator in this.validators)
        {
            validator.Validate(item);
        }
    }
}

The container is configured like this:

var assemblies = new[] { typeof(IValidator<>).Assembly };
var container = new Container();
container.RegisterCollection(typeof(IValidator<>), assemblies);
container.Register(typeof(IValidator<>), typeof(CompositeValidator<>));

where the assemblies variable contains all the assemblies you want to search for validators.

When you resolve IValidator<User> using container.GetInstance<IValidator<User>>() or through constructor injection you get back CompositeValidator<User> which internally references any and all IValidator<User>'s.

like image 165
qujck Avatar answered Feb 13 '23 21:02

qujck


The way to get decorators of a type using batch registration is by calling the GetTypesToRegister method overload that accepts a TypesToRegisterOptions object. This way you can instruct SI to return decorators as well.

container.Register(typeof(IValidator<>), assemblies);

var t1 = container.GetTypesToRegister(typeof(IValidator<>), assemblies);
var t2 = container.GetTypesToRegister(typeof(IValidator<>), assemblies,
    new TypesToRegisterOptions { IncludeDecorators = true });

foreach (Type t in t2.Except(t1)) {
    container.RegisterDecorator(typeof(IValidator<>), t);
}

Do note that I do not suggest using this code. @qujck's answer addresses the design issue you have with your code, and his solutions therefore brings you to a much better place.

like image 29
Steven Avatar answered Feb 13 '23 19:02

Steven