网络核心 Web api 项目的 class 库中的依赖注入

Dependency Injection inside class library for a net core web api project

我有一个 .NET Core WEB API 项目,我想作为 .NET CORE Class 库成为多个“项目”的容器。

我有的是:

首先,在我的“轨道”项目中,Startup class,到目前为止我所做的:

  public void ConfigureServices(IServiceCollection services)
    {

        this.ConfigureMVC(services);
        this.ConfigureIOC(services);

    }

    private void ConfigureMVC(IServiceCollection services)
    {
        IMvcBuilder mvcBuilder = services.AddMvc(option => { option.EnableEndpointRouting = false; });
        // for each assembly inside modules directory, add the controllers
        foreach (string assemblyPath in Directory.GetFiles($"{System.AppDomain.CurrentDomain.BaseDirectory}/Modules", "*.dll", SearchOption.AllDirectories))
        {
            var assembly = Assembly.LoadFile(assemblyPath);
            mvcBuilder.AddApplicationPart(assembly);
        }
    }

这部分效果很好,因为我们可以触发在我的 SpaceRadar 项目中定义的控制器。 但是我想在我的 class 库项目中使用依赖注入,如果可能的话,通过扫描 dll 来获取扩展 IScopedServices / ISingletonServices / ITransientServices 的所有类型。

但老实说,我不知道在哪里注册我的接口及其各自的实现。 我试过这个解决方案:

private void ConfigureIOC(IServiceCollection services)
    {

        // Store all the type that need to be injected in the IOC system
        List<Type> implementationTypes = new List<Type>();
        List<Type> singletons = new List<Type>();
        List<Type> scopeds = new List<Type>();
        List<Type> transients = new List<Type>();

        // for each assembly, load it, populate the type list of things to be injected 
        foreach (string assemblyPath in Directory.GetFiles(System.AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories))
        {    
            var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
            implementationTypes.AddRange(assembly.GetExportedTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
            singletons.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
            scopeds.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
            transients.AddRange(assembly.GetExportedTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
        }

        // Register into the service collection
        foreach (Type type in singletons)
        {
            services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
        }
        foreach (Type type in scopeds)
        {
            services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
        }
        foreach (Type type in transients)
        {
            services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
        }

}

不过好像是程序集的上下文有问题。 我也尝试了 Assembly.LoadFrom() 但没有用,在博客

上找到的 ReaderLoadContext 解决方案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;

namespace Orbit
{
    public class ReaderLoadContext : AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;

        public ReaderLoadContext(string readerLocation)
        {
            _resolver = new AssemblyDependencyResolver(readerLocation);
        }

        protected override Assembly Load(AssemblyName assemblyName)
        {
            string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
            if (assemblyPath != null)
            {
                return LoadFromAssemblyPath(assemblyPath);
            }

            return null;
        }

        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);

            if (libraryPath != null)
            {
                return LoadUnmanagedDllFromPath(libraryPath);
            }

            return IntPtr.Zero;
        }
    }
}

甚至在程序集内调用“setup”方法


        private void ConfigureIOC(IServiceCollection services)
        {
            // for each assembly, load it, add the controller into the mvcBuilder and populate the type list of things to be injected 
            foreach (string assemblyPath in Directory.GetFiles($"{System.AppDomain.CurrentDomain.BaseDirectory}/Modules", "*.dll", SearchOption.AllDirectories))
            {
                Assembly assembly = Assembly.LoadFrom(assemblyPath);
                Type startup = assembly.GetTypes().SingleOrDefault(t => t.Name == "Startup");
                if (startup != null)
                {
                    var setupMethod = startup.GetMethod("Setup");
                    setupMethod.Invoke(setupMethod, new Object[] { services });
                }
            }
        }

在我的 class 图书馆里

public static void Setup(IServiceCollection services)
        {
            
            List<Type> implementationTypes = new List<Type>();
            List<Type> singletons = new List<Type>();
            List<Type> scopeds = new List<Type>();
            List<Type> transients = new List<Type>();

            foreach (string assemblyPath in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll", SearchOption.AllDirectories))
            {
                var assembly = Assembly.Load(assemblyPath);
                // get all Singleton, Scoped and Transient interfaces
                implementationTypes.AddRange(assembly.GetTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
                singletons.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
                scopeds.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
                transients.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
            }

            // Register into the service collection
            foreach (Type type in singletons)
            {
                services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
            }
            foreach (Type type in scopeds)
            {
                services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
            }
            foreach (Type type in transients)
            {
                services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
            }
            
        }

我还尝试了 HostingStartup 属性

[assembly: HostingStartup(typeof(SpaceRadar.ServiceKeyInjection))]
namespace SpaceRadar
{
    public class ServiceKeyInjection : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((context, services) => {
                List<Type> implementationTypes = new List<Type>();
                List<Type> singletons = new List<Type>();
                List<Type> scopeds = new List<Type>();
                List<Type> transients = new List<Type>();

                foreach (string assemblyPath in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll", SearchOption.AllDirectories))
                {
                    var assembly = Assembly.Load(assemblyPath);
                    // get all Singleton, Scoped and Transient interfaces
                    implementationTypes.AddRange(assembly.GetTypes().Where(type => type.IsClass && (type.GetInterface("ISingletonServices") != null || type.GetInterface("IScopedServices") != null || type.GetInterface("ITransientServices") != null)));
                    singletons.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ISingletonServices") != null));
                    scopeds.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("IScopedServices") != null));
                    transients.AddRange(assembly.GetTypes().Where(type => type.IsInterface && type.GetInterface("ITransientServices") != null));
                }

                // Register into the service collection
                foreach (Type type in singletons)
                {
                    services.AddSingleton(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
                }
                foreach (Type type in scopeds)
                {
                    services.AddScoped(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
                }
                foreach (Type type in transients)
                {
                    services.AddTransient(type, implementationTypes.Single(t => t.GetInterface(type.FullName) != null));
                }
            });
        }
    }
}

所有这些解决方案都不允许我在 class 库中使用这种控制器:

 public class RadarControllers : BaseControllers
    {
        private readonly IRadarServices _radarServices;

        public RadarControllers(IRadarServices radarServices)
        {
            _radarServices = radarServices;
        }

        [HttpGet("radars")]
        public async Task<IActionResult> GetRadars(CancellationToken cancellationToken)
        {
            IList<string> data = await _radarServices.GetRadars(cancellationToken);
            return Ok(data);
        }
    }

如何进行此依赖项注入? 谢谢。

我终于找到了解决这个问题的方法。 问题是在每个项目上,程序集名称都是相同的(例如:“服务”)。 .Net 核心不喜欢这样,似乎如果我尝试通过 AssemblyLoadContext.Default.LoadFromAssemblyPath 加载,无论要指示的路径如何,它都会继续加载相同的程序集。

在项目上,我只需右键单击 > 属性并将程序集名称更改为另一个。多亏了这一点,AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath) 有效。

例如,如果我有项目 A 和 B,我将程序集名称服务重命名为 A.Services 和 B.Services

所以 dll 现在是 A.Services.dll 而不是 Services.dll