.Net Core:" Scoped"的自定义范围依赖注入w.out。一个控制器

时间:2018-04-05 08:03:24

标签: dependency-injection scope .net-core message-queue amqp

我有一个应用程序无法通过控制器接收普通的HTTP请求,而是监听和接收消息(AMQP协议)以启动它的逻辑流程。

我的应用程序一次可能会收到并处理多条消息。我有一个对象将在整个过程中收集信息/数据,在几个不同的服务/类中,以便我最后使用它。 但我需要将每个收到的消息分开,作为一个" Scoped"注入会将注入的实例与其他HTTP请求分开。

因为我在普通API中使用Scoped注入对象的方式非常类似,但是我在听众中收到一条消息,而不是新的HTTP请求。

有没有办法可以为每个收到的消息创建一个自定义范围,通过某种配置,或者让代码创建一个新的范围作为我的Listener.MessageReceived(消息消息)方法中的第一件事?

想象一下这样的流程:

public class Listener {
    ServiceClassA serviceClassA //injected in constructor
    CustomLogger customLogger // (HAS TO BE SAME OBJECT INJECTED INTO ServiceClassA, ServiceClassB and Listener)

    public void ReceiveMessage(Message message) {
        using (var scope = CreateNewScope()) {
            try {
                serviceClassA.DoStuff();
            } catch(Exception e) {
                Console.Write(customLogger.GetLogs())
            }
        }
    }
}


public class ServiceClassA {
    ServiceClassB serviceClassB //injected in constructor
    CustomLogger customLogger //(HAS TO BE SAME OBJECT INJECTED INTO ServiceClassA, ServiceClassB and Listener)

    public void DoStuff() {
        customLogger = ResolveCustomLogger(); // how do I make sure I can get/resolve the same object as in Listener (without having to pass parameters)
        var data = // does stuff
        customLogger.Log(data);

        serviceClassB.DoStuff();
    }
}


public class ServiceClassB {
    CustomLogger customLogger //(HAS TO BE SAME OBJECT INJECTED INTO ServiceClassA, ServiceClassB and Listener)

    public void DoStuff() {
        customLogger = ResolveCustomLogger(); // how do I make sure I can get/resolve the same object as in Listener (without having to pass parameters)
        var data = // does other stuff
        customLogger.Log(data);
    }
}

我的CustomLogger不仅可以使用1或2个服务层,可能有很多层,我可能只想在底部使用CustomLogger,但我希望它可以在顶层访问,以便检索存储在其中的数据。

非常感谢。

1 个答案:

答案 0 :(得分:0)

您可以在对队列中的消息做出反应的类中注入ServiceScopyFactory,然后针对收到的每个消息创建一个范围,并从该范围请求MessageHandler依赖项。

下面的代码示例正是这样做的(它也处理队列中的会话,但这对创建作用域没有影响)。

public class SessionHandler : ISessionHandler
{
    public readonly string SessionId;
    private readonly ILogger<SessionHandler> Logger;
    private readonly IServiceScopeFactory ServiceScopeFactory;

    readonly SessionState SessionState;

    public SessionHandler(
        ILogger<SessionHandler> logger,
        IServiceScopeFactory serviceScopeFactory,
        string sessionId)
    {
        Logger = logger;
        ServiceScopeFactory = serviceScopeFactory;
        SessionId = sessionId
        SessionState = new SessionState();
    }

    public async Task HandleMessage(IMessageSession session, Message message, CancellationToken cancellationToken)
    {
        Logger.LogInformation($"Message of {message.Body.Length} bytes received.");


        // Deserialize message
        bool deserializationSuccess = TryDeserializeMessageBody(message.Body, out var incomingMessage);

        if (!deserializationSuccess)
            throw new NotImplementedException(); // Move to deadletter queue?


        // Dispatch message
        bool handlingSuccess = await HandleMessageWithScopedHandler(incomingMessage, cancellationToken);

        if (!handlingSuccess)
            throw new NotImplementedException(); // Move to deadletter queue?
    }

    /// <summary>
    /// Instantiate a message handler with a service scope that lasts until the message handling is done.
    /// </summary>
    private async Task<bool> HandleMessageWithScopedHandler(IncomingMessage incomingMessage, CancellationToken cancellationToken)
    {
        try
        {
            using IServiceScope messageHandlerScope = ServiceScopeFactory.CreateScope();
            var messageHandlerFactory = messageHandlerScope.ServiceProvider.GetRequiredService<IMessageHandlerFactory>();
            var messageHandler = messageHandlerFactory.Create(SessionState);

            await messageHandler.HandleMessage(incomingMessage, cancellationToken);

            return true;
        }
        catch (Exception exception)
        {
            Logger.LogError(exception, $"An exception occurred when handling a message: {exception.Message}.");
            return false;
        }
    }

    private bool TryDeserializeMessageBody(byte[] body, out IncomingMessage? incomingMessage)
    {
        incomingMessage = null;

        try
        {
            incomingMessage = IncomingMessage.Deserialize(body);
            return true;
        }
        catch (MessageDeserializationException exception)
        {
            Logger.LogError(exception, exception.Message);    
        }

        return false;
    }
}

现在,每当实例化MessageHandlerFactory时(​​对于从队列接收到的每条消息都会发生),工厂请求的所有范围内的依赖项将一直存在,直到MessageHandler.HandleMessage()任务完成为止。

我创建了一个消息处理程序工厂,以便SessionHandler除了可以将DI服务之外,还可以将非DI服务参数传递给MessageHandler的构造函数(在本例中为SessionState对象)。是工厂请求(范围内的)依赖项并将它们传递给MessageHandler。如果不使用会话,则可能不需要工厂,而可以直接从作用域中获取MessageHandler。