Подтвердить что ты не робот

Нечетное поведение UserManager в .Net Identity

Чтобы этот вопрос был прост, я опишу проблему с более высоким уровнем, а затем, если необходимо, перейду в любую информацию о реализации.

Я использую идентификатор ASP.NET в своем приложении в процессе разработки. В конкретном сценарии по ряду запросов UserManager сначала получает текущего пользователя (по крайней мере, один запрос FindById), где пользователь извлекается. При последующем запросе я обновляю информацию об этом пользователе, которая сохраняется UserManager.Update, и я вижу, что изменения сохраняются в базе данных.

Проблема заключается в том, что при последующих последующих запросах объект пользователя, полученный из FindById, не обновляется. Это странно, но может быть что-то вроде кэширования в UserManager, который я не понимаю. Однако, когда я отслеживаю вызовы базы данных, я вижу, что UserManager действительно отправляет sql-запросы в базу данных для получения пользователя.

И это становится странным - даже если база данных будет обновлена, UserManager все равно каким-то образом возвращает старый объект из этого процесса. Когда я сам запускаю точно такой же запрос, который трассируется непосредственно в базе данных, я получаю обновленные данные, как ожидалось.

Что это за черная магия?

Очевидно, что-то где-то кэшировано, но почему он делает запрос к базе данных, просто чтобы игнорировать обновленные данные, которые он получает?

Пример

В приведенном ниже примере обновляется все, как ожидалось, в db для каждого запроса для действия контроллера, а когда GetUserDummyTestClass вызывает findById на другом экземпляре UserManager, я могу отслеживать запросы sql и проверять их непосредственно на db и проверять что они возвращают обновленные данные. Тем не менее, пользовательский объект, возвращенный из той же самой строки кода, все еще имеет старые значения (в этом случае первое редактирование после запуска приложения, независимо от того, сколько раз срабатывает тестовое действие).

контроллер

public ActionResult Test()
{
    var userId = User.Identity.GetUserId();
    var user = UserManager.FindById(userId);

    user.RealName = "name - " + DateTime.Now.ToString("mm:ss:fff");
    UserManager.Update(user);
    return View((object)userId);
}

Test.cshtml

@model string

@{
    var user = GetUserDummyTestClass.GetUser(Model);
}

@user.RealName;

GetUserDummyTestClass

public class GetUserDummyTestClass
{
    private static UserManager<ApplicationUser> _userManager;

    private static UserManager<ApplicationUser> UserManager
    {
        get { return _userManager ?? (_userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()))); }
    }

    public static ApplicationUser GetUser(string id)
    {
        var user = UserManager.FindById(id);
        return user;
    }
}

Обновление

Как отметил Эрик, я не должен использовать статические UserManagers. Однако, если я сохраняю UserManager в GetUserDummyTest связанным с HttpContext (сохраняя его на HttpRequest) в случае, если я хочу использовать его несколько раз во время запроса, он все еще кэширует первый объект User, который он получает по идентификатору, и не обращая внимания на какие-либо обновления из другого UserManager. Таким образом, предполагая, что реальная проблема заключается в том, что я использую два разных UserManager в качестве предлагаемого метода trailmax и что он не предназначен для такого использования.

В моем примере выше, если я сохраняю UserManager в GetUserDummyTestClass постоянным над HttpRequest, добавьте метод Update и используйте его только в контроллере, все работает нормально, как ожидалось.

Итак, если вы сделаете вывод, правильно ли было бы утверждать, что если я хочу использовать логику из UserManager за пределами области действия контроллера, мне нужно глобализовать экземпляр UserManager в соответствующем классе, где я могу связать экземпляр в HttpContext, если я хочу избежать создания и удаления экземпляров для одноразового использования?

Обновление 2

Изучив немного дальше, я понял, что я действительно намерен использовать один экземпляр для каждого запроса, и что это уже фактически настроено для OwinContext в Startup.Auth, а затем получилось следующим образом:

using Microsoft.AspNet.Identity.Owin;

// Controller
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>()

// Other scopes
HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>()

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

4b9b3361

Ответ 1

Ваша проблема заключается в том, что вы используете два разных экземпляра UserManager, и похоже, что они оба статически определены (что является огромным нет-no в веб-приложениях, поскольку они распределяются между всеми потоками и пользователями системы и не являются потокобезопасными, вы даже не можете сделать их потокобезопасными, блокируя их, поскольку они содержат состояние, специфичное для пользователя)

Измените свой GetUserDummyTestClass на это:

private static UserManager<ApplicationUser> UserManager
{
    get { return new UserManager<ApplicationUser>(
          new UserStore<ApplicationUser>(new ApplicationDbContext())); }
}

public static ApplicationUser GetUser(string id)
{
    using (var userManager = UserManager)
    {
        return UserManager.FindById(id);
    }
}