2011-10-31 16 views
5

Ho un'interfaccia generica che accetta due tipi generici. Voglio decorare tutte le versioni restituite, ma poiché non conosco il tipo quando si chiama EnrichWith, ovviamente non viene compilato. Ho provato a utilizzare il sovraccarico EnrichWith che passa nel contesto, pensando che forse potrei prendere i tipi generici passati e chiamare Activator.CreateInstance, ma il contesto non ha alcuna informazione utile su di esso durante il debug e l'ispezione.Decorazione di un'interfaccia generica con Structuremap

Ecco quello che ho finora. Questa è la mia interfaccia generica:

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

Ecco un esempio di implementazione:

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

Ecco il mio decoratore, che prende in un altro servizio, nonché:

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

Infine, ecco come lontano ho ottenuto sul mio contenitore. Questo, ovviamente, non si compila, ma la linea EnrichWith mostra quello che sto cercando di realizzare:

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

E proprio perché questa domanda aveva bisogno di un po 'più di codice, ecco la mia prova che sto cercando di arrivare a passare :

Mi sento come se fosse qualcosa che dovrebbe essere semplice, e mi manca davvero qualcosa su come usare StructureMap. Potrei creare versioni specifiche per tipo per tutte le combinazioni di tipi di richiesta e risposta, ma ovviamente non è auspicabile. Quindi cosa mi sto perdendo?

+0

È stato in grado di capirlo, utilizzando un RegistrationConvention per arricchire ogni tipo chiuso dell'interfaccia direttamente. Pubblicherei quello che ho fatto, ma non posso per altre poche ore. – Robert

risposta

4

È stato in grado di capirlo, alla fine. Ho creato una 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

Ecco un approccio che sfrutta ancora le capacità di IoC StructureMap, consentendo ai servizi aggiuntivi da iniettare facilmente nel vostro decoratore. Non è perfetto poiché presuppone che si stia utilizzando il contenitore principale e non un contenitore secondario, ma probabilmente funzionerà per la maggior parte degli scenari.

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, IContext di StructureMap dovrebbe esporre il Con il metodo proprio come IContainer fa. Senza quello, non c'è davvero una grande soluzione a questo problema.