Украшение универсального интерфейса с Structuremap - PullRequest
5 голосов
/ 31 октября 2011

У меня есть универсальный интерфейс, который принимает два универсальных типа. Я хочу украсить все возвращенные версии, но поскольку я не знаю тип при вызове EnrichWith, он, очевидно, не компилируется. Я попытался использовать перегрузку EnrichWith, которая передается в контексте, думая, что, возможно, я мог бы получить переданные универсальные типы и вызвать Activator.CreateInstance, но контекст не имеет никакой полезной информации при отладке и проверке.

Вот что у меня есть. Это мой общий интерфейс:

public interface IServiceOperation<in TRequest, out TResponse> where TResponse : ServiceResult, new()
{
    TResponse PerformService(TRequest validatedRequest);
}

Вот пример реализации:

public class SignUpService : IServiceOperation<SignUpRequest, SignUpResult>
{
    private readonly IUserRepository _userRepo;

    public SignUpService(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }

    public SignUpResult PerformService(SignUpRequest validatedRequest)
    {
        var user = Mapper.Map<User>(validatedRequest);

        user.MarkAsLoggedIn();
        user.ChangePassword(validatedRequest.UnhashedPassword);

        using(var transaction = _userRepo.BeginTransaction())
        {
            _userRepo.Save(user);
            transaction.Commit();
        }

        return new SignUpResult();
    }
}

Вот мой декоратор, который принимает и другой сервис:

public class ValidateServiceDecorator<TRequest, TResponse> : IServiceOperation<TRequest, TResponse> where TResponse : ServiceResult, new()
{
    private readonly IServiceOperation<TRequest, TResponse> _serviceOperation;
    private readonly IValidationService _validationService;

    public ValidateServiceDecorator(IServiceOperation<TRequest, TResponse> serviceOperation,
        IValidationService validationService)
    {
        _serviceOperation = serviceOperation;
        _validationService = validationService;
    }

    public TResponse PerformService(TRequest request)
    {
        var response = new TResponse();
        var validationResult = _validationService.Validate(request);

        if (!validationResult.IsValid)
        {
            response.ValidationErrors = validationResult.ValidationErrors;
            return response;
        }

        return _serviceOperation.PerformService(request);
    }

Наконец, вот как далеко я продвинулся в своем контейнере. Это, очевидно, не компилируется, но строка EnrichWith показывает, чего я пытаюсь достичь:

public class StructureMapServiceScanner : Registry
{
    public StructureMapServiceScanner()
    {
        Scan(scanner =>
                {
                    scanner.AssemblyContainingType(typeof (IServiceOperation<,>));
                    scanner.ConnectImplementationsToTypesClosing(typeof (IServiceOperation<,>));
                });

        For(typeof (IServiceOperation<,>))
        .EnrichWith((ioc, original) => new ValidateServiceDecorator(original, ioc.GetInstance<IValidationService>()));
    }
}

И поскольку этот вопрос требовал немного больше кода, вот мой тест, который я пытаюсь пройти:

[TestClass]
public class StructureMapServiceScannerSpecs
{
    [TestMethod]
    public void Test()
    {
        ObjectFactory.Configure(cfg =>
                                    {
                                        cfg.AddRegistry<StructureMapServiceScanner>();
                                        cfg.For<IUserRepository>().Use(new Mock<IUserRepository>().Object);
                                        cfg.For<IValidationService>().Use(new Mock<IValidationService>().Object);
                                    });

        var service = ObjectFactory.GetInstance<IServiceOperation<SignUpRequest, SignUpResult>>();

        service.ShouldNotBeNull();
        service.ShouldBeType<ValidateServiceDecorator<SignUpRequest, SignUpResult>>();
    }
}

Мне кажется, что это что-то, что должно быть простым, и я действительно что-то упускаю из-за того, как использовать StructureMap. Я мог бы создать специфичные для типа версии для всех комбинаций типов запросов и ответов, но, очевидно, это нежелательно. Так чего мне не хватает?

Ответы [ 2 ]

4 голосов
/ 01 ноября 2011

В конце концов смог разобраться. Я создал RegistrationConvention:

public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        var interfacesImplemented = type.GetInterfaces();

        foreach (var interfaceImplemented in interfacesImplemented)
        {
            if (interfaceImplemented.IsGenericType && interfaceImplemented.GetGenericTypeDefinition() == typeof(IServiceOperation<,>))
            {
                var genericParameters = interfaceImplemented.GetGenericArguments();
                var closedValidatorType = typeof(ValidateServiceDecorator<,>).MakeGenericType(genericParameters);

                registry.For(interfaceImplemented)
                    .EnrichWith((context, original) => Activator.CreateInstance(closedValidatorType, original,
                                                                                context.GetInstance<IValidationService>()));
            }
        }
    }
}
3 голосов
/ 16 ноября 2011

Вот подход, который все еще использует возможности IoC StructureMap, позволяя легко вводить дополнительные сервисы в ваш декоратор. Он не идеален, поскольку предполагает, что вы используете основной контейнер, а не дочерний контейнер, но, вероятно, он будет работать для большинства сценариев.

public class ServiceRegistrationConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        var handlerInterfaces = (from t in type.GetInterfaces()
                                 where t.IsGenericType &&
                                       t.GetGenericTypeDefinition() == typeof (IHandle<,>)
                                 select t);

        foreach (var handler in handlerInterfaces)
        {
            var decoratorType = typeof (ValidationDecorator<,>).MakeGenericType(handler.GetGenericArguments());

            registry.For(handler)
                .EnrichWith((ctx, orig) => ObjectFactory.With(handler, orig).GetInstance(decoratorType));
        }
    }
}

В идеале IContext от StructureMap должен предоставлять метод With, как это делает IContainer. Без этого, на самом деле не существует отличного решения этой проблемы.

...