.Net Core:“Scoped”依赖注入的自定义范围。控制器

Posted

技术标签:

【中文标题】.Net Core:“Scoped”依赖注入的自定义范围。控制器【英文标题】:.Net Core: Custom scope for "Scoped" Dependency injection w.out. a controller 【发布时间】:2018-09-14 23:14:43 【问题描述】:

我有一个应用程序不通过控制器接收普通的 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);
    

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

非常感谢。

【问题讨论】:

你读过the documentation吗? @Steven 我已经看到使用 using 子句创建范围的示例,但在我看来,创建同一对象的另一个实例需要我将此“范围对象”传递给该方法,以便创建具有相同范围的所需对象。这并不比首先传递我的对象更好,我宁愿避免。我将用一个例子扩展我原来的帖子。 这里的诀窍是serviceClassA注入Listener,而是从你在@内部创建的scope解决它987654326@,并将CustomLogger 注册为Singleton @Steven 我如何确保我在 serviceClassA 中解析的 CustomLogger 与在调用 serviceClassA 方法的 Listener 方法中解析的对象相同(不传递范围对象)? 注册CustomLogger为单身人士。 【参考方案1】:

您可以在对来自队列的消息做出反应的类中注入一个 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 服务参数传递给 MessageHandler 的构造函数(在本例中为 SessionState 对象)以及 DI 服务。工厂请求(范围)依赖项并将它们传递给 MessageHandler。如果您不使用会话,那么您可能不需要工厂,您可以直接从作用域中获取 MessageHandler。

【讨论】:

以上是关于.Net Core:“Scoped”依赖注入的自定义范围。控制器的主要内容,如果未能解决你的问题,请参考以下文章

.NET Core 依赖注入生命周期

Blazor University (48)依赖注入 —— Scoped 依赖

Net Core依赖注入

.NET 通过源码深究依赖注入原理

ASP.NET Core 依赖注入基本用法

ASP.NET Core中的依赖注入:依赖注入(DI)