Использование внедрения зависимостей в приложении .NET Core Service - PullRequest
0 голосов
/ 06 декабря 2018

У нас есть сервисное приложение в ядре .net, которое будет работать как демон в среде Linux.Все работает, как ожидалось, но у меня возникли проблемы с обработкой инъекций зависимостей.Ниже приведен код для справки

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

Конструктор для MainService выглядит следующим образом

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

Если вы видите в моем MainService кодея передаю три экземпляра для IMessageQueue интерфейса с помощью внедрения зависимости конструктора.То, что я действительно хочу, основано на потребности в любой части приложения, я мог бы получить новый экземпляр класса ActiveMQHandler, передав интерфейс IMessageQueue.Так как я не мог найти решение для этого, я пропускаю три случая (я не доволен этим решением) IMessageQueue.Если мне нужно использовать другой экземпляр класса ActiveMQHandler, мне нужно будет передать четвертый параметр как IMessageQueue интерфейс в моем классе MainService.

Что я действительно ищу, так это использовать ServiceProvider (или что-то более изящное) и используйте его для получения нового / единственного экземпляра (в зависимости от того, как он определен в Program.cs) экземпляра класса, который реализует интерфейс IMessageQueue.

Предложения, ребята ??

Ответы [ 4 ]

0 голосов
/ 07 декабря 2018

Наконец-то я пришел с решением, которое я считаю элегантным и не зависит от конструктора DI.Идея состоит в том, чтобы служба (да, у нас была микросервисная архитектура) создала коллекцию зависимостей в IServiceCollection, и как только служба была запущена, любой класс, когда они захотят разрешить зависимость, просто передаст ей Interface и получитэкземпляр конкретного класса.Мой окончательный код такой.Я создал отдельный класс в общей библиотеке

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

Теперь мой метод Main в файле Program.cs выглядит следующим образом

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

Теперь в любом месте проекта, когда мне нужноэкземпляр класса ActiveMQHandler, я просто пишу следующую строку кода

var messageQueue = DependencyInjection.GetService<IMessageQueue>();

Просто для информации в моем Program.cs я использую AddScoped, но я также проверил код с AddSingleton икаждый раз, когда я просил конкретный экземпляр класса, это было то же самое.

Статья по этой ссылке https://stackify.com/net-core-dependency-injection/ помогла мне

0 голосов
/ 06 декабря 2018

Если вы измените свою подпись конструктора MainService на

public MainService(IConfiguration configuration, IApplicationLifetime appLifetime, IEnumerable<IMessageQueue> messageQueues)

, вы сможете получить доступ ко всем трем реализациям интерфейса.

Проблема может заключаться в том, что вам необходимо идентифицировать их из списка, например, для выполнения различных действий в каждой реализации.Если вам нужно выполнить одно и то же действие для каждой реализации, это сработает.

В противном случае вам следует рассмотреть возможность использования универсального типа для дифференциации внедренных реализаций.

0 голосов
/ 06 декабря 2018

Просто измените конструктор на IEnumerable<IMessageQueue>.Он должен дать вам список всех зарегистрированных разработчиков IMessageQueue.

Лично мне не нравится брать зависимости от IApplicationLifetime или IServiceProvider в моих классах.Это немного похоже на анти-шаблон ServiceLocator.

0 голосов
/ 06 декабря 2018

Вы можете добавить IServiceProvider в свой класс, а затем использовать GetServices(typeof(IMessageQueue)) или функцию расширения GetServices<IMessageQueue>(), которая находится в пространстве имен Microsoft.Extensions.DependencyInejction.Так что-то вроде этого:

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

Могут быть более элегантные решения, основанные на том, для чего именно вы используете IMessageQueue.Кажется, IMessageQueue для какой-то регистрации.Например, скажем, вам нужна очередь сообщений для каждого класса, где SLProcess и SL - это разные классы.Для таких сценариев вы можете ввести общий.Таким образом, вы можете определить что-то вроде этого:

interface IMessageQueue<T> : IMessageQueue { }

class ActiveMQHandler<T> : ActiveMQHandler, IMessageQueue<T> {

    public string targetType => typeof(T).ToString();
}

С этим вы сможете добавить что-то вроде этого: AddTransient(typeof(IMessageQueue<>), typeof(ActiveMQHandler<>)).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...