簡體   English   中英

從 Static 工廠訪問 ASP.NET 核心 DI 容器 Class

[英]Accessing ASP.NET Core DI Container From Static Factory Class

我創建了一個 ASP.NET Core MVC/WebApi 網站,該網站有一個 RabbitMQ 訂閱者,基於 James Still 的博客文章Real-World PubSub Messaging with RabbitMQ

在他的文章中,他使用 static class 來啟動隊列訂閱者並為排隊事件定義事件處理程序。 這個 static 方法然后通過 static 工廠 class 實例化事件處理程序類。

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 核心 IoC 容器來解決依賴關系? 我真的不想手動啟動整個依賴項堆棧:(

或者,有沒有更好的方法從 ASP.NET 核心應用程序訂閱 RabbitMQ? 我找到了 RestBus但它沒有針對 Core 1.x 進行更新

您可以避免使用靜態類並始終結合使用依賴注入:

  • 每當應用程序啟動/停止時,使用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的非靜態類。它與你原來的非常相似,我只是用選項依賴替換了 Start 方法的參數,處理程序工廠現在是一個依賴而不是靜態類:

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

    // ...
}

盡管使用依賴注入是一個更好的解決方案,但在某些情況下你必須使用靜態方法(比如在擴展方法中)。

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

我知道我的回答晚了,但我想分享我是如何做到的。

首先:使用ServiceLocator反模式,所以盡量不要使用它。 在我來說,我需要它來打電話MediatR我的DomainModel內實現DomainEvents邏輯。

但是,我必須找到一種方法來調用我的 DomainModel 中的靜態類,以從 DI 獲取某些已注冊服務的實例。

所以我決定使用HttpContext來訪問IServiceProvider但我需要從靜態方法訪問它而不在我的域模型中提及它。

讓我們這樣做:

1-我創建了一個接口來包裝 IServiceProvider

public interface IServiceProviderProxy
{
    T GetService<T>();
    IEnumerable<T> GetServices<T>();
    object GetService(Type type);
    IEnumerable<object> GetServices(Type type);
}

2- 然后我創建了一個靜態類作為我的 ServiceLocator 訪問點

public static class ServiceLocator
{
    private static IServiceProviderProxy diProxy;

    public static IServiceProviderProxy ServiceProvider => diProxy ?? throw new Exception("You should Initialize the ServiceProvider before using it.");

    public static void Initialize(IServiceProviderProxy proxy)
    {
        diProxy = proxy;
    }
}

3- 我為IServiceProviderProxy創建了一個實現,它在內部使用IHttpContextAccessor

public class HttpContextServiceProviderProxy : IServiceProviderProxy
{
    private readonly IHttpContextAccessor contextAccessor;

    public HttpContextServiceProviderProxy(IHttpContextAccessor contextAccessor)
    {
        this.contextAccessor = contextAccessor;
    }

    public T GetService<T>()
    {
        return contextAccessor.HttpContext.RequestServices.GetService<T>();
    }

    public IEnumerable<T> GetServices<T>()
    {
        return contextAccessor.HttpContext.RequestServices.GetServices<T>();
    }

    public object GetService(Type type)
    {
        return contextAccessor.HttpContext.RequestServices.GetService(type);
    }

    public IEnumerable<object> GetServices(Type type)
    {
        return contextAccessor.HttpContext.RequestServices.GetServices(type);
    }
}

4- 我應該像這樣在 DI 中注冊IServiceProviderProxy

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
    services.AddSingleton<IServiceProviderProxy, HttpContextServiceProviderProxy>();
    .......
}

5- 最后一步是在應用程序啟動時使用IServiceProviderProxy實例初始化ServiceLocator

public void Configure(IApplicationBuilder app, IHostingEnvironment env,IServiceProvider sp)
{
    ServiceLocator.Initialize(sp.GetService<IServiceProviderProxy>());
}

因此,現在您可以在您的 DomainModel 類“或和需要的地方”中調用 ServiceLocator 並解析您需要的依賴項。

public class FakeModel
{
    public FakeModel(Guid id, string value)
    {
        Id = id;
        Value = value;
    }

    public Guid Id { get; }
    public string Value { get; private set; }

    public async Task UpdateAsync(string value)
    {
        Value = value;
        var mediator = ServiceLocator.ServiceProvider.GetService<IMediator>();
        await mediator.Send(new FakeModelUpdated(this));
    }
}

對於你的情況,我的看法如下:

如果可能,我會將已解析的服務作為參數發送

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
}

是 ServiceLocator 的一個很好的實現,它也使用 Scope。 所以甚至適用於 IHttpContextAccessor!

只需將此類復制到您的代碼中即可。 然后注冊ServiceLocator

 ServiceActivator.Configure(app.ApplicationServices);

重要說明:ServiceLocator 被視為反模式,因此如果您有任何其他選擇,請不要使用它!!!!

關於Wahid Bitar的回答

驚人的。 對於 .Net Core 6,我在Program.cs上執行:

builder.Services.AddSingleton<IServiceProviderProxy, HttpContextServiceProviderProxy>();

ServiceLocator.Initialize(app.Services.GetService<IServiceProviderProxy>());

您可以在Configure獲取服務參考:

app.UseMvc();
var myServiceRef = app.ApplicationServices.GetService<MyService>();

然后將它傳遞給 init 函數或在類上設置靜態成員

當然,如其他答案中所述,依賴注入將是更好的解決方案......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM