2014-09-06 13 views
12

Aby uprościć to pytanie, opiszę problem na wyższym poziomie, a następnie w razie potrzeby przejdę do szczegółów implementacji.Dziwne zachowanie użytkownika UserManager w .Net Identity

Używam tożsamości ASP.NET w mojej aplikacji w fazie rozwoju. W określonym scenariuszu dla szeregu zgłoszeń menedżer UserManager otrzymuje najpierw bieżącego użytkownika (co najmniej jedno żądanie FindById), w którym użytkownik jest pobierany. Na kolejne żądanie aktualizuję informacje o tym użytkowniku, które są zapisywane przez UserManager.Update i widzę, że zmiana trwała w bazie danych.

Problem polega na tym, że przy kolejnych kolejnych żądaniach obiekt użytkownika pobrany z FindById nie jest aktualizowany. To dziwne, ale może być coś z buforowaniem w UserManager, którego nie rozumiem. Jednak podczas śledzenia wywołań bazy danych widzę, że UserManager rzeczywiście wysyła zapytania sql do bazy danych w celu pobrania użytkownika.

I tu robi się naprawdę dziwnie - mimo że baza danych jest potwierdzona, że ​​jest aktualna, UserManager nadal w jakiś sposób zwraca stary obiekt z tego procesu. Kiedy sam uruchomię dokładnie to samo zapytanie, które jest bezpośrednio śledzone w bazie danych, otrzymuję zaktualizowane dane zgodnie z oczekiwaniami.

Czym jest ta czarna magia?

Oczywiście coś jest gdzieś zbuforowane, ale dlaczego robi zapytanie do bazy danych, tylko po to, aby zignorować zaktualizowane dane, które dostaje?

Przykład

To poniżej przykład aktualizuje wszystko zgodnie z oczekiwaniami w dB dla każdego żądania do działania kontrolera, a kiedy GetUserDummyTestClass dzwoni findById drugiej instancji UserManager mogę śledzić SQL żądań, a może przetestuj je bezpośrednio na db i sprawdź, czy zwracają zaktualizowane dane. Jednak obiekt użytkownika zwrócony z tego samego wiersza kodu nadal ma stare wartości (w tym scenariuszu pierwsza edycja po uruchomieniu aplikacji, bez względu na to, ile razy wywoływana jest akcja testowa).

Kontroler

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

Aktualizacja

Jak zauważył Erik, nie powinienem używać statycznych UserManagers. Jeśli jednak zachowam menedżera UserManager w GetUserDummyTest powiązanego z HttpContext (utrzymując go przez HttpRequest) w przypadku, gdy chcę go użyć kilka razy podczas żądania, to nadal buforuje pierwszy obiekt użytkownika, który otrzymuje przez Id, i pomijając wszelkie aktualizacje z innego menedżera UserManager. Sugeruje to, że prawdziwym problemem jest to, że używam dwóch różnych UserManagerów, jak sugeruje trailmax, i że nie jest on przeznaczony do tego rodzaju użycia.

W powyższym przykładzie, jeśli utrzymuję UserManager w GetUserDummyTestClass trwały przez HttpRequest, dodać metodę Update i używać tylko w kontroler, wszystko działa poprawnie zgodnie z oczekiwaniami.

Więc jeśli dojdziemy do wniosku, czy słusznie byłoby stwierdzić, że jeśli chcę używać logiki z UserManagera poza zakresem kontrolera, muszę dokonać globalizacji instancji UserManager w odpowiedniej klasie, gdzie mogę powiązać instancji do HttpContext, jeśli chcę uniknąć tworzenia i usuwania instancji do jednorazowego użycia?

Aktualizacja 2

Badanie trochę dalej, zdałem sobie sprawę, że jestem rzeczywiście przeznaczone do używania jednej instancji na żądanie, i że to już rzeczywiście jest skonfigurowana dla OwinContext w Startup.Auth a później uzyskać jak to:

using Microsoft.AspNet.Identity.Owin; 

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

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

to jest rzeczywiście żenująco oczywiste patrząc na konfiguracji domyślnej AccountController dostarczonych, ale myślę, że raczej dziwne i nieoczekiwane zachowanie opisane powyżej okazały się bardzo rozpraszać. Mimo to, byłoby interesujące zrozumieć przyczynę tego zachowania, nawet jeśli nie będzie to już problemem przy użyciu OwinContext.GetUserManager.

+1

wiem, że trzeba Po pojawieniu się nowego pytania poczekaj chwilę, ale moja strefa czasowa niestety zmusza mnie do opuszczenia tego na chwilę. Jeśli ktoś ma pytania, prosimy o cierpliwość. – Alex

+0

Czy możesz wysłać przykładowy kod, który powiela twój problem? – trailmax

+0

@trailmax Zaktualizowany – Alex

Odpowiedz

7

Twój problem polega na tym, że używasz dwóch różnych instancji menedżera użytkowników i wygląda na to, że oba są statycznie zdefiniowane (co jest ogromnym nie-nie w aplikacjach internetowych, ponieważ są one udostępniane wszystkim wątkom i użytkownikom System i nie są wątku bezpieczne, nie można nawet uczynić je wątku bezpieczne zamykając się wokół nich, ponieważ zawierają one stan specyficzne dla użytkownika)

Zmień GetUserDummyTestClass do tego:

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

Dziękuję za aluzję dotyczącą tego, że jest statyczny, nie wiedziałem, że jego stan jest specyficzny dla określonego użytkownika. Ale przynajmniej powinienem był zauważyć, że zawiera instancję dbcontext, i to zdecydowanie nie powinno być statyczne. Jednak tylko z ciekawości, jak by to wyjaśnić, że UserManager w GetUserDummyTestClass wysyła zapytanie do bazy danych tylko po to, aby zignorować zaktualizowane dane, które otrzymuje? A może zachowanie w przypadku statycznej instancji nieokreślonej (i na pewno nieistotnej)? – Alex

+0

Zaktualizowane pytanie ponownie. Jeśli chcę utrzymywać UserManager przez HttpRequest (w HttpContext), po prostu muszę użyć tylko jednego wystąpienia w przepływie wykonawczym? – Alex

+1

Czy to nadal jest poprawny sposób tworzenia nowej instancji menedżera UserManager? – jasdefer