从静态工厂类访问ASP.NET Core DI容器 [英] Accessing ASP.NET Core DI Container From Static Factory Class

查看:94
本文介绍了从静态工厂类访问ASP.NET Core DI容器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于James Still的博客文章

I've created an ASP.NET Core MVC/WebApi site that has a RabbitMQ subscriber based off James Still's blog article Real-World PubSub Messaging with RabbitMQ.

在他的文章中,他使用静态类来启动队列订户并为排队的事件定义事件处理程序.然后,此静态方法通过静态工厂类实例化事件处理程序类.

In his article he uses a static class to start the queue subscriber and define the event handler for queued events. This static method then instantiates the event handler classes via a static factory class.

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;

namespace NST.Web.MessageProcessing
{
    public static class MessageListener
    {
        private static IConnection _connection;
        private static IModel _channel;

        public static void Start(string hostName, string userName, string password, int port)
        {
            var factory = new ConnectionFactory
            {
                HostName = hostName,
                Port = port,
                UserName = userName,
                Password = password,
                VirtualHost = "/",
                AutomaticRecoveryEnabled = true,
                NetworkRecoveryInterval = TimeSpan.FromSeconds(15)
            };

            _connection = factory.CreateConnection();
            _channel = _connection.CreateModel();
            _channel.ExchangeDeclare(exchange: "myExchange", type: "direct", durable: true);

            var queueName = "myQueue";

            QueueDeclareOk ok = _channel.QueueDeclare(queueName, true, false, false, null);

            _channel.QueueBind(queue: queueName, exchange: "myExchange", routingKey: "myRoutingKey");

            var consumer = new EventingBasicConsumer(_channel);
            consumer.Received += ConsumerOnReceived;

            _channel.BasicConsume(queue: queueName, noAck: false, consumer: consumer);

        }

        public static void Stop()
        {
            _channel.Close(200, "Goodbye");
            _connection.Close();
        }

        private static void ConsumerOnReceived(object sender, BasicDeliverEventArgs ea)
        {
            // get the details from the event
            var body = ea.Body;
            var message = Encoding.UTF8.GetString(body);
            var messageType = "endpoint";  // hardcoding the message type while we dev...

            // instantiate the appropriate handler based on the message type
            IMessageProcessor processor = MessageHandlerFactory.Create(messageType);
            processor.Process(message);

            // Ack the event on the queue
            IBasicConsumer consumer = (IBasicConsumer)sender;
            consumer.Model.BasicAck(ea.DeliveryTag, false);
        }

    }
}

它可以很好地工作到我现在需要在我的消息处理器工厂中解析服务而不仅仅是写到控制台上的时候.

It works great up to the point where I now need to resolve a service in my message processor factory rather than just write to the console.

using NST.Web.Services;
using System;

namespace NST.Web.MessageProcessing
{
    public static class MessageHandlerFactory
    {
        public static IMessageProcessor Create(string messageType)
        {
            switch (messageType.ToLower())
            {
                case "ipset":
                    // need to resolve IIpSetService here...
                    IIpSetService ipService = ???????

                    return new IpSetMessageProcessor(ipService);

                case "endpoint":
                    // need to resolve IEndpointService here...
                    IEndpointService epService = ???????

                    // create new message processor
                    return new EndpointMessageProcessor(epService);

                default:
                    throw new Exception("Unknown message type");
            }
        }
    }
}

