简体   繁体   English

从 Static 工厂访问 ASP.NET 核心 DI 容器 Class

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

I've created an ASP.NET Core MVC/WebApi site that has a RabbitMQ subscriber based off James Still's blog article Real-World PubSub Messaging with RabbitMQ .我创建了一个 ASP.NET Core MVC/WebApi 网站,该网站有一个 RabbitMQ 订阅者,基于 James Still 的博客文章Real-World PubSub Messaging with RabbitMQ

In his article he uses a static class to start the queue subscriber and define the event handler for queued events.在他的文章中,他使用 static class 来启动队列订阅者并为排队事件定义事件处理程序。 This static method then instantiates the event handler classes via a static factory 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);
        }

    }
}

It works great up to the point where I now need to resolve a service in my message processor factory rather than just write to the console.它工作得很好,直到我现在需要在我的消息处理器工厂中解析服务,而不是仅仅写入控制台。

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

Is there any way to access the ASP.NET Core IoC container to resolve the dependencies?有没有办法访问 ASP.NET 核心 IoC 容器来解决依赖关系? I don't really want to have to spin up the whole stack of dependencies by hand:(我真的不想手动启动整个依赖项堆栈:(

Or, is there a better way to subscribe to RabbitMQ from an ASP.NET Core application?或者,有没有更好的方法从 ASP.NET 核心应用程序订阅 RabbitMQ? I found RestBus but it's not been updated for Core 1.x我找到了 RestBus但它没有针对 Core 1.x 进行更新

You can avoid the static classes and use Dependency Injection all the way through combined with:您可以避免使用静态类并始终结合使用依赖注入:

  • The use of IApplicationLifetime to start/stop the listener whenever the application starts/stops.每当应用程序启动/停止时,使用IApplicationLifetime来启动/停止侦听器。
  • The use of IServiceProvider to create instances of the message processors.使用IServiceProvider创建消息处理器的实例。

First thing, let's move the configuration to its own class that can be populated from the appsettings.json:首先,让我们将配置移动到可以从 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
  }
}

Next, convert MessageHandlerFactory into a non-static class that receives an IServiceProvider as a dependency.接下来,将MessageHandlerFactory转换为接收IServiceProvider作为依赖项的非静态类。 It will use the service provider to resolve the message processor instances:它将使用服务提供者来解析消息处理器实例:

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

This way your message processor classes can receive in the constructor any dependencies they need (as long as you configure them in Startup.ConfigureServices ).通过这种方式,您的消息处理器类可以在构造函数中接收它们需要的任何依赖项(只要您在Startup.ConfigureServices配置它们)。 For example, I am injecting an ILogger into one of my sample processors:例如,我将一个 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);
    }
}

Now convert MessageListener into a non-static class that depends on IOptions<RabbitOptions> and MessageHandlerFactory .It's very similar to your original one, I just replaced the parameters of the Start methods with the options dependency and the handler factory is now a dependency instead of a static class:现在将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);
    }
}

Almost there, you will need to update the Startup.ConfigureServices method so it knows about your services and options (You can create interfaces for the listener and handler factory if you want):几乎在那里,您将需要更新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>();
}

Finally, update the Startup.Configure method to take an extra IApplicationLifetime parameter and start/stop the message listener in the ApplicationStarted / ApplicationStopped events (Although I noticed a while ago some issues with the ApplicationStopping event using IISExpress, as in this question ):最后,更新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();
    });

    // ...
}

Even though using Dependency Injection is a better solution, but in some cases you have to use static methods (like in Extension Methods).尽管使用依赖注入是一个更好的解决方案,但在某些情况下你必须使用静态方法(比如在扩展方法中)。

For those cases you can add a static property to your static class and initialize it in your ConfigureServices method.对于这些情况,您可以向静态类添加静态属性并在 ConfigureServices 方法中对其进行初始化。

For example:例如:

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

and in your ConfigureServices:并在您的 ConfigureServices 中:

EnumExtentions.StringLocalizerFactory = services.BuildServiceProvider().GetService<IStringLocalizerFactory>();

I know my answer is late, but I wanted to share how I did it.我知道我的回答晚了,但我想分享我是如何做到的。

First of all : It's Antipattern to use ServiceLocator so try not to use it as you can.首先:使用ServiceLocator反模式,所以尽量不要使用它。 In my case I needed it to call MediatR inside of my DomainModel to implement the DomainEvents logic.在我来说,我需要它来打电话MediatR我的DomainModel内实现DomainEvents逻辑。

However , I had to find a way to call a static class in my DomainModel to get an instance of some registered service from DI.但是,我必须找到一种方法来调用我的 DomainModel 中的静态类,以从 DI 获取某些已注册服务的实例。

So I've decided to use the HttpContext to access the IServiceProvider but I needed to access it from a static method without mention it in my domain model.所以我决定使用HttpContext来访问IServiceProvider但我需要从静态方法访问它而不在我的域模型中提及它。

Let's do it:让我们这样做:

1- I've created an interface to wrap the IServiceProvider 1-我创建了一个接口来包装 IServiceProvider

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

2- Then I've created a static class to be my ServiceLocator access point 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- I've created an implementation for the IServiceProviderProxy which use internally the IHttpContextAccessor 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- I should register the IServiceProviderProxy in the DI like this 4- 我应该像这样在 DI 中注册IServiceProviderProxy

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

5- Final step is to initialize the ServiceLocator with an instance of IServiceProviderProxy at the Application startup 5- 最后一步是在应用程序启动时使用IServiceProviderProxy实例初始化ServiceLocator

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

As a result now you can call the ServiceLocator in your DomainModel classes "Or and needed place" and resolve the dependencies that you need.因此,现在您可以在您的 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));
    }
}

Here is my opinion about your case:对于你的情况,我的看法如下:

If possible i would send resolved service as a parameter如果可能,我会将已解析的服务作为参数发送

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

Otherwise service lifetime would be important.否则使用寿命将很重要。

If service is singleton i would just set dependency on configure method:如果服务是单例,我只会设置对配置方法的依赖:

 // 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
}

If service lifetime is scoped i would use HttpContextAccessor:如果服务生命周期是范围的,我将使用 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
}

Here is a good implementation of ServiceLocator it is using Scope also. 是 ServiceLocator 的一个很好的实现,它也使用 Scope。 So will work for even IHttpContextAccessor!所以甚至适用于 IHttpContextAccessor!

Just copy this class into your code.只需将此类复制到您的代码中即可。 And then register ServiceLocator然后注册ServiceLocator

 ServiceActivator.Configure(app.ApplicationServices);

IMPORTANT NOTE : ServiceLocator is consider as ANTI-PATTERN so do not use it if you have ANY other choice!!!!重要说明:ServiceLocator 被视为反模式,因此如果您有任何其他选择,请不要使用它!!!!

About answer of Wahid Bitar关于Wahid Bitar的回答

Amazing.惊人的。 For .Net Core 6 I do on Program.cs :对于 .Net Core 6,我在Program.cs上执行:

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

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

You can get the service reference in Configure :您可以在Configure获取服务参考:

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

and then ie pass it to an init function or set a static member on a class然后将它传递给 init 函数或在类上设置静态成员

of course dependency injection would be a better solution as explained in the other answers ...当然,如其他答案中所述,依赖注入将是更好的解决方案......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM