2011-10-31 12 views
5

Tengo una interfaz genérica, que admite dos tipos genéricos. Quiero decorar todas las versiones devueltas, pero como no sé el tipo cuando llamo a EnrichWith, obviamente no compila. He intentado utilizar la sobrecarga EnrichWith que pasa en el contexto, pensando que tal vez podría tomar los tipos genéricos pasados ​​y llamar a Activator.CreateInstance, pero el contexto no tiene información útil al depurarlo e inspeccionarlo.Decorar una interfaz genérica con Structuremap

Esto es lo que tengo hasta ahora. Este es mi interfaz genérica:

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

Aquí está un ejemplo de implementación:

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(); 
    } 
} 

Aquí es mi decorador, que se lleva en otro servicio, así:

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); 
    } 

Por último, aquí es cómo Hasta ahora he subido a mi contenedor. Esto, obviamente, no se compila, pero la línea EnrichWith muestra lo que estoy tratando de lograr:

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>())); 
    } 
} 

Y sólo porque esta pregunta necesitaba un poco más de código, aquí está mi prueba de que estoy tratando de llegar a pasar :

[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>>(); 
    } 
} 

Siento que esto es algo que debería ser simple, y realmente me falta algo con cómo usar StructureMap. Pude crear versiones de tipo específico para todas las combinaciones de tipos de Solicitud y Respuesta, pero obviamente eso no es deseable. Entonces, ¿qué me estoy perdiendo?

+0

Pude resolverlo, usando RegistrationConvention para enriquecer cada tipo cerrado de la interfaz directamente. Publicaba lo que hice, pero no puedo por unas pocas horas. – Robert

Respuesta

4

Pude resolverlo, eventualmente. He creado un 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

Aquí es un enfoque que todavía aprovecha las capacidades del COI StructureMap, permitiendo a los servicios adicionales que se inyecta fácilmente en su decorador. No es perfecto, ya que supone que está utilizando el contenedor primario y no un contenedor hijo, pero probablemente funcionará para la mayoría de los escenarios.

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)); 
     } 
    } 
} 

Idealmente, el IContext de StructureMap debería exponer el método With al igual que IContainer. Sin eso, no hay realmente una gran solución para este problema.

Cuestiones relacionadas