有什么方法可以访问ASP.NET Core IoC容器来解决依赖关系?我真的不想手动旋转整个依赖项堆栈:(

Is there any way to access the ASP.NET Core IoC container to resolve the dependencies? I don't really want to have to spin up the whole stack of dependencies by hand :(

或者,有没有更好的方法可以从ASP.NET Core应用程序订阅RabbitMQ?我发现 RestBus ,但尚未针对Core 1.x进行更新

Or, is there a better way to subscribe to RabbitMQ from an ASP.NET Core application? I found RestBus but it's not been updated for Core 1.x

推荐答案

您可以避免使用静态类,并通过与以下方式结合使用Dependency Injection:

You can avoid the static classes and use Dependency Injection all the way through combined with:

  • 使用 IApplicationLifetime 来启动/停止应用程序启动/停止时的监听器.
  • 使用IServiceProvider创建消息处理器的实例.
  • The use of IApplicationLifetime to start/stop the listener whenever the application starts/stops.
  • The use of IServiceProvider to create instances of the message processors.

首先,让我们将配置移至可以从appsettings.json中填充的类:

First thing, let's move the configuration to its own class that can be populated from the appsettings.json:

public class RabbitOptions
{
    public string HostName { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public int Port { get; set; }
}

// In appsettings.json:
{
  "Rabbit": {
    "hostName": "192.168.99.100",
    "username": "guest",
    "password": "guest",
    "port": 5672
  }
}

接下来,将MessageHandlerFactory转换为接收IServiceProvider作为依赖项的非静态类.它将使用服务提供商来解析消息处理器实例:

Next, convert MessageHandlerFactory into a non-static class that receives an IServiceProvider as a dependency. It will use the service provider to resolve the message processor instances:

public class MessageHandlerFactory
{
    private readonly IServiceProvider services;
    public MessageHandlerFactory(IServiceProvider services)
    {
        this.services = services;
    }

    public IMessageProcessor Create(string messageType)
    {
        switch (messageType.ToLower())
        {
            case "ipset":
                return services.GetService<IpSetMessageProcessor>();                
            case "endpoint":
                return services.GetService<EndpointMessageProcessor>();
            default:
                throw new Exception("Unknown message type");
        }
    }
}

这样,您的消息处理器类就可以在构造函数中接收它们所需的任何依赖项(只要您在Startup.ConfigureServices中对其进行配置).例如,我正在将ILogger注入我的示例处理器之一:

This way your message processor classes can receive in the constructor any dependencies they need (as long as you configure them in Startup.ConfigureServices). For example, I am injecting an ILogger into one of my sample processors:

public class IpSetMessageProcessor : IMessageProcessor
{
    private ILogger<IpSetMessageProcessor> logger;
    public IpSetMessageProcessor(ILogger<IpSetMessageProcessor> logger)
    {
        this.logger = logger;
    }

    public void Process(string message)
    {
        logger.LogInformation("Received message: {0}", message);
    }
}

现在将MessageListener转换为依赖于IOptions<RabbitOptions>MessageHandlerFactory的非静态类.它与您的原始类非常相似,我只是将Start方法的参数替换为options依赖项和处理程序工厂现在是一个依赖项,而不是一个静态类:

Now convert MessageListener into a non-static class that depends on IOptions<RabbitOptions> and MessageHandlerFactory.It's very similar to your original one, I just replaced the parameters of the Start methods with the options dependency and the handler factory is now a dependency instead of a static class:

public class MessageListener
{
    private readonly RabbitOptions opts;
    private readonly MessageHandlerFactory handlerFactory;
    private IConnection _connection;
    private IModel _channel;

    public MessageListener(IOptions<RabbitOptions> opts, MessageHandlerFactory handlerFactory)
    {
        this.opts = opts.Value;
        this.handlerFactory = handlerFactory;
    }

    public void Start()
    {
        var factory = new ConnectionFactory
        {
            HostName = opts.HostName,
            Port = opts.Port,
            UserName = opts.UserName,
            Password = opts.Password,
            VirtualHost = "/",
            AutomaticRecoveryEnabled = true,
            NetworkRecoveryInterval = TimeSpan.FromSeconds(15)
        };

        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel();
        _channel.ExchangeDeclare(exchange: "myExchange", type: "direct", durable: true);

        var queueName = "myQueue";

        QueueDeclareOk ok = _channel.QueueDeclare(queueName, true, false, false, null);

        _channel.QueueBind(queue: queueName, exchange: "myExchange", routingKey: "myRoutingKey");

        var consumer = new EventingBasicConsumer(_channel);
        consumer.Received += ConsumerOnReceived;

        _channel.BasicConsume(queue: queueName, noAck: false, consumer: consumer);

    }

    public void Stop()
    {
        _channel.Close(200, "Goodbye");
        _connection.Close();
    }

    private void ConsumerOnReceived(object sender, BasicDeliverEventArgs ea)
    {
        // get the details from the event
        var body = ea.Body;
        var message = Encoding.UTF8.GetString(body);
        var messageType = "endpoint";  // hardcoding the message type while we dev...
        //var messageType = Encoding.UTF8.GetString(ea.BasicProperties.Headers["message-type"] as byte[]);

        // instantiate the appropriate handler based on the message type
        IMessageProcessor processor = handlerFactory.Create(messageType);
        processor.Process(message);

        // Ack the event on the queue
        IBasicConsumer consumer = (IBasicConsumer)sender;
        consumer.Model.BasicAck(ea.DeliveryTag, false);
    }
}

几乎在那儿,您将需要更新Startup.ConfigureServices方法,以便它了解您的服务和选项(您可以根据需要为侦听器和处理程序工厂创建接口):

Almost there, you will need to update the Startup.ConfigureServices method so it knows about your services and options (You can create interfaces for the listener and handler factory if you want):

public void ConfigureServices(IServiceCollection services)
{            
    // ...

    // Add RabbitMQ services
    services.Configure<RabbitOptions>(Configuration.GetSection("rabbit"));
    services.AddTransient<MessageListener>();
    services.AddTransient<MessageHandlerFactory>();
    services.AddTransient<IpSetMessageProcessor>();
    services.AddTransient<EndpointMessageProcessor>();
}

最后,更新Startup.Configure方法以使用额外的IApplicationLifetime参数,并在ApplicationStarted/ApplicationStopped事件中启动/停止消息侦听器(尽管前一阵子我注意到ApplicationStopping事件使用IISExpress,如此问题):

Finally, update the Startup.Configure method to take an extra IApplicationLifetime parameter and start/stop the message listener in the ApplicationStarted/ApplicationStopped events (Although I noticed a while ago some issues with the ApplicationStopping event using IISExpress, as in this question):

public MessageListener MessageListener { get; private set; }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
    appLifetime.ApplicationStarted.Register(() =>
    {
        MessageListener = app.ApplicationServices.GetService<MessageListener>();
        MessageListener.Start();
    });
    appLifetime.ApplicationStopping.Register(() =>
    {
        MessageListener.Stop();
    });

    // ...
}

这篇关于从静态工厂类访问ASP.NET Core DI容器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