3

We have components registrations in Castle Windsor container like so

void RegisterComponent<TInterface, TImplementation>() {
    var component = Component.For<TInterface>().ImplementedBy<TImplementation>();
    component.Interceptors<SomeInterceptor>();
    container.Register(component);
}

However we got to the problem that when we do a method call from within the class it does not get intercepted. For example we have component like

ServiceA : IService {

    public void MethodA1() {
        // do some stuff
    }

    public void MethodA2() {
        MethodA1();
    }

}

And if we call MethodA2 or MethodA1 methods from some other class it is intercepted, but MethodA1 apparently not intercepted when called from MethodA2 since the call is from within the class.

We have found similar case with the solution Castle Dynamic Proxy not intercepting method calls when invoked from within the class However the solution features component and proxy creation using new operator which is not suitable in our case since we are using container. Can we use this solution with component registration like above? Or are there other approaches to solve the problem?

Community
  • 1
  • 1
Nikolay K
  • 3,770
  • 3
  • 25
  • 37

5 Answers5

3

For interception to work on MethodA1 when invoked from MethodA2 you need to be using inheritance based interception (it's because you are using this reference to make the invocation).

To make inheritance based interception possible first you need to make MethodA1 and MethodA2 virtual.

Then you can make container registration like this:

container.Register(Component.For<ServiceA>().Interceptors<SomeInterceptor>());
container.Register(Component.For<IService>().UsingFactoryMethod(c => c.Resolve<ServiceA>()));

First register your service as itself applying interceptors (this will add inheritance based interception over the service). Then you can register the interface which will use service registered earlier.

Krzysztof Branicki
  • 7,417
  • 3
  • 38
  • 41
  • Is there any way to to register the service as Interface but use Inheritance based interception without this hack? Why is it disabled by default? – xumix Mar 02 '16 at 06:04
  • Unfortunately I don't know about any switch that would let you change default interception mechanism, but I never really needed this so never really looked hard. I usually use interception on application layer services to do things like transaction management. So in my case it doesn't make much sens to have this behavior (e.g. if interceptor already opened transaction during outer method invocation I don't want that to happen again for inner method call). – Krzysztof Branicki Mar 02 '16 at 08:46
  • @krzysztof-kozmic So there is no simple way to use Inheritance based interception? – xumix Mar 02 '16 at 10:51
  • Thank you for your answer! However we have found another solution by ourselves and used it in the project, I will post it as another answer. Also we have tried your solution and it is working as good as ours, so I'll accept your answer. – Nikolay K Mar 04 '16 at 16:32
  • @xumix You can look at the implementation of [`DefaultProxyFactory`](https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/Windsor/Proxy/DefaultProxyFactory.cs) which handles proxy creation and see that there are no options to use `CreateClassProxy` method which uses Inheritance-based proxy. However take a look at my answer, maybe it will be helpful. – Nikolay K Mar 04 '16 at 17:51
2

Change your registration to the following and Windsor should switch to class proxies - i.e. using inheritance for interception, instead of composition.

void RegisterComponent<TInterface, TImplementation>() {
    container.Register(Component.For<TInterface,TImplementation>().ImplementedBy<TImplementation>().Interceptors<SomeInterceptor>());
}
Phil Degenhardt
  • 7,215
  • 3
  • 35
  • 46
  • This is not a solution since you are unable to register all Controllers for example. Or you must register it one by one. – xumix Mar 05 '16 at 11:27
  • ? This is the registration method provided by the asker with a single change to force class proxies. – Phil Degenhardt Mar 05 '16 at 21:16
1

We use CreateClassProxy method to create the proxy for the service as it was proposed in an answer to the question Castle Dynamic Proxy not intercepting method calls when invoked from within the class. Then we register the obtained proxy as an implementation for the interface. So our custom RegisterComponent method looks like this

private void RegisterComponent<TInterface, TImplementation>()
    where TInterface : class 
    where TImplementation : class, TInterface
{
    var proxyType = new ProxyGenerator().CreateClassProxy<TImplementation>().GetType();
    Container.Register(Component.For<TInterface>().ImplementedBy(proxyType));
}

The full component registration is

Container = new WindsorContainer();
Container.Kernel.Resolver.AddSubResolver(new CollectionResolver(Container.Kernel));

// Interceptor
Container.Register(Component.For<IInterceptor>().ImplementedBy<SomeInterceptor>().LifestyleTransient());

// Component registrations
RegisterComponent<ISomeService, SomeService>();

And, of course, all methods you need to intercept should be virtual since inheritance based proxy is used.

However a drawback of this solution is that you could not use constructor injection when creating a proxy object. Notice that you are creating "dummy" proxy object with new operator only to get a type of the proxy. Therefore you are unable to use constructor injection only when constructing a dummy proxy, but when you resolve your service via container, injection would work just fine. So this drawback is critical only for components with construction logic being more complex than just assigment of dependencies. If you need only dependency assigments you can try to resolve all dependencies from container manually before creating dummy proxy

private object[] ResolveConstructorParameters<TType>()
{
    return typeof(TType).GetConstructors()
                        .Single(c => c.IsPublic)
                        .GetParameters()
                        .Select(p => _container.Resolve(p.ParameterType))
                        .ToArray();
}

and then RegisterComponent would become

private void RegisterComponent<TInterface, TImplementation>()
    where TInterface : class
    where TImplementation : class, TInterface
{
    var constructorParameters = ResolveConstructorParameters<TImplementation>();
    var proxyType = new ProxyGenerator().CreateClassProxy(typeof(TImplementation), constructorParameters).GetType();
    _container.Register(Component.For<TInterface>().ImplementedBy(proxyType));
}

You can also just fill arguments with null.

Community
  • 1
  • 1
Nikolay K
  • 3,770
  • 3
  • 25
  • 37
  • Why didn't you try to overrride DefaultProxyFactory and implement Inheritance-based proxy there? Inability to use constructor injection is a show-stopper for me :( – xumix Mar 05 '16 at 11:25
  • @xumix I have updated my answer with more explanations. Maybe later I would try to overrride `DefaultProxyFactory`, but you also could try and add another answer to this question :) – Nikolay K Mar 05 '16 at 18:55
1

@NikolayKondratyev I've looked into https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/Windsor/Proxy/DefaultProxyFactory.cs#L110 and I've done the registration the easy way: container.Register(Classes.FromThisAssembly().BasedOn(typeof(IRepositoryBase<,>)) .WithServiceAllInterfaces().WithServiceSelf() .LifestyleTransient());

Note .WithServiceSelf() call, this actually switches class-based proxying

xumix
  • 593
  • 7
  • 17
  • I guess this does the same as registering component for implementation of a service like others proposed. – Nikolay K Mar 09 '16 at 09:38
  • @NikolayKondratyev nope, this adds not only the service as itself but also adds all of its interfaces. Also you dont need to register components 1-by-1 using this method – xumix Mar 09 '16 at 09:40
  • I was talking about `WithServiceSelf`. Great solution! – Nikolay K Mar 09 '16 at 16:23
0

I know this is an old thread, but I just came across it while getting Castle interceptors working in Blazor WASM (which they actually do, but beware...Mono can't seem to support proxying any class that has any generic methods...).

Anyway, to get around this issue in my case, I simply injected the container into my class, and in the method that needed to call a "sibling method" via this I simply resolved a fresh instance of my interface and called the method on that. It won't work for scenarios with shared context/transient states, but the interceptor indeed does its thing.

In Blazor's client WASM app's Program.cs:

public static async Task Main(string[] args)
{
    WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args);

    ...

    builder.ConfigureContainer<IWindsorContainer>(new WindsorServiceProviderFactory(), container =>
    {
        container.Register(Component.For<IInterceptor>()
                                    .ImplementedBy<BlazorInterceptor>()
                                    .Named("BlazorInterceptor").LifestyleTransient());
    });

    ...

    builder.Services.AddScoped<IService, Service>();
    
    ...

    await builder.Build().RunAsync();
}

Example service and interface implementation:

public Interface IService
{
    MethodA(int arg);
    MethodB(int arg);
}

[Interceptor("BlazorInterceptor")]
public class Service : IService
{
    private readonly IServiceProvider _container;

    public Service(IServiceProvider container)
    {
        this._container = container;
    }

    public MethodA(int arg)
    {
        IService service = this._container.GetRequiredService<IService>();
        service.MethodB(arg);
    }

    public MethodB(int arg)
    {
        //should be intercepted...just in a different instance of the service unless you're using singletons...
    }
}

Pros: Doesn't require virtualizing methods or complicating your DI configuration. Cons: Kind of gross (useful for stateless repositories, but would probably give something like EF a heart attack).

Dharman
  • 30,962
  • 25
  • 85
  • 135
domino
  • 66
  • 2