Accessing ASP.NET Core DI Container From Static Factory Class

后端 未结 5 1612
闹比i
闹比i 2021-01-30 16:57

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 arti

5条回答
  •  执念已碎
    2021-01-30 17:31

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

    • 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.

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

    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();                
                case "endpoint":
                    return services.GetService();
                default:
                    throw new Exception("Unknown message type");
            }
        }
    }
    

    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 logger;
        public IpSetMessageProcessor(ILogger logger)
        {
            this.logger = logger;
        }
    
        public void Process(string message)
        {
            logger.LogInformation("Received message: {0}", message);
        }
    }
    

    Now convert MessageListener into a non-static class that depends on IOptions 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 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);
        }
    }
    

    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(Configuration.GetSection("rabbit"));
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
        services.AddTransient();
    }
    

    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.Start();
        });
        appLifetime.ApplicationStopping.Register(() =>
        {
            MessageListener.Stop();
        });
    
        // ...
    }
    

提交回复
热议问题