网络核心 Web api 项目的 class 库中的依赖注入
Dependency Injection inside class library for a net core web api project
我有一个 .NET Core WEB API 项目,我想作为 .NET CORE Class 库成为多个“项目”的容器。
我有的是:
带有 .NET Core Web API 项目的解决方案“Orbit”。
带有 .NET Core Class 库的解决方案“SpaceRadar”。
首先,在我的“轨道”项目中,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
我有一个 .NET Core WEB API 项目,我想作为 .NET CORE Class 库成为多个“项目”的容器。
我有的是:
带有 .NET Core Web API 项目的解决方案“Orbit”。
带有 .NET Core Class 库的解决方案“SpaceRadar”。
首先,在我的“轨道”项目中,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