如何在单独的服务实例中注入每个具体类?



我想运行多个HostedServices,每个IAnimal注入。我如何确保每个IAnimal运行5个不同的HostedService实例?

此刻我正在做下面的事情,但感觉很脏:

AnimalDependencyResolver.cs

public interface IDependencyResolver<out TResolve>
{
TResolve GetDependency();
}
public class AnimalDependencyResolver : IDependencyResolver<IAnimal>
{
private int _currentIndex = 0;
private readonly int _animalCount = 0;
private readonly IEnumerable<IAnimal> _animals;
public AnimalDependencyResolver(IEnumerable<IAnimal> animals)
{
_animals = animals;
_animalCount = _animals.Count();
}
public IAnimal GetDependency()
{
if (_animalCount <= 0 || _currentIndex >= _animalCount)
throw new InvalidOperationException("Sequence contains no (more) elements");
return _animals.ElementAt(_currentIndex++);
}
}

Program.cs

services.AddSingleton<IHostedService, AnimalService>();
services.AddSingleton<IHostedService, AnimalService>();
services.AddSingleton<IHostedService, AnimalService>();
services.AddSingleton<IHostedService, AnimalService>();
services.AddSingleton<IHostedService, AnimalService>();
services.AddScoped<IAnimal, MajesticSeaFlapFlap>();    
services.AddScoped<IAnimal, TrashPanda>();
services.AddScoped<IAnimal, FartSquirrel>();
services.AddScoped<IAnimal, DangerFloof>();
services.AddScoped<IAnimal, NopeRope>();
services.AddSingleton<IDependencyResolver<IAnimal>, AnimalDependencyResolver>();

AnimalService.cs

public AnimalService(ILogger<AnimalService> logger, IDependencyResolver<IAnimal> animalResolver)
{
_logger = logger;
_animal = animalResolver.GetDependency();
}

感谢您的帮助。

我会用另一种方式。创建一个托管服务AnimalProcessingHost,解析所有动物,并为每个动物生成一个任务来完成你需要的工作。在启动方法中执行一个简单的LINQ:

_tasks = animals
.Select(a => {
var service = dependencyResolver.GetRequiredService<IAnimalService>();
return service.ProcessAsync(a, cancellationTokenFromHostedService); // or even better, wrap in Task.Run
}).ToList();

这样,一旦你注册了新的动物,你就得到了处理。IAnimalService不再是IHostedService,只是一个服务,做长时间的工作,注册为暂态。

或者,您可以创建通用(AnimalService<T> where T : IAnimal)并像这样注册它们:

services.AddSingleton<IHostedService, AnimalService<Tiger>>();
services.AddSingleton<IHostedService, AnimalService<Dog>>();
services.AddSingleton<IHostedService, AnimalService<Cat>>();

但是您需要记住为每只动物添加这一行,所以可能需要编写一个扩展方法来扫描程序集中的动物并为您进行注册。

我假设您需要在容器中注册IAnimals是出于其他原因,而不仅仅是为了将它们注入到服务中…否则,让你的服务泛型,并在被消费之前通过反射构造你的动物实例就足够了。

在您的示例中,您需要在托管服务中创建一个作用域,以便您可以提取您的动物的新实例。如果你不这样做,那么把动物放在DI容器中就没有任何意义了——一旦注入,它们就会变成单例。

interface IAnimalService {}
class AnimalService<T> : IAnimalService, IHostedService where T : IAnimal
{
private readonly IServiceProvider sp;
public AnimalService(IServiceProvider sp) => this.sp = sp;
public async Task StartAsync(CancellationToken stoppingToken)
{
var needsToProcessANewInstanceOfAnimal = true;
while (needsToProcessANewInstanceOfAnimal)
{
await using var scope = this.sp.CreateAsyncScope();
// Be aware that this will lead to O(n^2) complexity and unnecessary instantiation
// I would not recommend this for thousands of specimens
var animals = scope.ServiceProvider.GetRequiredService<IEnumerable<IAnimal>>();
T? myAnimal = animals.FirstOrDefault(a => typeof(a).IsAssignableFrom(typeof(T)))
as T;
await this.ConsumeAsync(myAnimal, stoppingToken);
needsToProcessANewInstanceOfAnimal = this.DoWeNeedAnotherAnimal();
}
}
private Task ConsumeAsync(T? animal, CancellationToken ct){...}
}
services.AddSingleton<IHostedService, AnimalService<AnimalType1>>();
services.AddSingleton<IHostedService, AnimalService<AnimalType2>>();
services.AddScoped<IAnimal, AnimalType1>();
services.AddScoped<IAnimal, AnimalType2>();

你可以使用反射来简化注册(IAnimals也一样):

var serviceTypes = typeof(Program).Assembly.GetTypes().Where(t => 
t.IsInterface 
&& typeof(IAnimalService).IsAssignableFrom(t)
&& typeof(IHostedService).IsAssignableFrom(t)
&& t != typeof(IAnimalService)
);
foreach(var t in serviceTypes)
{
services.Add(typeof(IHostedService), t, ServiceLifetime.Singleton);
}

对于多个动物标本,您可以更改动物注册和检索,并执行如下操作:

interface IAnimal<T> where T: IAnimal<T> {}
class AnimalType1: IAnimal<AnimalType1> {}
...
services.AddSingleton<IHostedService, AnimalService<AnimalType1>>();
services.AddScoped<IAnimal<AnimalType1>, AnimalType1>();
...
class AnimalService<T> : IAnimalService, IHostedService where T : IAnimal<T>
{
...
public async Task StartAsync(CancellationToken stoppingToken)
{
T? animal = scope.ServiceProvider.GetService<IAnimal<T>>() as T;
}
}

希望这对你有帮助🙂

你可以这样修改你的AnimalService:

public AnimalService(ILogger<AnimalService> logger, IAnimal animal)
{
_logger = logger;
_animal = animal;
}

然后这样配置服务:

var animalTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => typeof(IAnimal).IsAssignableFrom(p))
.Where(p => p.IsClass)
.ToList();
var animalTypeEnumerator = animalTypes.GetEnumerator();
services.AddTransient<IAnimal>(sp =>
{
if (animalTypeEnumerator.MoveNext())
{
return (IAnimal)sp.GetRequiredService(animalTypeEnumerator.Current);
}
throw new InvalidOperationException($"No more IAnimal implementations"); // This line should never be reached
});
foreach (var item in animalTypes)
{
services.AddTransient(item);
services.AddSingleton<IHostedService, AnimalService>();
}

最新更新