2014-10-02 5 views
14

Ich habe eine ASP MVC 4 App, die Strukturkarte verwendet. Ich versuche, meine Anwendung über Structuremap interception mit Logging zu versehen. In einer Registry scannen ich eine bestimmte Baugruppe, um alle seine Typen mit der Standard-Konvention zu registrieren:Structuremap abfangen für Registrierung gescannte Typen

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

Der Interceptor:

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

ich die Abfangjäger für einen bestimmten Plugin-Typen hinzufügen wie folgt aus:

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

aber ich will StructureMap machen Logging-Proxies für alle Typen erstellen, die in der Registrierung gescannt wurden. Gibt es einen Weg, dies zu erreichen?

+0

Haben Sie das zur Arbeit gebracht? –

+0

Leider nicht. Ich habe sie manuell für jeden meiner Plugin-Typen hinzugefügt. – rinat

Antwort

8

Es sieht nicht so aus, als gäbe es einen einfachen Erweiterungspunkt, aber ich habe es mit einer ziemlich anständigen Lösung mit einer benutzerdefinierten Konvention arbeiten. Um Ihnen zu helfen, die Entscheidungen zu verstehen, die ich getroffen habe, werde ich Sie durch einige Schritte führen (die vielen, vielen Fehltritte überspringend, die ich auf meinem Weg gemacht habe).

Lassen Sie uns zuerst die DefaultConvention ansehen, die Sie bereits verwenden.

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

Ganz einfach, wir die Art und Interface-Paare erhalten und prüfen Sie, ob sie einen Konstruktor haben, wenn sie es tun wir sie registrieren. Es wäre schön, dies einfach so zu ändern, dass es DecorateWith aufruft, aber Sie können das nur für <>() aufrufen. Verwenden Sie <>(), nicht For(). Verwenden Sie().

Als nächstes sehen wir uns an, was DecorateWith tut:

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

So entsteht ein FuncInterceptor und registriert sie. Ich habe ein gutes Stück Zeit versucht, eine dieser dynamisch mit Reflexion zu schaffen, bevor sie entscheidet, es würde nur einfacher sein, eine neue Klasse zu machen:

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

Diese Klasse nur macht es einfacher, mit zu arbeiten, wenn wir die Art haben als eine Variable.

Endlich habe ich meine eigene Konvention basierend auf der Standard Konvention gemacht.

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

Es ist fast genau das gleiche mit einem Zusatz, erstelle ich einen Abfangjäger und interceptorType für jede Art wir registrieren. Ich registriere diese Politik dann.

Abschließend einige Unit-Tests, es zu beweisen funktioniert:

[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 
{ 

} 

ist es auf jeden Fall Raum für einige hier aufzuräumen (Caching, machen es trocken, mehr Tests machen es einfacher zu konfigurieren), aber es funktioniert für was Sie brauchen und ist eine ziemlich vernünftige Art, es zu tun.

Bitte fragen Sie, wenn Sie weitere Fragen dazu haben.

+0

Das sieht gut aus, ich brauche Zeit, um den alten strucutremap-Code umzuformatieren, da ich eine ältere Version wiederherstellen musste. Dies ist viel mehr Code und es scheint seltsam, sie haben die Fähigkeit, dies einfach zu tun entfernt. – Simon

+0

Dies nur Magd mein Tag danke für die Rost Antwort und gehen Sie durch den Code. Ich benutze eine neuere Version von structuremap (v4.0.30319), die mich dazu zwingt, die ScanTypes-Methode in der DefaultConventionWithProxyScanner-Klasse zu implodieren, aber ich habe nur diese Schleife durch das TypeSet und alle Process-Methode, die für mich funktionierte. – TechLiam

+0

Oh, ich gehe auch Fehler für generische Klassen wie IRepository , die nicht Instanzen für sie erstellt haben können, aber da ich diesen Code nicht wirklich protokollieren möchte, bin ich ok, aber andere müssen eine Möglichkeit kennen, um dies zu umgehen. – TechLiam