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

时间:2019-04-22 15:39:26

标签: c# dependency-injection simple-injector constructor-injection

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

我定义了一个接口,该接口可提供当前用户的访问权限,以供消息处理所用的各种类使用:

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

我想这可以使用Lifetime Scoping来完成,但是我无法在静态类中定义它并在每个线程中设置User,因为它可能破坏另一个进程。这是我对实现的最佳猜测?

 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中实现此方法的正确方法吗?还有另一种更正确的模式吗?

1 个答案:

答案 0 :(得分:3)

让我们从您的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类重写为以下内容:

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

这允许此特定于ASP.NET的IUserContext实现注册如下:

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

当然,前一个方法不能解决Windows Service中的问题,但前一个方法确实为解决方案奠定了基础。

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

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

此实现要简单得多,这里的ServiceUserContext的{​​{1}}属性是可写的属性。这可以轻松解决您的问题,因为您现在可以执行以下操作:

CurrentUser

在这里,解决方案还包括对象图的创建和运行时数据的使用的分离。在这种情况下,构造后(即使用// 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); ..... } )将运行时数据提供给对象图。