Контекстное внедрение зависимостей в многопоточном приложении - PullRequest
2 голосов
/ 22 апреля 2019

У меня есть служба, работающая на сервере, который прослушивает очередь сообщений.Когда сообщение получено, запускается новый поток, и сообщение передается этому потоку для обработки.

Я определил интерфейс, который предоставляет доступ текущему пользователю для использования в различных классах, используемых для обработки сообщения:

public interface IUserContext {
    User CurrentUser { get; }
}

Этот пользователь, скорее всего, изменится от сообщения к сообщению.

Мой вопрос заключается в том, как зарегистрировать реализацию IUserContext в SimpleInjector, чтобы правильный пользователь, содержащийся во входящем сообщении,правильно возвращается свойством 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, но я могуне определить это в статическом классе и установить пользователя в каждом потоке, потому что это может повредить другой процесс.Это мое лучшее предположение о реализации?

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

Тогда в моем коде в новой теме:

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

Тогда регистрация будет выглядеть примерно так:

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

Thread Safety aside, Это правильный подход для реализации этого в SimpleInjector?Есть ли другой шаблон, который был бы более правильным?

1 Ответ

3 голосов
/ 22 апреля 2019

Давайте начнем с вашей ASP.NET-специфической IUserContext регистрации:

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

    return new UserContext(user);
});

Эта регистрация проблематична, поскольку компонент UserContext зависит от доступности данных времени выполнения, в то время как, как описано здесь , создание графов объектов должно быть отделено от данных времени выполнения, а данные времени выполнения должны выполняться через систему.

Другими словами, вы должны переписать свой класс UserContext следующим образом:

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

Эта реализация намного проще, и здесь свойство ServiceUserContext *1025* является записываемым свойством. Это решит вашу проблему элегантно, потому что теперь вы можете делать следующее:

// 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)).

...