How can I get started with ASP.NET (5) Core and Castle Windsor for Dependency Injection?
Asked Answered
P

3

17

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?

Purlieu answered 5/2, 2015 at 20:14 Comment(3)
IApplicationBuilder.UseServices() which you're using in your sample code is no longer included in ASP.NET 5. It's been removed in one of the beta versions. @jason-li's answer provides an alternative.Maccabean
Related: #32789137Sadesadella
We have created an adapter for it. You can get more information on our Github page: github.com/volosoft/castle-windsor-ms-adapterWithin
A
5

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

Atmosphere answered 11/5, 2015 at 7:17 Comment(1)
More on this approach can be found in the ASP.NET 5 documentation page on Dependency Injection: docs.asp.net/en/latest/fundamentals/…Maccabean
T
2

There is a lot going on in your question, and to be honest I don't understand all of it.

However, there is a working Castle Windsor composition root in MvcSiteMapProvider that you are welcome reverse-engineer. Follow these steps to get a working composition root demo project for Windsor:

  1. Create a new MVC 5 project.
  2. Install MvcSiteMapProvider.MVC5.DI.Windsor.
  3. Analyze the following files for the basic structure:
    • /App_Start/DIConfig.cs
    • /App_Start/CompositionRoot.cs
    • /DI/InjectableControllerFactory.cs
    • /DI/Windsor/WindsorDependencyInjectionContainer.cs
    • /DI/Windsor/Installers/MvcInstaller.cs
    • /DI/Windsor/Installers/MvcSiteMapProviderInstaller.cs

Once you have this working configuration, you can then refactor it and add to it to suit your application's needs.

As I recall, there weren't any changes required to make the MVC 4 DI configuration work with MVC 5. So, the problem you are running into is most likely one of the following:

  • You are using a 3rd party DI component that is not compatible with MVC 5.
  • You are using DependencyResolver, and your configuration doesn't include the necessary code to resolve the dependencies of MVC 5.
  • You are using advanced features of Castle Windsor that we are not using, and have them misconfigured in some way.

ControllerFactory vs DependencyResolver

Do note that according to Dependency Injection in .NET by Mark Seemann (which I highly recommend), it is ill-advised to use IDependencyResolver with Castle Windsor because it guarantees resource leaks. In fact, this is probably the most compelling argument that he makes for his reasoning for declaring service locator as anti-pattern.

The recommended approach is to use IControllerFactory as the integration point into MVC, which implements a ReleaseController method to solve this issue.

Templar answered 6/2, 2015 at 15:3 Comment(2)
Thanks for the quite extensive post, unfortunately ASP.NET 5 includes MVC 6 with some radical changes including a rudimentary default DI container, which I haven't been able to figure out how to modify to get Castle Windsor in as a replacement/supplementary container. Your post seems to be covering MVC 5 and pre ASP.NET 5 projects which are different (and I've had DI working in them)Purlieu
Argh - Now I remember why I didn't hit the submit button before - because I realized as useful as my answer was it was not for the right framework. Yea, you are definitely swimming in new waters...Templar
T
0

So looking at your code, literally all of it can be replaced by Castle.Windsor.MsDependencyInjection library.

Add Castle.Windsor.MsDependencyInjection to your project then use like so:

    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        // Normal component registration can go here...

        return WindsorRegistrationHelper.CreateServiceProvider(yourWindsorContainer, services);
    }
Tamathatamaulipas answered 10/11, 2017 at 10:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.