Search code examples
c#webformsninjectninject-extensionsninject-interception

Using Ninject, how can I use property injection, Lazy<>, and interception without encountering errors in castle.core?


I created a simple program as a PoC for an old SharePoint On-Prem project that uses ASP.NET Webforms. In its pages, I have to use property injection, and for everything else, I can use constructor injection. I am also using:

  • Ninject.Extensions.Factory
  • Ninject.Extensions.Interception
  • Ninject.Extensions.Interception.DynamicProxy

Everything was working relatively well until I added interceptors and used Lazy<> to tackle some cyclic dependencies. To simplify this in an example, I've written the following example as a console application:

public class Program
{
    static void Main(string[] args)
    {
        IKernel kernel = new StandardKernel(
            new NinjectSettings() { LoadExtensions = false },
            new DynamicProxyModule(),
            new FuncModule());

        kernel.Bind<ISomeClass>().To<SomeClass>();
        kernel.Bind<IOtherClass>().To<OtherClass>();
        kernel.Bind<IAnotherClass>().To<AnotherClass>();

        kernel.Intercept(p => true).With(new ClassInterceptor()); // Removing this works, but I need the interceptors.

        ISomeClass someClass = kernel.TryGet<ISomeClass>();

        someClass.Foo();
    }

    public interface ISomeClass
    {
        void Foo();
    }

    public class SomeClass : ISomeClass
    {
        [Inject]
        public IOtherClass OtherClass { get; set; }

        public SomeClass() { }

        public void Foo()
        {
            Console.WriteLine("Foo");

            this.OtherClass.Bar();
        }
    }

    public interface IOtherClass
    {
        void Bar();
    }

    public class OtherClass : IOtherClass
    {
        private readonly Lazy<IAnotherClass> _anotherClass;

        public IAnotherClass AnotherClass { get { return this._anotherClass.Value; } }

        public OtherClass(Lazy<IAnotherClass> anotherClass)
        {
            this._anotherClass = anotherClass;
        }

        public void Bar()
        {
            Console.WriteLine("Bar");
        }
    }

    public interface IAnotherClass
    {
        void FooBar();
    }

    public class AnotherClass : IAnotherClass
    {
        private readonly Lazy<IOtherClass> _otherClass;

        public IOtherClass OtherClass { get { return this._otherClass.Value; } }

        public AnotherClass(Lazy<IOtherClass> otherClass)
        {
            this._otherClass = otherClass;
        }

        public void FooBar()
        {
            Console.WriteLine("FooBar");

            this.OtherClass.Bar();
        }
    }

    public class ClassInterceptor: SimpleInterceptor
    {
        public ClassInterceptor() { }

        protected override void BeforeInvoke(IInvocation invocation)
        {
            base.BeforeInvoke(invocation);

            Console.WriteLine("I'm doing stuff before.");
        }

        protected override void AfterInvoke(IInvocation invocation)
        {
            base.BeforeInvoke(invocation);

            Console.WriteLine("I'm doing stuff after.");
        }
    }
}

As a result, I am getting the following error:

Unhandled Exception: System.TypeLoadException: Could not load type 'Castle.Proxies.Func`2Proxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=a621a9e7e5c32e69' because the parent type is sealed.

If I remove the "Lazy<>", it will re-introduce cyclic dependencies. If I remove interception, it will not have any errors, but I need the interceptors. And, I have to use property injection on the pages because the constructors of the pages are managed by webforms with no helpful hooks like there are in MVC land. NOTE: webforms are being used because SharePoint 2013 is being used.

Any ideas how to keep both interception and Lazy<> declarations without encountering the error above?


Solution

  • I was able to resolve this by not adding the interceptors to the entire kernel as there were some types that could not have proxies created for them. Considering the exclusions lists were probably more than I was encountering, I chose the next best option which was to apply global interceptors individually to each binding:

        static void Main(string[] args)
        {
            IKernel kernel = new StandardKernel(
                new NinjectSettings() { LoadExtensions = false },
                new DynamicProxyModule(),
                new FuncModule());
    
            AddInterceptors(kernel.Bind<ISomeClass>().To<SomeClass>());
            AddInterceptors(kernel.Bind<IOtherClass>().To<OtherClass>());
            AddInterceptors(kernel.Bind<IAnotherClass>().To<AnotherClass>());
    
            //kernel.Intercept(p => true).With(new ClassInterceptor());
    
            ISomeClass someClass = kernel.TryGet<ISomeClass>();
    
            someClass.Foo();
        }
    
        private static void AddInterceptors<T>(IBindingWhenInNamedWithOrOnSyntax<T> binding)
        {
            binding.Intercept(p => true).With(new ClassInterceptor());
            /* ... other interceptors ... */
        }
    

    Thanks for letting me know what was going with the interception extension trying to create a proxy for Lazy and T, @BatteryBackupUnit.

    Any better solutions are welcomed!