Accès au conteneur ASP.NET Core DI à partir de la classe d’usine statique

J’ai créé un site ASP.NET Core MVC / WebApi qui compte un abonné RabbitMQ basé sur l’article de blog de James Still, Real-World PubSub Messaging avec RabbitMQ .

Dans son article, il utilise une classe statique pour démarrer l’abonné de la queue et définir le gestionnaire d’événements pour les événements en queue. Cette méthode statique instancie ensuite les classes du gestionnaire d’événements via une classe fabrique statique.

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(ssortingng hostName, ssortingng userName, ssortingng 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.GetSsortingng(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); } } } 

Cela fonctionne très bien jusqu’au moment où je dois maintenant résoudre un service dans mon usine de traitement de messages plutôt que de simplement écrire sur la console.

 using NST.Web.Services; using System; namespace NST.Web.MessageProcessing { public static class MessageHandlerFactory { public static IMessageProcessor Create(ssortingng 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"); } } } } 

Y a-t-il un moyen d’accéder au conteneur ASP.NET Core IoC pour résoudre les dépendances? Je ne veux pas vraiment avoir à faire tourner la stack de dépendances à la main 🙁

Ou, existe-t-il un meilleur moyen de s’abonner à RabbitMQ à partir d’une application ASP.NET Core? J’ai trouvé RestBus mais il n’a pas été mis à jour pour Core 1.x

Vous pouvez éviter les classes statiques et utiliser l’dependency injection au complet en combinaison avec:

  • Utilisation de IApplicationLifetime pour démarrer / arrêter l’écouteur à chaque démarrage / arrêt de l’application.
  • Utilisation de IServiceProvider pour créer des instances des processeurs de message.

Tout d’abord, déplaçons la configuration dans sa propre classe, qui peut être renseignée à partir du fichier appsettings.json:

 public class RabbitOptions { public ssortingng HostName { get; set; } public ssortingng UserName { get; set; } public ssortingng Password { get; set; } public int Port { get; set; } } // In appsettings.json: { "Rabbit": { "hostName": "192.168.99.100", "username": "guest", "password": "guest", "port": 5672 } } 

Ensuite, convertissez MessageHandlerFactory en une classe non statique qui reçoit un IServiceProvider tant que dépendance. Il utilisera le fournisseur de services pour résoudre les instances du processeur de messages:

 public class MessageHandlerFactory { private readonly IServiceProvider services; public MessageHandlerFactory(IServiceProvider services) { this.services = services; } public IMessageProcessor Create(ssortingng messageType) { switch (messageType.ToLower()) { case "ipset": return services.GetService(); case "endpoint": return services.GetService(); default: throw new Exception("Unknown message type"); } } } 

Ainsi, vos classes de processeur de messages peuvent recevoir dans le constructeur toutes les dépendances dont elles ont besoin (à condition de les configurer dans Startup.ConfigureServices ). Par exemple, j’injecte un ILogger dans l’un de mes processeurs d’échantillons:

 public class IpSetMessageProcessor : IMessageProcessor { private ILogger logger; public IpSetMessageProcessor(ILogger logger) { this.logger = logger; } public void Process(ssortingng message) { logger.LogInformation("Received message: {0}", message); } } 

Maintenant, convertissez MessageListener en une classe non statique qui dépend d’ IOptions et de MessageHandlerFactory . Il est très similaire à celui d’origine, je viens de remplacer les parameters des méthodes Start par la dépendance options et la fabrique de gestionnaires est désormais une dépendance au lieu de une classe statique:

 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.GetSsortingng(body); var messageType = "endpoint"; // hardcoding the message type while we dev... //var messageType = Encoding.UTF8.GetSsortingng(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); } } 

Presque là, vous devrez mettre à jour la méthode Startup.ConfigureServices afin qu’elle connaisse vos services et vos options (vous pouvez créer des interfaces pour l’usine d’écouteurs et de gestionnaires si vous le souhaitez):

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

Enfin, mettez à jour la méthode Startup.Configure afin de prendre un paramètre supplémentaire IApplicationLifetime et de démarrer / arrêter l’écouteur de message dans les événements ApplicationStarted / ApplicationStopped (Bien que j’aie déjà remarqué quelques problèmes avec l’événement ApplicationStopping utilisant IISExpress, comme dans cette 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(); }); // ... } 

Voici mon opinion sur votre cas:

Si possible, j’enverrais un service résolu en tant que paramètre

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

Sinon , la durée de vie serait importante.

Si service est singleton, je voudrais juste définir la dépendance sur la méthode 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(ssortingng messageType) { // use IpSetService } 

Si la durée de vie du service est limitée, j’utiliserais 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(ssortingng messageType) { var ipSetService = HttpContextAccessor.HttpContext.RequestServices.GetService(); // use it } 

Même si utiliser Dependency Injection est une meilleure solution, vous devez parfois utiliser des méthodes statiques (comme dans les méthodes d’extension).

Dans ces cas, vous pouvez append une propriété statique à votre classe statique et l’initialiser dans votre méthode ConfigureServices.

Par exemple:

 public static class EnumExtentions { static public ISsortingngLocalizerFactory SsortingngLocalizerFactory { set; get; } public static ssortingng GetDisplayName(this Enum e) { var resourceManager = SsortingngLocalizerFactory.Create(e.GetType()); var key = e.ToSsortingng(); var resourceDisplayName = resourceManager.GetSsortingng(key); return resourceDisplayName; } } 

et dans vos ConfigureServices:

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