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

本文关键字:静态 工厂 ASP NET Core DI 访问 | 更新日期: 2023-09-27 18:17:56

我已经创建了一个ASP。. NET Core MVC/WebApi网站,有一个基于James Still的博客文章的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订阅RabbitMQ ?. NET Core应用程序?我找到了RestBus,但它没有为Core 1更新。x

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

你可以避免使用静态类,并使用依赖注入:

  • 使用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的applicationstop事件存在一些问题,如此问题):

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是反模式的,所以尽量不要使用它。在我的例子中,我需要它在DomainModel中调用MediatR来实现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);
    }
}
我应该像这样在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>());
}

作为结果,现在你可以调用ServiceLocator在你的DomainModel类"或和需要的地方",并解决你需要的依赖。

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方法:

 // 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的一些代码,它也使用范围。所以将工作,甚至IHttpContextAccessor!

把这个类复制到你的代码中。然后注册ServiceLocator

 ServiceActivator.Configure(app.ApplicationServices);

重要提示: ServiceLocator被认为是反模式,所以如果你有任何其他选择,不要使用它!!!!

关于瓦希德·比塔尔的回答

神奇!对于。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函数或在类上设置静态成员

当然,依赖注入是一个更好的解决方案,正如其他答案所解释的那样…