2014-10-02 20 views
14

Ho un'app ASP MVC 4 che utilizza Structuremap. Sto cercando di aggiungere la registrazione alla mia applicazione tramite l'intercettazione di Structuremap. in un registro, la scansione di un assembly specifico al fine di registrare tutto quanto di tipi con la convenzione di default:Intercettazione mappa strutturale per tipi scansionati di registro

public class ServicesRegistry : Registry 
{ 
    public ServicesRegistry() 
    { 
     Scan(x => 
     { 
      x.AssemblyContainingType<MyMarkerService>(); 
      x.WithDefaultConventions(); 
     }); 
    } 
} 

L'intercettore:

public class LogInterceptor : IInterceptor 
{ 
    public void Intercept(IInvocation invocation) 
    { 
     var watch = Stopwatch.StartNew(); 
     invocation.Proceed(); 
     watch.Stop();//log the time 
    } 
} 

posso aggiungere l'intercettore per un tipo specifico di plug-in in questo modo:

var proxyGenerator = new ProxyGenerator(); 
container.Configure(x => x.For<IServiceA>().Use<ServiceA>().DecorateWith(instance => proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor()))); 

ma voglio fare StructureMap creare proxy di registrazione per tutti i tipi che sono stati analizzati nel registro di sistema. C'è un modo per raggiungere questo obiettivo?

+0

Hai funzionato? –

+0

Purtroppo no. Li ho aggiunti manualmente per ognuno dei miei tipi di plugin. – rinat

risposta

8

Non sembra che ci sia un semplice punto di estensione per questo, ma ho capito che funziona con una soluzione abbastanza decente usando una convenzione personalizzata. Per aiutarti a capire le decisioni che ho preso ti guiderò attraverso alcuni passaggi (saltando i molti, molti passi falsi che ho fatto sulla mia strada).

Per prima cosa dare un'occhiata a DefaultConvention che si sta già utilizzando.

DefaultConvention:

public class DefaultConventionScanner : ConfigurableRegistrationConvention 
{ 
    public override void Process(Type type, Registry registry) 
    { 
     if (!TypeExtensions.IsConcrete(type)) 
      return; 
     Type pluginType = this.FindPluginType(type); 
     if (pluginType == null || !TypeExtensions.HasConstructors(type)) 
      return; 
     registry.AddType(pluginType, type); 
     this.ConfigureFamily(registry.For(pluginType, (ILifecycle)null)); 
    } 

    public virtual Type FindPluginType(Type concreteType) 
    { 
     string interfaceName = "I" + concreteType.Name; 
     return Enumerable.FirstOrDefault<Type>((IEnumerable<Type>)concreteType.GetInterfaces(), (Func<Type, bool>)(t => t.Name == interfaceName)); 
    } 
} 

Abbastanza semplice, otteniamo il tipo di interfaccia e coppie e controllare per assicurarsi che essi hanno un costruttore, se lo fanno li registriamo. Sarebbe bello modificare questo in modo che chiami DecorateWith, ma puoi chiamarlo solo per For <>(). Utilizza <>(), non Per(). Usa().

successiva consente di guardare a ciò che fa DecorateWith:

public T DecorateWith(Expression<Func<TPluginType, TPluginType>> handler) 
{ 
    this.AddInterceptor((IInterceptor) new FuncInterceptor<TPluginType>(handler, (string) null)); 
    return this.thisInstance; 
} 

Quindi questo crea un FuncInterceptor e la registra. Ho passato un bel po 'di tempo cercando di creare uno di questi in modo dinamico con la riflessione prima di decidere che sarebbe stato più facile fare una nuova classe:

public class ProxyFuncInterceptor<T> : FuncInterceptor<T> where T : class 
{ 
    public ProxyFuncInterceptor() : base(x => MakeProxy(x), "") 
    { 
    } 

    protected ProxyFuncInterceptor(Expression<Func<T, T>> expression, string description = null) 
     : base(expression, description) 
    { 
    } 

    protected ProxyFuncInterceptor(Expression<Func<IContext, T, T>> expression, string description = null) 
     : base(expression, description) 
    { 
    } 

    private static T MakeProxy(T instance) 
    { 
     var proxyGenerator = new ProxyGenerator(); 
     return proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor()); 
    } 
} 

Questa classe rende solo più facile lavorare con quando abbiamo il tipo come variabile.

Infine ho creato la mia convenzione basata sulla convenzione di default.

