Odd behavior by UserManager in .Net Identity - c#

To keep this question simple, I'll describe the higher level problem and then go into any implementation details if needed.
I use the ASP.NET Identity in my application under development. In a specific scenario on a series of requests, the UserManager first get the current user(at least one FindById request), where the user is fetched. On a subsequent request, I update information on this user that is saved by UserManager.Update and I can see the change persisted in the database.
The problem is here that on further subsequent requests, the user object gotten from FindById is not updated. That is strange, but could be something about caching in UserManager I do not understand. However, when I trace the database calls, I see that UserManager indeed is sending the sql-requests to the database for getting the user.
And this is where it gets really strange - even though the database is confirmed to be up to date, UserManager still somehow returns an old object from this process. When I myself run exactly the same query traced directly to the database, I get updated data as expected.
What is this black magic?
Obviously, something is cached somewhere, but why does it make a query to the database, just to disregard the updated data it gets?
Example
This below example updates everything as expected in the db for each request to the controller action, and when GetUserDummyTestClass is calling findById on the other instance of UserManager I can trace the sql requests, and can test these directly to the db and verify that they return updated data. However, the user object returned from that very same line of code still has the old values (in this scenario, the first edit after the application was started, regardless of how many time the Test action is invoked).
Controller
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;
}
}
Update
As Erik pointed out, I should not use static UserManagers. However, if I keep the UserManager in GetUserDummyTest bound to the HttpContext (persisting it per HttpRequest) in case I want to use it several times during a request, it is still caching the first User object it gets by a Id, and disregarding any updates from another UserManager. Thus suggesting that the real issue is indeed that I'm using two different UserManagers as trailmax suggested, and that it's not designed for this kind of usage.
In my example above, if I keep the UserManager in GetUserDummyTestClass persistent over the HttpRequest, add a Update-method and only use this in the controller, everything works fine as expected.
So if going to a conclusion, would it be correct to state that if I want to use logic from a UserManager outside of the scope of the controller, I have to globalize the UserManager instance in an appropriate class where I can bind the instance to the HttpContext, if I want to avoid creating and disposing instances for one-time usage?
Update 2
Investigating a little further, I realized that I am indeed intended to use one instance per request, and that this already actually is set up for the OwinContext in Startup.Auth and later accessed like this:
using Microsoft.AspNet.Identity.Owin;
// Controller
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>()
// Other scopes
HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>()
This is actually embarrassingly obvious looking at the setup of the default AccountController provided, but I guess the rather strange and unexpected behavior described above proved quite distracting. Still, it would be interesting to understand the reason for this behavior, even though it will not be a problem anymore using OwinContext.GetUserManager.

Your problem is that you're using two different UserManager instances, and it looks like they're both statically defined (which is a huge no-no in Web applications, since these are shared between all threads and users of the system and are not thread safe, you can't even make them thread safe by locking around them because they contain state specific to a user)
Change your GetUserDummyTestClass to this:
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);
}
}

Related

Why is my scoped service being called as a new instance every time?

This is a practice ASP.NET project I'm using to better understand a few techniques, and while I've got Dependency Injection working, its not working quite as I want it to. I have a class that I want to use to store a history, so every time the user hits a submit button, it displays a result, and after the second time it starts displaying the history. Anyway I added the history to the DI as a scoped service, thinking that would mean it would be created and then remain the same instance for the duration of the session for that user. However according to the debugger it looks like the list never gets bigger than one, and thats at the point of adding the item to the list. So the code.
The object
{
public class RollHistory : IRollHistory
{
public List<IRollMessage> Entries { get; set; } = new List<IRollMessage>();
}
}
The DI
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddTransient<IDiceTray, DiceTray>();
services.AddTransient<IRollMessage, RollMessage>();
services.AddScoped<IRollHistory, RollHistory>();
}
The Controller constructor
public HomeController(ILogger<HomeController> logger, IDiceTray diceTray, IRollMessage rollMessage, IRollHistory rollHistory)
{
_logger = logger;
_diceTray = diceTray;
_rollMessage = rollMessage;
_rollHistory = rollHistory;
}
And the code for when the button gets clicked
[HttpPost]
public IActionResult Index(DiceRollModel diceRoll)
{
_diceTray.DiceRoll(diceRoll.DiceType, diceRoll.DiceCount, diceRoll.Bonus, diceRoll.VantageType);
_rollMessage.RollMessages(_diceTray);
diceRoll.RollResult = _rollMessage;
_rollHistory.Entries.Add(_rollMessage);
diceRoll.History = _rollHistory.Entries;
return View(diceRoll);
}
It's worth noting I've tried to code this at least 4 different ways with and without DI, the only way it works is if I use AddSingleton, while this might not be an issue because this app is unlikely to ever be live, its a poor excuse not to do it right.
I believe “scope” is by default per request which would explain that each submit gets is own service.
“Doing stuff right” is of course to some extend a matter of opinion. But my opinion would clearly be that I would avoid server-side session to avoid problems with scaling to more than one instance. There are also ways to support shared state, but this is difficult. To me singletons are not a code smell either, but they have their own problems.
Your problem might be solved by storing whatever state you need in the browser either in a cookie or localStorage. Your service would then have request scope, but it would read user state from browser causing “user scope” for the data. (But don’t rely on browser state to persist and remember it is modifiable to the user.)

