2014-10-02 9 views
14

Mam aplikację ASP MVC 4, która korzysta z Structuremap. Próbuję dodać rejestrowanie do mojej aplikacji poprzez przechwytywanie Structuremap. w rejestrze, skanować konkretny zespół w celu zarejestrowania wszystkich jego rodzajów z domyślnym schematem:Przechwycenie mapy strukturalnej dla skanowanych typów rejestru

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

Interceptor:

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

mogę dodać przechwytywania dla jednego konkretnego rodzaju wtyczek w ten sposób:

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

, ale chcę utworzyć mapę struktury tworzenia dzienników rejestrowania dla wszystkich typów, które zostały zeskanowane w rejestrze. Czy istnieje sposób, aby to osiągnąć?

+0

Czy to działa? –

+0

Niestety nie. Dodałem je ręcznie dla każdego z moich typów wtyczek. – rinat

Odpowiedz

8

Wygląda na to, że nie ma łatwego punktu rozszerzenia, ale pracowałem z dość przyzwoitym rozwiązaniem przy użyciu niestandardowej konwencji. Aby pomóc ci zrozumieć decyzje, które podjąłem, przeprowadzę Cię przez kilka kroków (pomijając wiele, wiele błędnych kroków, które zrobiłem na mojej drodze).

Najpierw spójrzmy na DefaultConvention, której już używasz.

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

Całkiem proste, mamy pary i typ interfejsu i upewnij się, że mają konstruktora, jeśli robią je zarejestrować. Byłoby miło po prostu zmodyfikować to tak, aby wywoływał DecorateWith, ale możesz to wywołać tylko dla: <>(). Użyj <>(), nie dla(). Użyj().

Następny pozwala spojrzeć na to, co robi DecorateWith:

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

Więc ta tworzy FuncInterceptor i rejestruje go. Spędziłem sprawiedliwego trochę czasu stara się stworzyć jedną z nich dynamicznie z refleksji przed podjęciem decyzji, byłoby to po prostu łatwiejsze nową klasę:

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

Klasa ta właśnie sprawia, że ​​łatwiej pracować, gdy mamy typ jako zmienna.

W końcu stworzyłem własną Konwencję opartą na konwencji 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); 
    } 
} 

To prawie dokładnie to samo z jednym dodatkiem, tworzę przechwytujący i typ interceptor dla każdego zarejestrowanego typu. Następnie rejestruję tę politykę.

koniec kilka testy jednostkowe to udowodnić działa:

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

} 

Jest zdecydowanie miejsce dla niektórych oczyścić tutaj (buforowanie, sprawiają, że DRY, więcej testów, łatwiej skonfigurować), ale to działa na czego potrzebujesz i jest to całkiem rozsądny sposób robienia tego.

Proszę zapytać, jeśli masz inne pytania na ten temat.

+0

To wygląda dobrze, potrzebuję czasu na refaktoryzację starego kodu strucutremap, ponieważ musiałem przywrócić poprzednie wydanie. Jest to o wiele więcej kodu i wydaje się dziwne, że usunęli możliwość zrobienia tego po prostu. – Simon

+0

To tylko pokojówka mój dzień dzięki za odpowiedź wdzięczności i chodzić przez kod. Używam nowszej wersji structuremap (v4.0.30319), która wymagała ode mnie zastosowania metody ScanTypes w klasie DefaultConventionWithProxyScanner, ale mam tylko tę pętlę przez TypeSet i całą metodę Process, która działała dla mnie. – TechLiam

+0

Oh I również błędy dla klas generycznych, takich jak IRepository , które nie mogą mieć utworzonych dla nich instancji, ale tak naprawdę nie chcę rejestrować tego kodu, jestem w porządku, ale inni mogą potrzebować znać sposób, aby to obejść. – TechLiam