2016-11-15 29 views
9

我創建了一個基於James Still博客文章Real-World PubSub Messaging with RabbitMQ的RabbitMQ訂閱者的ASP.NET Core MVC/WebApi網站。從靜態工廠類訪問ASP.NET核心DI容器

在他的文章中,他使用靜態類來啓動隊列訂閱者併爲排隊事件定義事件處理程序。這個靜態方法然後通過靜態工廠類實例化事件處理程序類。

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,但它不是更新的核心1.x的

+0

你能轉換了MessageListener成依賴,無論你有自己的依賴注入需要注入呢? – PmanAce

+0

我很好奇,下面的答案有幫助嗎? –

回答

10

可以避開靜態類和使用依賴注射一路結合:

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

這種方式(如你在Startup.ConfigureServices配置它們只要)你的消息處理器類可以在構造函數中的任何依賴關係,他們需要得到。例如,我注入ILogger到我的樣品處理器之一:

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。它非常類似於原來的一個,我剛剛更換了一個非靜態類與期權相關性和處理程序工廠的啓動方法的參數現在是一個依賴,而不是一個靜態類:

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方法,因此它知道你的服務和選項(你可以創建接口如果你想監聽和處理廠):

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,如this question的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>(); 
     MessageListener.Start(); 
    }); 
    appLifetime.ApplicationStopping.Register(() => 
    { 
     MessageListener.Stop(); 
    }); 

    // ... 
} 
+0

我不知道在這種情況下,如何將臨時註冊爲transient並實現IDisposable的行爲。在asp.net核心中,如果解決臨時依賴關係 - 它將在請求完成後處理。但這裏沒有要求。 – Evk

+0

內置DI在終生管理等方面相對簡單。在這種情況下,可能需要考慮掛鉤第三方容器,例如Autofact,StructureMap,Unity等,並且爲每個郵件創建一個範圍 –

+0

是的,但是如果您不這樣做並使用默認值,我希望它至少贏了不會被容器處理? – Evk

1

這是我對你的情況的意見:

如果可能的話我會派解決服務作爲參數

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

否則使用壽命將是重要的。

如果服務是單身,我將只設置配置方法的依賴:

// configure method 
public IApplicationBuilder Configure(IApplicationBuilder app) 
{ 
    var ipService = app.ApplicationServices.GetService<IIpSetService>(); 
    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<IHttpContextAccessor, HttpContextAccessor>(); 
} 

public IApplicationBuilder Configure(IApplicationBuilder app) 
{ 
    var httpContextAccessor= app.ApplicationServices.GetService<IHttpContextAccessor>(); 
    MessageHandlerFactory.HttpContextAccessor = httpContextAccessor; 
} 

// static class 
public static IHttpContextAccessor HttpContextAccessor; 

public static IMessageProcessor Create(string messageType) 
{ 
    var ipSetService = HttpContextAccessor.HttpContext.RequestServices.GetService<IIpSetService>(); 
    // use it 
} 
2

即使使用依賴注入是一個更好的解決辦法,但在某些情況下,你必須在擴展方法是使用靜態方法(像)。

對於這些情況,您可以將靜態屬性添加到靜態類並在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<IStringLocalizerFactory>();