Asp.net-core – How to get started with ASP.NET (5) Core and Castle Windsor for Dependency Injection

asp.net-coreasp.net-core-mvccastle-windsordependency-injection

Background:

I've used Castle Windsor with Installers and Facilities according to the Castle Windsor tutorial with earlier versions of MVC (pre-6) and WebAPI.

ASP.NET (5) Core has included some Dependency Injection support but I still haven't figured out exactly how to wire it up, and the few samples I have found look a lot different than how I've used it before (with the installers/facilities). Most examples predate ASP.NET (5) cores recent release and some seem to have outdated information.

It seems to have changed quite radically from the previous versions composition root setup, and not even Microsoft.Framework.DependencyInjection.ServiceProvider can resolve all of the dependencies when I set it as the Castle Windsor DI fallback. I'm still digging into the details but there isn't much up to date information.

My attempt to use Castle Windsor for DI

I've found an adapter like this: Github Castle.Windsor DI container.

Startup.cs

    private static IWindsorContainer container;
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
    {
        container = new WindsorContainer();
        app.UseServices(services =>
        {
            // ADDED app.ApplicationServices FOR FALLBACK DI
            container.Populate(services, app.ApplicationServices);
            container.BeginScope();
            return container.Resolve<IServiceProvider>();
        });
        // ... default stuff

WindsorRegistration.cs
I added a few lines to add a Castle Windsor ILazyComponentLoader fallback.

using Castle.MicroKernel.Lifestyle;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.Resolvers.SpecializedResolvers;
using Castle.Windsor;
using Microsoft.Framework.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Notes.Infrastructure
{
    /// <summary>
    /// An adapted current autofac code to work with Castle.Windsor container.
    /// https://github.com/aspnet/Home/issues/263
    /// </summary>
    public static class WindsorRegistration
    {
        public static void Populate(
                this IWindsorContainer container,
                IEnumerable<IServiceDescriptor> descriptors,
                IServiceProvider fallbackProvider // ADDED FOR FALLBACK DI
                )
        {
            // ADDED FOR FALLBACK DI
            // http://davidzych.com/2014/08/27/building-the-castle-windsor-dependency-injection-populator-for-asp-net-vnext/
            // Trying to add a fallback if Castle Windsor doesn't find the .NET stuff
            var fallbackComponentLoader = new FallbackLazyComponentLoader(fallbackProvider);
            container.Register(Component.For<ILazyComponentLoader>().Instance(fallbackComponentLoader));

            // Rest as usual from the Github link
            container.Register(Component.For<IWindsorContainer>().Instance(container));
            container.Register(Component.For<IServiceProvider>().ImplementedBy<WindsorServiceProvider>());
            container.Register(Component.For<IServiceScopeFactory>().ImplementedBy<WindsorServiceScopeFactory>());

            container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));

            Register(container, descriptors);
        }

        private static void Register(
                IWindsorContainer container,
                IEnumerable<IServiceDescriptor> descriptors)
        {
            foreach (var descriptor in descriptors)
            {
                if (descriptor.ImplementationType != null)
                {
                    // Test if the an open generic type is being registered
                    var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo();
                    if (serviceTypeInfo.IsGenericTypeDefinition)
                    {
                        container.Register(Component.For(descriptor.ServiceType)
                                                .ImplementedBy(descriptor.ImplementationType)
                                                .ConfigureLifecycle(descriptor.Lifecycle)
                                                .OnlyNewServices());
                    }
                    else
                    {
                        container.Register(Component.For(descriptor.ServiceType)
                                                .ImplementedBy(descriptor.ImplementationType)
                                                .ConfigureLifecycle(descriptor.Lifecycle)
                                                .OnlyNewServices());
                    }
                }
                else if (descriptor.ImplementationFactory != null)
                {
                    var service1 = descriptor;
                    container.Register(Component.For(descriptor.ServiceType)
                            .UsingFactoryMethod<object>(c =>
                            {
                                var builderProvider = container.Resolve<IServiceProvider>();
                                return
                                    service1.ImplementationFactory(builderProvider);
                            })
                            .ConfigureLifecycle(descriptor.Lifecycle)
                            .OnlyNewServices());
                }
                else
                {
                    container.Register(Component.For(descriptor.ServiceType)
                            .Instance(descriptor.ImplementationInstance)
                            .ConfigureLifecycle(descriptor.Lifecycle)
                            .OnlyNewServices());
                }
            }
        }

        private static ComponentRegistration<object> ConfigureLifecycle(
                this ComponentRegistration<object> registrationBuilder,
                LifecycleKind lifecycleKind)
        {
            switch (lifecycleKind)
            {
                case LifecycleKind.Singleton:
                    registrationBuilder.LifestyleSingleton();
                    break;

                case LifecycleKind.Scoped:
                    registrationBuilder.LifestyleScoped();
                    break;

                case LifecycleKind.Transient:
                    registrationBuilder.LifestyleTransient();
                    break;
            }

            return registrationBuilder;
        }

        private class WindsorServiceProvider : IServiceProvider
        {
            private readonly IWindsorContainer _container;

            public WindsorServiceProvider(IWindsorContainer container)
            {
                _container = container;
            }

            public object GetService(Type serviceType)
            {
                return _container.Resolve(serviceType);
            }
        }

        private class WindsorServiceScopeFactory : IServiceScopeFactory
        {
            private readonly IWindsorContainer _container;

            public WindsorServiceScopeFactory(IWindsorContainer container)
            {
                _container = container;
            }

            public IServiceScope CreateScope()
            {
                return new WindsorServiceScope(_container);
            }
        }

        private class WindsorServiceScope : IServiceScope
        {
            private readonly IServiceProvider _serviceProvider;
            private readonly IDisposable _scope;

            public WindsorServiceScope(IWindsorContainer container)
            {
                _scope = container.BeginScope();
                _serviceProvider = container.Resolve<IServiceProvider>();
            }

            public IServiceProvider ServiceProvider
            {
                get { return _serviceProvider; }
            }

            public void Dispose()
            {
                _scope.Dispose();
            }
        }
    }
}

First hiccup and resolution attempt

From that example I was getting:

An exception of type 'Castle.MicroKernel.ComponentNotFoundException' occurred in Castle.Windsor.dll but was not handled in user code
Additional information: No component for supporting the service Microsoft.Framework.Runtime.IAssemblyLoaderEngine was found

It wasn't available looking in the debugger at the Castle Fallback – Microsoft.Framework.DependencyInjection.ServiceProvider (table of services).

From http://davidzych.com/tag/castle-windsor/ I have tried to add a Fallback since Windsor couldn't resolve all of the ASP.NET dependencies.

FallbackLazyComponentLoader.cs

/// <summary>
/// https://github.com/davezych/DependencyInjection/blob/windsor/src/Microsoft.Framework.DependencyInjection.Windsor/FallbackLazyComponentLoader.cs
/// </summary>
public class FallbackLazyComponentLoader : ILazyComponentLoader
{
    private IServiceProvider _fallbackProvider;

    public FallbackLazyComponentLoader(IServiceProvider provider)
    {
        _fallbackProvider = provider;
    }

    public IRegistration Load(string name, Type service, IDictionary arguments)
    {
        var serviceFromFallback = _fallbackProvider.GetService(service);
        if (serviceFromFallback != null)
        {
            return Component.For(service).Instance(serviceFromFallback);
        }
        return null;
    }
}

It was seemingly necessary (to inject all the .NET dependencies)

I could comment out startup.cs app.UseBrowserLink(); to get rid of the IAssemblyLoaderEngine exception.

        if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase))
        {
            //app.UseBrowserLink(); // 

Now I run into an exception:

An exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll but was not handled in user code

Trying to get the service: {Name = "IUrlHelper" FullName = "Microsoft.AspNet.Mvc.IUrlHelper"}

    public IRegistration Load(string name, Type service, IDictionary arguments)
    {
        var serviceFromFallback = _fallbackProvider.GetService(service);

How to move forward?

What is wrong with this attempt to wire up Castle Windsor DI into ASP.NET (5) Core?

Best Answer

For now I don't think you can use Castle Windsor Container as the DI container because Windsor doesn't support the new DNVM. But AutoFac does and they follow the same rule.

In the Startup.cs there is a ConfigureServices method whose return type is void. You can change the return type to ISerivceProvider and return a concrete IServiceProvider, the system will use the new IServiceProvider as the default DI container. Below is the AutoFac example.

public IServiceProvider ConfigureServices(IServiceCollection services)
{
       services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
       services.AddMvc();

       var builder = new ContainerBuilder();
       AutofacRegistration.Populate(builder, services);
       var container = builder.Build();
       return container.Resolve<IServiceProvider>();
}

The other DI adapters also implemented the similar interfaces. You can try yourself, but note AutoFac is in beta5 now so you need to make some adjustment to make your application run.

Hope this helps