【问题标题】:Dependency Injection inside class library for a net core web api projectnet core web api项目的类库中的依赖注入
【发布时间】:2020-09-11 08:16:39
【问题描述】:

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

我拥有的是:

  • 带有 .NET Core Web API 项目的“Orbit”解决方案。

  • 使用 .NET Core 类库的解决方案“SpaceRadar”。

首先,在我的“Orbit”项目中,Startup 类,我目前所做的:

  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 项目中定义的控制器。 但是我想在我的类库项目中使用依赖注入,如果可能的话,通过扫描 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;
        }
    }
}

甚至在程序集中调用“设置”方法


        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 });
                }
            }
        }

在我的类库中

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));
                }
            });
        }
    }
}

所有这些解决方案都不允许我在我的类库中拥有这种控制器:

 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);
        }
    }

如何进行这种依赖注入? 谢谢。

【问题讨论】:

  • tldr;我通常做的是在 IServiceCollection 上创建一个 ExtensionMethod 并只注册服务,就像我在 Startup 中所做的那样。因此,如果我有 lib“SomeServiceLib”,我将在该 lib 中有 public static void AddSomeService(this IServiceCollection services){ ...
  • 这不像我的帖子中描述的那样工作。

标签: c# .net-core dll inversion-of-control webapi


【解决方案1】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2020-08-11
    • 2023-02-24
    • 2017-07-23
    • 2020-12-20
    • 1970-01-01
    • 2021-07-15
    • 1970-01-01
    • 2020-11-20
    • 2018-03-24
    相关资源
    最近更新 更多