且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

从静态工厂类访问 ASP.NET Core DI 容器

更新时间:2023-02-15 23:29:06

你可以避免静态类,并通过结合使用依赖注入:

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 转换为依赖于 IOptionsMessageHandlerFactory 的非静态类.它与您原来的非常相似,我只是用选项依赖替换了 Start 方法的参数,处理程序工厂现在是依赖而不是静态类:

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 事件(虽然我前段时间注意到使用 IISExpress 的 ApplicationStopping 事件存在一些问题,如 这个问题):

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

    // ...
}