.Net Core:在没有控制器的情况下自定义“Scoped”依赖注入作用域

7
我有一个应用程序,它不通过控制器接收普通的HTTP请求,而是侦听和接收消息(AMQP协议)以启动其逻辑流程。
我的应用程序可能会同时接收和处理多个消息。我有一个对象将在整个过程中在几个不同的服务/类中收集信息/数据,以便我在最后使用它。但我需要按接收的每个消息分离数据,就像“Scoped”注入将注入实例与其他HTTP请求分离一样。
因此,我的用例非常类似于在普通API中使用Scoped注入对象的方式,但我接收的是侦听器中的消息,而不是新的HTTP请求。
是否有任何方法可以为每个接收的消息创建自定义范围,无论是通过某种配置还是使代码在Listener.MessageReceived(Message message)方法中作为第一件事创建新范围?
想象一个如下的流程:
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);
    }
}

我的自定义记录器不仅可以用于1或2个服务层,可能有许多层,我可能只想在最底层使用CustomLogger,但之后我希望它可以在顶层访问,以检索其中存储的数据。

非常感谢。


你有读过文档吗? - Steven
@Steven,我看过使用using子句创建范围的示例,但是在我的理解中,如果要创建同一对象的另一个实例,则需要将此“范围对象”传递给该方法,以便使用相同的范围创建所需的对象。这并不比一开始就直接传递我的对象更好,而我宁愿避免这种情况。我将在原帖中附上一个示例。 - Nixxon
这里的诀窍是不要将 serviceClassA 注入到 Listener 中,而是从 Listener 内部创建的 scope 中解析它,并将 CustomLogger 注册为 Singleton - Steven
@Steven 我该如何确保在serviceClassA中解析的CustomLogger对象与调用serviceClassA方法的Listener方法中解析的对象相同(而不传递作用域对象)? - Nixxon
CustomLogger注册为单例。 - Steven
1个回答

6

您可以在负责处理队列消息的类中注入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 服务参数(在这种情况下为 SessionState 对象)传递给 MessageHandler 的构造函数,以及 DI 服务。工厂会请求 (scoped) 依赖项并将其传递给 MessageHandler。如果您不使用会话,则可能不需要工厂,而可以直接从作用域中获取 MessageHandler。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接