Структурный перехват для отсканированных типов реестра

У меня есть приложение ASP MVC 4, которое использует Structuremap. Я пытаюсь добавить logging к моему приложению через перехват Structuremap. В реестре я просматриваю конкретную сборку, чтобы зарегистрировать все ее типы со стандартным соглашением:

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

Перехватчик:

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

Я могу добавить перехватчик для одного конкретного типа плагина следующим образом:

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

но я хочу, чтобы структура структуры создала прокси-сервер для всех типов, которые были проверены в реестре. Есть ли способ достичь этого?

Ответы

Ответ 1

Не похоже на то, что для этого есть простая точка расширения, но я получил работу с довольно приличным решением с использованием специального соглашения. Чтобы помочь вам понять решения, которые я принял, я проведу вас через несколько шагов (пропуская много и много ошибок, которые я сделал на моем пути).

Сначала давайте посмотрим на DefaultConvention, который вы уже используете.

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

Довольно просто, мы получаем пары типов и интерфейсов и проверяем, есть ли у них конструктор, если они их регистрируют. Было бы неплохо просто изменить это так, чтобы он вызывал DecorateWith, но вы можете называть это только для параметра "Для < > (). Используйте < > (), а не For(). Используйте().

Далее рассмотрим, что делает DecorateWith:

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

Итак, это создает FuncInterceptor и регистрирует его. Я потратил довольно много времени, пытаясь создать один из них динамически с отражением, прежде чем решить, что проще будет создать новый класс:

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

Этот класс просто упрощает работу, когда у нас есть тип как переменная.

Наконец, я сделал свою собственную конвенцию на основе соглашения по умолчанию.

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

Это почти то же самое с одним добавлением, я создаю перехватчик и interceptorType для каждого типа, который мы регистрируем. Затем я зарегистрирую эту политику.

Наконец, несколько модульных тестов, чтобы доказать, что это работает:

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

}

Здесь определенно есть место для очистки (кэширование, сделать его СУХОЙ, больше тестов, упростить настройку), но оно работает для того, что вам нужно, и это довольно разумный способ сделать это.

Спросите, есть ли у вас какие-либо другие вопросы.