可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
We have a service based application in .net core which would run as a daemon in Linux environment. Everything is working as expected but i am having problem in handling dependency injection.
Below is the code for reference
Program.cs
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Starting PreProcessor Application ");
try
{
ConfigParameters.LoadSettings(args);
}
catch (Exception ex)
{
Console.BackgroundColor = ConsoleColor.Red;
Console.WriteLine($"Error in setting config parameters {ex.Message}");
return;
}
IHost host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddHostedService<MainService>();
services.AddTransient<IMessageQueue, ActiveMQHandler>(x =>
{
return new ActiveMQHandler(ConfigParameters.Settings.MessageQueueAddress);
});
services.AddTransient<IMessageQueue, ActiveMQHandler>(x =>
{
return new ActiveMQHandler(ConfigParameters.Settings.MessageQueueAddress);
});
services.AddTransient<IMessageQueue, ActiveMQHandler>(x =>
{
return new ActiveMQHandler(ConfigParameters.Settings.MessageQueueAddress);
});
})
.Build();
await host.RunAsync();
}
}
Constructor for MainService
looks like this
IApplicationLifetime appLifetime;
IConfiguration configuration;
PreProcessorQueueListener listener;
private string reason = "SHUTDOWN SIGNAL";
private IMessageQueue messageQueue;
private IMessageQueue messageQueueSL;
private IMessageQueue messageQueueSLProcess;
public MainService(IConfiguration configuration, IApplicationLifetime appLifetime, IMessageQueue messageQueue, IMessageQueue messageQueueSL, IMessageQueue messageQueueSLProcess)
{
this.configuration = configuration;
this.messageQueue = messageQueue;
this.messageQueueSL = messageQueueSL;
this.messageQueueSLProcess = messageQueueSLProcess;
this.appLifetime = appLifetime;
}
If you see in my MainService
code i am passing three instances for IMessageQueue
interface using constructor dependency injection. What i really want is based on a need in any part of the application i could grab a new instance of ActiveMQHandler
class by passing IMessageQueue
interface. Since i could not find a solution for this i am passing three instances (i am not happy with this solution) of IMessageQueue
. If i need to use another instance of ActiveMQHandler
class then i will have to pass fourth parameter as IMessageQueue
interface in my MainService
class.
What i am really looking for is use ServiceProvider
(or something more elegant) and use that to get a new / singleton (based on how it is defined in Program.cs
) instance of the class which implements the IMessageQueue
interface.
An suggestions guys??
回答1:
If you change your MainService constructor signature to
public MainService(IConfiguration configuration, IApplicationLifetime appLifetime, IEnumerable<IMessageQueue> messageQueues)
you will be able to access all three interface implementations.
The problem might be if you need to identify them from the list, for example to perform different action on each implementation. If you need to perform the same action on each implementation than this will work.
Otherwise, you should consider using generic type to differentiate implementations injected.
回答2:
Just change the constructor to contain IEnumerable<IMessageQueue>
.
It should give you a list of all registered IMessageQueue implementers.
Personally I do not like taking on dependencies on IApplicationLifetime or IServiceProvider in my classes. This is a bit like the ServiceLocator anti-pattern.
回答3:
You can inject the IServiceProvider
into your class then use the GetServices(typeof(IMessageQueue))
or the extension function GetServices<IMessageQueue>()
that can be found within the Microsoft.Extensions.DependencyInejction
namespace. So something like this:
public MainService(IConfiguration configuration, IApplicationLifetime appLifetime, IServiceProvider serviceProvider)
{
this.configuration = configuration;
messageQueue = serviceProvider.GetServices<IMessageQueue>();
messageQueueSL = serviceProvider.GetServices<IMessageQueue>();
messageQueueSLProcess = serviceProvider.GetServices<IMessageQueue>();
this.appLifetime = appLifetime;
}
There could be more elegant solutions based on what exactly you are using IMessageQueue
for. It seems like IMessageQueue
is for some kind of logging. For instance, let's say you needed a message queue for each class where SLProcess
and SL
were different classes. For such scenarios, you can inject a generic. So you can have something like this defined:
interface IMessageQueue<T> : IMessageQueue { }
class ActiveMQHandler<T> : ActiveMQHandler, IMessageQueue<T> {
public string targetType => typeof(T).ToString();
}
With this you should be able to inject something like this: AddTransient(typeof(IMessageQueue<>), typeof(ActiveMQHandler<>))
.
回答4:
Finally i have come with a solution which i think is elegant and is not dependent on constructor DI.
Idea is let the service (yes we have a microservice architecture) create the collection of dependencies in IServiceCollection
and once the service is started any class whenever they want to resolve a Dependency they will just pass in the Interface
and will get the instance of the concrete class.
My final code is like this. I have created a separate class in a common library
public class DependencyInjection
{
private static ServiceProvider Provider;
public static void AddServices(IServiceCollection services)
{
Provider = services.BuildServiceProvider();
}
public static T GetService<T>()
{
var serviceScopeFactory = Provider.GetRequiredService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
return scope.ServiceProvider.GetService<T>();
}
}
}
Now my Main
method in Program.cs
file looks like this
static async Task Main(string[] args)
{
Console.WriteLine("Starting PreProcessor Application ");
IServiceCollection servicesCollection = new ServiceCollection();
try
{
ConfigParameters.LoadSettings(args);
servicesCollection.AddScoped<IMessageQueue, ActiveMQHandler>(x =>
{
return new ActiveMQHandler("127.0.0.1");
});
DependencyInjection.AddServices(servicesCollection);
}
catch (Exception ex)
{
Console.BackgroundColor = ConsoleColor.Red;
Console.WriteLine($"Error in setting config parameters {ex.Message}");
return;
}
IHost host = new HostBuilder()
.ConfigureHostConfiguration(configHost =>
{
configHost.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddHostedService<MainService>();
})
.Build();
await host.RunAsync();
}
Now anywhere in the project when i need the instance of the ActiveMQHandler class i just write the below line of code
var messageQueue = DependencyInjection.GetService<IMessageQueue>();
Just for the information in my Program.cs
i am using AddScoped
but i have tested the code with AddSingleton
also and everytime i requested for the concrete class instance it was the same.
The article at this link https://stackify.com/net-core-dependency-injection/ helped me