public class DefaultConventionWithProxyScanner : ConfigurableRegistrationConvention 
{ 
    public override void Process(Type type, Registry registry) 
    { 
     if (!type.IsConcrete()) 
      return; 
     var pluginType = this.FindPluginType(type); 
     if (pluginType == null || !type.HasConstructors()) 
      return; 
     registry.AddType(pluginType, type); 
     var policy = CreatePolicy(pluginType); 
     registry.Policies.Interceptors(policy); 

     ConfigureFamily(registry.For(pluginType)); 
    } 

    public virtual Type FindPluginType(Type concreteType) 
    { 
     var interfaceName = "I" + concreteType.Name; 
     return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName); 
    } 

    public IInterceptorPolicy CreatePolicy(Type pluginType) 
    { 
     var genericPolicyType = typeof(InterceptorPolicy<>); 
     var policyType = genericPolicyType.MakeGenericType(pluginType); 
     return (IInterceptorPolicy)Activator.CreateInstance(policyType, new object[]{CreateInterceptor(pluginType), null});  
    } 

    public IInterceptor CreateInterceptor(Type pluginType) 
    { 
     var genericInterceptorType = typeof(ProxyFuncInterceptor<>); 
     var specificInterceptor = genericInterceptorType.MakeGenericType(pluginType); 
     return (IInterceptor)Activator.CreateInstance(specificInterceptor); 
    } 
} 

sua quasi esattamente la stessa cosa con un'aggiunta, creo un intercettore e interceptorType per ogni tipo registriamo. Quindi registro quella politica.

Infine, un paio di unit test per dimostrare che funziona:

[TestFixture] 
public class Try4 
{ 
    [Test] 
    public void Can_create_interceptor() 
    { 
     var type = typeof (IServiceA); 
     Assert.NotNull(new DefaultConventionWithProxyScanner().CreateInterceptor(type)); 
    } 

    [Test] 
    public void Can_create_policy() 
    { 
     var type = typeof (IServiceA); 
     Assert.NotNull(new DefaultConventionWithProxyScanner().CreatePolicy(type)); 
    } 

    [Test] 
    public void Can_register_normally() 
    { 
     var container = new Container(); 
     container.Configure(x => x.Scan(y => 
     { 
      y.TheCallingAssembly(); 
      y.WithDefaultConventions(); 
     })); 

     var serviceA = container.GetInstance<IServiceA>(); 
     Assert.IsFalse(ProxyUtil.IsProxy(serviceA)); 
     Console.WriteLine(serviceA.GetType()); 
    } 

    [Test] 
    public void Can_register_proxy_for_all() 
    { 
     var container = new Container(); 
     container.Configure(x => x.Scan(y => 
     { 
      y.TheCallingAssembly(); 
      y.Convention<DefaultConventionWithProxyScanner>(); 
     })); 

     var serviceA = container.GetInstance<IServiceA>(); 
     Assert.IsTrue(ProxyUtil.IsProxy(serviceA)); 
     Console.WriteLine(serviceA.GetType()); 
    } 

    [Test] 
    public void Make_sure_I_wait() 
    { 
     var container = new Container(); 
     container.Configure(x => x.Scan(y => 
     { 
      y.TheCallingAssembly(); 
      y.Convention<DefaultConventionWithProxyScanner>(); 
     })); 

     var serviceA = container.GetInstance<IServiceA>(); 
     serviceA.Wait(); 
    } 
} 
} 

public interface IServiceA 
{ 
    void Wait(); 
} 

public class ServiceA : IServiceA 
{ 
    public void Wait() 
    { 
     Thread.Sleep(1000); 
    } 
} 

public interface IServiceB 
{ 

} 

public class ServiceB : IServiceB 
{ 

} 

C'è sicuramente spazio per qualche ripulire qui (caching, fare asciugare, più test, rendono più facile da configurare) ma funziona per quello di cui hai bisogno ed è un modo abbastanza ragionevole per farlo.

Si prega di chiedere se avete altre domande a riguardo.

+0

Questo sembra buono, ho bisogno di tempo per rifattorizzare il vecchio codice strucutremap come ho dovuto ripristinare una versione precedente. Questo è molto più codice e sembra strano che abbiano rimosso la capacità di farlo semplicemente. – Simon

+0

Questa è la mia giornata di ringraziamento per la risposta alla griglia e per il codice. Sto usando una versione più recente di structuremap (v4.0.30319) che mi ha richiesto di implodere il metodo ScanTypes nella classe DefaultConventionWithProxyScanner ma ho solo quel ciclo attraverso il TypeSet e tutto il metodo Process che ha funzionato per me. – TechLiam

+0

Oh ho anche commesso errori per classi generiche come IRepository che non possono essere create da istanze per loro ma poiché non desidero veramente registrare quel codice, sono ok ma altri potrebbero aver bisogno di sapere come aggirare questo problema. – TechLiam