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.
Czy to działa? –
Niestety nie. Dodałem je ręcznie dla każdego z moich typów wtyczek. – rinat