2015-04-07 13 views
5

Supponiamo di avere il seguente:autofac e Contratto classi

[ContractClass(typeof(ContractClassForIFoo))] 
public interface IFoo 
{ 
    int DoThing(string x); 
} 

public class Foo : IFoo { ... } 

[ContractClassFor(typeof(IFoo))] 
public class ContractClassForIFoo : IFoo 
{ 
    public int DoThing(string x) 
    { 
     Contract.Requires<ArgumentNullException>(x != null); 
     return 0; 
    } 
} 

Sto usando Autofac di registrare tutti i miei componenti che implementano IFoo:

builder.RegisterAssemblyTypes(ThisAssembly).As<IFoo>(); 

Quando poi risolvere il mio dipendenze con:

var dependencies = container.Resolve<IFoo[]>(); 
var dependencies = container.Resolve<IFoo[]>(); 

Dovrei ottenere tutte le classi che implementano IFooeccetto classe (e) di contratto. Come impedisco a di tutte le classi di contratto di risolvere il mio senza doverle spostare completamente in un assembly separato?

posso fare qualcosa di simile:

builder.RegisterAssemblyTypes(ThisAssembly) 
    .Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null) 
    .As<IFoo>(); 

Ma avrei bisogno di fare questo per ogni registrazione componente. Qualcosa che riguarda tutte le registrazioni sarebbe più bello. È possibile avere un'esclusione globale su tipi risolti da Autofac se hanno l'attributo ContractClassForAttribute?

risposta

1

EDIT Come spiegato nel commento di Steven, il ContractClass e ContractClassFor sono contrassegnati con [Conditional("CONTRACTS_FULL")], questa soluzione può introdurre bug per questi attributi. Vedi il commento di Steven per una spiegazione migliore.


Non so alcun meccanismi che consentono filtro globale sulle immatricolazioni registrate con il metodo RegisterAssemblyTypes. L'unica soluzione per filtrare la registrazione utilizzando questo metodo è utilizzare il metodo Where come mostrato nell'esempio di codice.

Quando una registrazione è registrata in un ComponentRegistry non è possibile rimuoverlo dal registro.

Se non si desidera utilizzare il metodo Where in ogni registrazione, è possibile creare un altro metodo.

public static class ContractClassRegistrationExtensions 
{ 
    public static IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> NotContractClass<TLimit, TScanningActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> registration) where TScanningActivatorData : ScanningActivatorData 
    { 
     if (registration == null) 
     { 
      throw new ArgumentNullException("registration"); 
     } 

     return registration.Where(t => t.GetCustomAttribute<ContractClassForAttribute>() == null); 
    } 
} 

Utilizzando questo metodo, invece di

builder.RegisterAssemblyTypes(ThisAssembly) 
     .Where(t=> t.GetCustomAttribute<ContractClassForAttribute>() == null) 
     .As<IFoo>(); 

Sarete in grado di scrivere:

builder.RegisterAssemblyTypes(ThisAssembly) 
     .NotContractClass() 
     .As<IFoo>(); 

Non è una vera soluzione ma è la soluzione che userei in un caso simile.

A proposito, se si vuole veramente qualcosa di magico usando Autofac, è possibile implementare un IRegistrationSource

public class FilterRegistrationSource : IRegistrationSource 
{ 
    private static MethodInfo _createFilteredRegistrationMethod = typeof(FilterRegistrationSource).GetMethod("CreateFilteredRegistration"); 

    public Boolean IsAdapterForIndividualComponents 
    { 
     get 
     { 
      return false; 
     } 
    } 

    public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) 
    { 
     IServiceWithType serviceWithType = service as IServiceWithType; 

     if (serviceWithType == null) 
     { 
      yield break; 
     } 

     Type serviceType = serviceWithType.ServiceType; 
     if (!serviceType.IsClosedTypeOf(typeof(IEnumerable<>))) 
     { 
      yield break; 
     } 
     Type elementType = new Type[] { serviceType }.Concat(serviceType.GetInterfaces()) 
             .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) 
             .Select(t => t.GetGenericArguments()[0]) 
             .First(); 

     yield return (IComponentRegistration)FilterRegistrationSource._createFilteredRegistrationMethod.MakeGenericMethod(elementType) 
                    .Invoke(this, new Object[] { serviceWithType }); 
    } 

    public IComponentRegistration CreateFilteredRegistration<T>(IServiceWithType serviceWithType) 
    { 
     return RegistrationBuilder.ForDelegate((cc, p) => cc.ComponentRegistry 
                  .RegistrationsFor(serviceWithType.ChangeType(typeof(T))) 
                  .Where(r => !r.Activator.LimitType.GetCustomAttributes(typeof(ContractClassForAttribute), false).Any()) 
                  .Select(r => r.Activator.ActivateInstance(cc, p)) 
                  .Cast<T>()) 
            .As((Service)serviceWithType) 
            .CreateRegistration(); 

    } 
} 

È possibile registrare in questo modo: builder.RegisterSource(new FilterRegistrationSource())

Non ho ancora testato la pena di prestazioni di questa soluzione, usala con cautela.

Un'altra soluzione interessante sarebbe utilizzare AOP per personalizzare il modo in cui si registrano le registrazioni.

+0

Questo è molto fragile e sconsiglio di provare a filtrare i tipi che sono contrassegnati con '[ContractClass]' o '[ContractClassFor] perché questi stessi attributi sono contrassegnati con' [Conditional ("CONTRACTS_FULL")] 'attributo, il che significa che quando l'assembly è compilato senza il simbolo di compilazione condizionale' CONTRACTS_FULL', il tipo diventerà un tipo 'normale' e sarà nuovamente registrato. È probabile che gli sviluppatori rimuovano questo simbolo nella configurazione RELEASE. Quindi il problema è che questo bug comparirà solo in UAT o in produzione dove effettivamente usi una build di rilascio. – Steven

+1

@ Grazie per questa informazione, non ero a conoscenza di questa caratteristica. Ho modificato la mia risposta. –

0

Una soluzione migliore a questo problema è definire correttamente la classe del contratto. Si consiglia che quando si crea una classe contenente i contratti per l'assembly che la classe è private, e abstract:

[ContractClass(typeof(ContractClassForIFoo))] 
public interface IFoo 
{ 
    int DoThing(string x); 
} 

public class Foo : IFoo { ... } 

[ContractClassFor(typeof(IFoo))] 
private abstract class ContractClassForIFoo : IFoo 
{ 
    public int DoThing(string x) 
    { 
     Contract.Requires<ArgumentNullException>(x != null); 
     throw new NotImplementedException(); 
    } 
} 

Ora, la classe è private, quindi autofac non dovrebbe essere in grado di vederlo — ma di Certo, potrebbe essere perché probabilmente sta usando il riflesso; ma dal momento che è private, non dovrebbe tentare di registrarlo. In aggiunta a ciò, è abstract, e quindi non può essere istanziato in modo definitivo comunque. Questo risolve tutti i problemi.

Inoltre, tutti i metodi nelle classi di contratto devono essere throw new NotImplementedException();. In questo modo, se ti dimentichi di contrassegnarlo come private o abstract, tutti i metodi vengono lanciati. Dovresti scoprirlo molto velocemente durante lo sviluppo. Solo l'uso di forme degenerate di metodi può sfuggire alla tua comunicazione.

Questo è il modello consigliato dal manuale Contratti di codice e comunità.