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

我创建了一个ASP.NET核心MVC / WebApi站点,该站点有一个RabbitMQ订阅者,基于James Still的博客文章Real-World PubSub Messaging with RabbitMQ 。

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

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

它工作得很好,我现在需要在消息处理器工厂中解析服务,而不是只写入控制台。

 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容器来解决依赖关系? 我真的不想手动旋转整个依赖的堆栈:(

或者,有没有更好的方法从ASP.NET Core应用程序订阅RabbitMQ? 我找到了RestBus,但它没有针对Core 1.x进行更新

您可以避免使用静态类并一直使用dependency injection:

  • 每当应用程序启动/停止时,使用IApplicationLifetime来启动/停止监听器。
  • 使用IServiceProvider创建消息处理器的实例。

首先,让我们将配置移动到可以从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作为依赖项。 它将使用服务提供程序来解析消息处理器实例:

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

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

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

现在将MessageListener转换为依赖于IOptionsMessageHandlerFactory的非静态类。它与原始的非常相似,我只是用选项依赖替换了Start方法的参数,而处理程序工厂现在是依赖而不是静态类:

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

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

 public void ConfigureServices(IServiceCollection services) { // ... // Add RabbitMQ services services.Configure(Configuration.GetSection("rabbit")); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); } 

最后,更新Startup.Configure方法以获取额外的IApplicationLifetime参数并在ApplicationStarted / ApplicationStopped事件中启动/停止消息侦听器(虽然我刚才注意到使用IISExpress的ApplicationStopping事件的一些问题,如本问题所示 ):

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

以下是我对您案件的看法:

如果可能,我会将已解决的服务作为参数发送

 public static IMessageProcessor Create(string messageType, IIpSetService ipService) { // } 

否则服务寿命很重要。

如果service是singleton,我只会设置对configure方法的依赖:

  // configure method public IApplicationBuilder Configure(IApplicationBuilder app) { var ipService = app.ApplicationServices.GetService(); MessageHandlerFactory.IIpSetService = ipService; } // static class public static IIpSetService IpSetService; public static IMessageProcessor Create(string messageType) { // use IpSetService } 

如果服务生命周期是作用域的,我将使用HttpContextAccessor:

 //Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddSingleton(); } public IApplicationBuilder Configure(IApplicationBuilder app) { var httpContextAccessor= app.ApplicationServices.GetService(); MessageHandlerFactory.HttpContextAccessor = httpContextAccessor; } // static class public static IHttpContextAccessor HttpContextAccessor; public static IMessageProcessor Create(string messageType) { var ipSetService = HttpContextAccessor.HttpContext.RequestServices.GetService(); // use it } 

即使使用dependency injection是一种更好的解决方案,但在某些情况下,您必须使用静态方法(如扩展方法)。

对于这些情况,您可以向静态类添加静态属性,并在ConfigureServices方法中对其进行初始化。

例如:

 public static class EnumExtentions { static public IStringLocalizerFactory StringLocalizerFactory { set; get; } public static string GetDisplayName(this Enum e) { var resourceManager = StringLocalizerFactory.Create(e.GetType()); var key = e.ToString(); var resourceDisplayName = resourceManager.GetString(key); return resourceDisplayName; } } 

在您的ConfigureServices中:

 EnumExtentions.StringLocalizerFactory = services.BuildServiceProvider().GetService();