Is possible to alter Scoped service from controller?

I am pretty much new in Asp.Net Core world and I am playing around with services now. I had an idea to create instance of class I created (named CourseEditor) that will be accessible through whole controller (in every action on that very controller). So I added the class as a Scoped service to Startup.cs and method ConfigureServices:
services.AddScoped<CourseEditor>();
Now I have my controller CourseEditorController.cs.
[Authorize(Roles = "Admin")]
[ViewLayout("_CourseEditorLayout")]
public class CourseEditorController : Controller
{
private CourseEditor _courseEditor;
private readonly SignInManager<IdentityUser> _signInManager;
public CourseEditorController(SignInManager<IdentityUser> signInManager, CourseEditor courseEditor)
{
_courseEditor = courseEditor;
_signInManager = signInManager;
}
[HttpGet]
public async Task<IActionResult> OpenCourse(int courseId)
{
Database db = new Database();
_courseEditor = await db.LoadCourseForEditByIDAsync(courseId);
return RedirectToAction("Index");
}
public IActionResult Index()
{
return View(_courseEditor);
}
public IActionResult EditHead()
{
return View(_courseEditor);
}
}
And I am quite stuck. Because every time I load into this controller, the _courseEditor is rewritten to default values. So now I am trying to figure out, how to alter the parameters of the service CourseEditor itself so it won't "reset" the _courseEditor every time I jump between actions.
So basically I am trying to alter the service CourseEditor.Title in controller CourseEditorController.cs because it's by default null and it's rewriting the _courseEditor.Title from actual text to null. Can I do that?
//Edit:
I forgot to explain how the Controller works. So basically when user moves to this "editor" controller, first it goes through action "OpenCourse" that will load all the data as _courseEditor.Title and stuff from MySQL Database. But as you can see, after that there is a RedirectToAction("Index"). So the _courseEditor is run through the constructor and there is everything set back to null since it's the value that was set when the program was initializing the service. Or at least I think this is happening.
So the solution is to add the service not as a Scoped service, but as a Singleton.
services.AddSingleton<CourseEditor>();
Unfortunately for me this does not solve anything since Singleton represents one instance for whole application which means every user will see data from this very instance of editor. They cannot create their "own" instance of editor.
One way how to reach that possibility of creating more instances is via ConcurrentDictionary (thank you #Legacy Code for the explanation). More details about this static dictionary is here in Asp.net core docs:
https://learn.microsoft.com/cs-cz/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=netcore-3.1
Second way is probably to just recover the data from database on every Action call.
Another way might be to use some kind of transporter as Cookies but this approach is not very secure since User can manipulate with cookies and it might be really hard to store a complex object there.

SignalR and ASP.NET Identity's UserManager class lifetime

In my MVC application, I user SignalR for communication between users. Basically, a client calls a method on the hub, which calls a method on a repository, which then saves the message to the database and the hub notifies the other client of the new message.
I had used the GetOwinContext() method during these calls from the client to get the current instance of UserManager and ApplicationDbContext, by using the GetUserManager<UserManager>() and Get<ApplicationDbcontex>() extension methods, respectively. However, I have noticed that calls from the same connection use the same context, which is, obviously, not a very good thing. I went ahead and changed my repository so it is like this now:
public XyzRepository() //constructor
{
db = ApplicationDbContext.Create(); //static method that generates a new instance
}
private ApplicatonDbContext db { get; set; }
private UserManager UserManager
{
get
{
return new UserManager(new UserStore<ApplicationUser>(db)); //returns a new UserManager using the context that is used by this instance of the repository
}
}
Since I reference the ApplicationUser objects using the UserManager (using FindByIdAsync(), etc, depending on the design), it is extremely important to use the context I currently work with for the UserStore of the UserManager's current instance. The repository is created once per request, which seems to apply to each SignalR calls as intended. While I have experienced no problems with this design so far, after reading about the issue (in this article), particularly this line:
"In the current approach, if there are two instances of the UserManager in the request that work on the same user, they would be working with two different instances of the user object.", I decided to ask the community:
Question: what is the preferred way to use ASP.NET Identity's UserManager class with SignalR, if it is imperative that I use the same instance of DbContext for my repository's methods that the UserManager's UserStore uses?
I think the preferred way is to use an Inversion of Control container and constructor-inject dependencies with some kind of lifetime scope. Here is another question that you might want to look into:
Using Simple Injector with SignalR
It is preferable that your DbContext instance live as long as the current web request. IoC containers have facilities that let you register DbContext instances with per web request lifetimes, but you need to set up the IoC container so that it can manage the construction of the Hub classes to achieve this. Some IoC containers (like SimpleInjector) will also automatically dispose of the DbContext at the end of the web request for you, so you don't need to wrap anything in a using block.
As for the UserManager, XyzRepository, etc, I think those can also have per-web-request lifetime, or even transient lifetimes. Ultimately, I don't see why you wouldn't be able to achieve something like this:
public class MyXyzHub : Hub
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly MessageRepository _messageRepository;
public MyXyzHub(UserManager<ApplicationUser> userManager,
MessageRepository messageRepository)
{
_userManager = userManager;
_messageRepository= messageRepository;
}
public void sendMessage(string message)
{
var user = _userManager.FindByIdAsync(...
_messageRepository.CreateAndSave(new Message
{
Content = message, UserId = user.Id
});
Clients.All.receiveMessage(message, user.Name);
}
}
If you wire up your IoC container the right way, then every time the Hub is constructed, it should reuse the same ApplicationDbContext instance for the current web request. Also with your current code, it looks like XyzRepository is never disposing of your ApplicationDbContext, which is another problem that an IoC container can help you out with.

SimpleMembershipProvider intermittently returning wrong user

I am administrator of a small practice project web application, AngularJS front-end pulling its back-end data from a C#/.NET WebAPI, and I'm handling security using the SimpleMembershipProvider.
I suspect that the way I implemented said security is not the best (I'm told ASP.NET Identity is now the way to go?) but that's another question altogether.
The issue that I'm very bewilderingly running into is that I get occasional reports that on a given page load to display a particular user's data, it returns somebody else's. Reloading the page fixes the issue (evidently) and I haven't been able to duplicate the scenario myself, or figure out anything particularly consistent in the users to which this happens.
None of the information being displayed is at all sensitive in nature (the app's just a friendly front end for an already public third-party API) so I'm not in panic mode about this, but I am both concerned and confused and want it fixed.
Here is what one of my API controller endpoints looks like:
[Authorize]
public class UserController : ApiController
{
private static int _userId;
private readonly IUserProfileRepository _userProfileRepository;
public UserController()
{
_userProfileRepository = new UserProfileRepository(new DatabaseContext());
_userId = WebSecurity.GetUserId(User.Identity.Name);
}
public UserProfileDto Get()
{
return _userProfileRepository.GetUserProfileById(_userId).ToDto();
}
}
Any feedback on where I might be going wrong here or what might be causing the intermittant inconsistency would be very much appreciated. (Laughter also acceptable if the way I handled this is just really bad. :P )
Static class fields are shared by all instances/threads of the same AppDomain (in your case - process). Different http requests are processed by threads running in parallel. Any two threads running [almost] at the same time may (will) change the value of _userId. You are assigning _userId in the constructor of your controller, and a new instance of this controller is created for each http request that is to be responded to by UserController. Therefore, this assignment will happen multiple times.
You will have hard time replicating this problem, since you are a single user testing the code, hence there are no overlapping request threads.
Remove static specifier from the _userId field declaration of the controller class.
Note: make sure that DatabaseContext is disposed of. One place that can be used for this is the overriden Controller.Dispose.
Change the Get to retrieve the user id rather than from a static variable:
public UserProfileDto Get()
{
return _userProfileRepository.GetUserProfileById(WebSecurity.GetUserId(User.Identity.Name)).ToDto();
}

load child objects in EF5

I have a method in my generic repository:
public IQueryable<T> Query<T>() where T : class, IEntity
{
return _context.Set<T>();
}
This is method for getting user:
public User GetUser(string email)
{
return _repository.Query<User>().FirstOrDefault(u => u.Email == email);
}
Finally, I put the user to session:
AppSession.CurrentUser = UserService.GetUser(email);
In my action I need to get the current user and get collection of objects Notifications (one-to-many):
AppSession.CurrentUser.Notifications.OfType<EmailNotification>().FirstOrDefault();
But, here I get the error:
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I know that Notifications not loaded when I getting User from DB.
How to say EF to load Notifications objects? I know about Include, but I cannot use it in GetUser method.
When the first HttpRequest ends after looking up your CurrentUser object, your _repository reference that the CurrentUser is expecting for additional lookups like EmailNotifications isn't available.
The exception is thrown because CurrentUser doesn't have the original object context, so you either have to attach the CurrentUser object to the new objectContext that your _repository is using, or use the easier solution of simply reloading the user through the new context that was created for your current request in the repository.
Before attempting to find the notifications in your action, add the following line:
AppSession.CurrentUser = UserService.GetUser(AppSession.CurrentUser.Email);
AppSession.CurrentUser.Notifications.OfType<EmailNotification>().FirstOrDefault();
As #Ryan said it is due to the fact that the object context is not available to lazy load in the associated notifications.
What I'd suggest is turn off lazy loading (if possible) as can cause lots of issues later and then do something like ...
var user = UserService.GetUser(AppSession.CurrentUser.Email);
user.Notifications = NotificationService.GetUserNotifications(user.Id /* or another identifier */);
AppSession.CurrentUser = user;
To do this you will require a new NotificationService, this can load (as suggested above) but also handle the execution of notifications (sending emails etc).
You should now have the notifications for that user in your Application session cache.
HTH

Categories