Search code examples
autofaccastle-windsorasp.net-core-3.1autofac-configurationautofac-module

Replacing CastleWindsor with Autofac in .NETCore3.1


I was using CastleWindsor in my ASP.NETCore2.2 WebAPI project and was working fine. I'm migrating to ASP.NETCore3.1 now and it doesn't look like CastleWindor has offical support for that so I decided to move to Autofac with minimal changes but having some issues resolving the dependencies.

In my project, I've maintained very loose coupling between different layers in the application namely, business layer, data layer, and translation layer. All of those layers are in their own assemblies. And then in my main project, I've a folder say "dependencies" which will hold all the DLLs of differnet layers. Additionally, I've a separate project that lists all the interfaces that are implemented by the different layers and which needs to be resolved by the IoC container.

The project having all the interfaces looks like this:

namespace Shared.Interfaces
{
    public interface IBusinessLayer<T>
    {
       ....
    }

    public interface IDataLayer<T>
    {
       ....
    }

    public interface ITranslationLayer<T>
    {
       ....
    }
}

The implementing projects looks like this:

namespace POC.Person.BusinessLayer
{
    public class BusinessLayer<T> : IBusinessLayer<T> where T : Models.Person
   {
      ...
   }
}

namespace POC.Person.DataLayer
    {
        public class DataLayer<T> : IDataLayer<T> where T : Models.Person
       {
          ...
       }
    }

namespace POC.Person.TranslationLayer
    {
        public class TranslationLayer<T> : ITranslationLayer<T> where T : Models.Person
       {
          ...
       }
    }

Using Autofac in my migrated .netcore3.1 project, Startup.cs looks like this:

public void ConfigureServices(IServiceCollection services)
        {   
            services.AddControllers();
            //and other codes
        }
        
        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule(new DependencyResolver());
        }

DependencyResolver is a class that inherits from Autofac.Module, which is again in a separate assembly in different project which looks like this:

namespace IOC.Autofac
{
    public class DependencyResolver: Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            base.Load(builder);

            // get our path to dependencies folder in the main project
            var path = Directory.GetCurrentDirectory() + "\\dependencies\\";
            
            //get all the assemblies inside that folder
            List<Assembly> assemblies = new List<Assembly>();
            foreach (string assemblyPath in Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories))
            {
                var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
                assemblies.Add(assembly);
            }

            // Register and resolve the types with the container
            builder
            .RegisterAssemblyTypes(assemblies.ToArray())
            .AsClosedTypesOf(typeof(IBusinessLayer<>))
            .AsClosedTypesOf(typeof(IDataLayer<>))
            .AsClosedTypesOf(typeof(ITranslationLayer<>))
            .AsImplementedInterfaces()
            .InstancePerRequest();  
         }
    }
}

I'm getting this error and I've not been able to fix it: ":"Unable to resolve service for type 'Shared.Interfaces.IBusinessLayer`1[Models.Person]' while attempting to activate 'POC.Person.Controllers.PersonController'.","

Inside my controller I've injection which looks like this:

namespace POC.Person.Controllers
{
    public class PersonController : ControllerBase
    {
        private readonly IBusinessLayer<Models.Person> _bl;

        public PersonController(IBusinessLayer<Models.Person> bl)
        {
            _bl = bl;
        }
        
        //other codes
    }
}

Program.cs looks like this:

namespace POC.Person
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = BuildWebHost(args);
            host.Build().Run();
        }

        public static IHostBuilder BuildWebHost(string[] args)
        {
            return Host.CreateDefaultBuilder(args)
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())         
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseKestrel()
                                .UseStartup<Startup>()
                                .UseIIS()
                                .UseIISIntegration();
                    ;
                }).ConfigureAppConfiguration((context, config) =>
                {
                    var builtConfig = config.Build();
                });
        }
    }
}

It looks like with autofac involving generics, registering and resolving the type is not that straight forward?


Solution

  • Autofac does not currently support registering open generics whilst assembly scanning. It's a long-running known issue. You can do assembly scanning, you can register open generics, you can't do both at the same time. There are some ideas in that linked issue on ways some folks have solved it.

    Out of the box, the scanning logic would, thus, be reduced to:

    builder
      .RegisterAssemblyTypes(assemblies.ToArray())
      .AsImplementedInterfaces()
      .InstancePerRequest();  
    

    You need to register generics separately, like:

    builder
      .RegisterGeneric(typeof(TranslationLayer<>))
      .As(typeof(ITranslationLayer<>));