多线程应用程序中基于上下文的依赖注入

Context Based Dependency Injection in Multi-Threaded Application

我在侦听消息队列的服务器上有一个服务 运行。当接收到消息时,启动一个新线程并将消息传递给该线程进行处理。

我已经定义了一个接口,它提供当前用户访问用于消息处理的各种 classes 的消费:

public interface IUserContext {
    User CurrentUser { get; }
}

该用户可能会在不同的消息之间切换。

我的问题是如何在 SimpleInjector 中注册 IUserContext 的实现,以便 CurrentUser 正确返回传入消息中包含的正确用户 属性?

在我的 Asp.Net 应用程序中,这是通过以下方式完成的:

container.Register<IUserContext>(() => {
    User user = null;
    try {
         user = HttpContext.Current?.Session[USER_CONTEXT] as IUser;
    }
    catch { }

    return new UserContext(user);
});

我想这可以使用生命周期范围来完成,但我不能在静态 class 中定义它并在每个线程中设置用户,因为它可能会破坏另一个进程。这是我对实现的最佳猜测?

 public static Func<User> UserContext { get; set; }

然后在新线程中我的代码中:

using (container.BeginLifetimeScope()) {
    .....
    var user = GetUserContext(message);
    UserContextInitializer.UserContext = () => new UserContext(user);
    .....
}

然后注册看起来像这样:

container.Register<IUserContext>(() => UserContextInitializer.UserContext);

撇开线程安全不谈,这是在 SimpleInjector 中实现它的正确方法吗?还有其他更正确的模式吗?

让我们从您的 ASP.NET-特定 IUserContext 注册开始:

container.Register<IUserContext>(() => {
    User user = null;
    try {
         user = HttpContext.Current?.Session[USER_CONTEXT] as IUser;
    }
    catch { }

    return new UserContext(user);
});

这个注册是有问题的,因为UserContext组件依赖于运行时数据的可用性,而如here所述,对象图的创建应该与运行时数据和运行时数据分开应该流经系统。

换句话说,您应该将 UserContext class 重写为以下内容:

public class AspNetUserContext : IUserContext
{
    User CurrentUser => (User)HttpContext.Current.Session[USER_CONTEXT];
}

这允许这个 ASP.NET 特定的 IUserContext 实现按如下方式注册:

container.RegisterInstance<IUserContext>(new AspNetUserContext());

当然前面的并没有解决你Windows服务中的问题,但是前面的确实为解决这个问题打下了基础

对于 Windows 服务,您还需要自定义实现(适配器):

public class ServiceUserContext : IUserContext
{
    User CurrentUser { get; set; }
}

这个实现要简单得多,这里 ServiceUserContextCurrentUser 属性 是可写的 属性。这优雅地解决了您的问题,因为您现在可以执行以下操作:

// Windows Service Registration:
container.Register<IUserContext, ServiceUserContext>(Lifestyle.Scoped);
container.Register<ServiceUserContext>(Lifestyle.Scoped);

// Code in the new Thread:
using (container.BeginLifetimeScope())
{
    .....
    var userContext = container.GetInstance<ServiceUserContext>();

    // Set the user of the scoped ServiceUserContext
    userContext.CurrentUser = GetUserContext(message);

    var handler = container.GetInstance<IHandleMessages<SomeMessage>>();

    handler.Handle(message);

    .....
}

在这里,解决方案也是将对象图的创建与运行时数据的使用分开。在这种情况下,运行时数据在构建后提供给对象图(即使用userContext.CurrentUser = GetUserContext(message))。