Это правильный путь к RegisterDecorator, когда некоторые типы не имеют реализации?

Использование простого инжектора с шаблоном команды, описанным здесь. У большинства команд есть сопутствующие классы, которые реализуют AbstractValidator<TCommand> плавной проверки, а это значит, что они также реализуют FV IValidator<TCommand>. Однако не всегда имеет смысл иметь реализацию валидатора для каждой команды.

Насколько я могу судить, реализация декоратора команд не может принимать IValidator<TCommand> в качестве аргумента конструктора, если у каждого ICommandHandler<TCommand> нет соответствующего FV.IValidator<TCommand>. Я пробовал следующее:

public class FluentValidationCommandDecorator<TCommand> 
    : IHandleCommands<TCommand>
{
    public FluentValidationCommandDecorator(IHandleCommands<TCommand> decorated
        , IValidator<TCommand> validator
    )
    {
        _decorated = decorated;
        _validator = validator;
    }
    ...
}
...
container.RegisterManyForOpenGeneric(typeof(IValidator<>), assemblies);
container.RegisterDecorator(typeof(IHandleCommands<>),
    typeof(FluentValidationCommandDecorator<>),
    context =>
    {
        var validatorType =
            typeof (IValidator<>).MakeGenericType(
                context.ServiceType.GetGenericArguments());
        if (container.GetRegistration(validatorType) == null)
            return false;
        return true;
    });

Модульные тесты, которые запускаются Container.Verify() один раз, пройдены. Модульные тесты, которые запускают Container.Verify() более одного раза, терпят неудачу из-за InvalidOperationException при втором вызове:

The configuration is invalid. Creating the instance for type 
IValidator<SomeCommandThatHasNoValidatorImplementation> failed. Object reference
not set to  an instance of an object.

Следующее работает, принимая Container в качестве аргумента:

public class FluentValidationCommandDecorator<TCommand> 
    : IHandleCommands<TCommand>
{
    private readonly IHandleCommands<TCommand> _decorated;
    private readonly Container _container;

    public FluentValidationCommandDecorator(Container container
        , IHandleCommands<TCommand> decorated
    )
    {
        _container = container;
        _decorated = decorated;
    }

    public void Handle(TCommand command)
    {
        IValidator<TCommand> validator = null;
        if (_container.GetRegistration(typeof(IValidator<TCommand>)) != null)
            validator = _container.GetInstance<IValidator<TCommand>>();

        if (validator != null) validator.ValidateAndThrow(command);

        _decorated.Handle(command);
    }
}
...
container.RegisterManyForOpenGeneric(typeof(IValidator<>), assemblies);
container.RegisterDecorator(typeof(IHandleCommands<>),
    typeof(FluentValidationCommandDecorator<>));

Если бы этому классу не нужно было зависеть от Simple Injector, я мог бы переместить его в доменный проект. Домен уже зависит от FluentValidation.net, поэтому допустимость домена можно протестировать. Я думаю, что этот декоратор принадлежит домену, но ни он, ни его проект модульного тестирования не зависят от simpleinjector (или должны были бы, поскольку домен не является корнем композиции).

Есть ли способ указать simpleinjector украшать экземпляр CommandHandler<TCommand> экземпляром FluentValidationCommandDecorator<TCommand> только в том случае, если для IValidator<TCommand> зарегистрирована реализация?


person danludwig    schedule 24.04.2012    source источник


Ответы (1)


Что вам нужно, так это разрешение незарегистрированных типов для сопоставления отсутствующих типов с реализацией по умолчанию. Или другими словами, вам нужно использовать метод RegisterOpenGeneric:

container.RegisterOpenGeneric(typeof(IValidator<>), 
    typeof(NullValidator<>));

Теперь вам нужно определить NullValidator<T>, который реализует IValidator<T> с пустой реализацией/реализацией по умолчанию.

Когда вы делаете это каждый раз, когда запрашивается определенный (незарегистрированный) IValidator<T>, будет возвращен новый экземпляр NullValidator<T>. Он не будет переопределять типы, зарегистрированные с помощью RegisterManyForOpenGeneric, поскольку эти типы зарегистрированы явно.

Когда реализация NullValidator<T> является потокобезопасной (что обычно имеет место с пустой реализацией), вы можете оптимизировать конструкцию, зарегистрировав их как singleton:

container.RegisterSingleOpenGeneric(typeof(IValidator<>), 
    typeof(NullValidator<>));

Дополнительную информацию можно прочитать в вики: Регистрация открытых универсальных типов

person Steven    schedule 24.04.2012
comment
Разрешение незарегистрированного типа, понятно. Кажется, что NullValidator<> принадлежит той же библиотеке, что и simpleinjector, но это позволяет мне убрать декоратор. Мой NullValidator просто наследует AbstractValidator<T> fluentvalidation — никаких конструкторов, полей, ничего — и, насколько я могу судить, является потокобезопасным, поэтому я регистрирую его как единый. - person danludwig; 25.04.2012
comment
Другим побочным эффектом этого, по-видимому, является то, что команды, которые ДЕЙСТВИТЕЛЬНО имеют IValidator<TCommand> реализации, украшаются дважды - один раз с реальной реализацией и один раз с NullValidator. Этого следует ожидать? - person danludwig; 25.04.2012
comment
Я не уверен, почему вы должны иметь NullValidator, чтобы быть декоратором. Я не уверен, что вы делаете. Можете ли вы обновить свой вопрос с вашим текущим кодом и конфигурацией? - person Steven; 25.04.2012
comment
Вы правы, команды с конкретными валидаторами не декорируются дважды. У меня есть 2 команды с одинаковыми именами, 1 с определенным валидатором и 1 без него. Я неправильно прочитал часы отладчика и подумал, что 1 команда была украшена дважды, хотя на самом деле команда, украшенная NullValidator, не имела того же типа TCommand, что и команда, украшенная определенным валидатором. Извините за путаницу. - person danludwig; 25.04.2012
comment
Visual Studio и .NET отображают общие типы в ужасном формате. Меня это сильно раздражает, поэтому я отображаю общие имена типов в более удобном формате в сообщениях об исключениях Simple Injector. - person Steven; 25.04.2012