ASP.NET Identity version 1.0
What is the default behavior of UserManager.CreateAsync(user, password) method call if we are trying to add an user with ID already existing in database?
I'm asking because some time ago the following line of code
IdentityResult result = await UserManager.CreateAsync(user, password);
returned an IdentityResult object with not empty Errors list saying something like "User with ID [ID] already exists".
But now (we didn't do much changes since that) we've got an exception when attempting to insert new user record in the database.
In both cases UserManager.CreateAsync calls FindByNameAsync in our IUserStore implementation and that method finds and returns a correct User object.
How can I see the internal implementation of UserManager.CreateAsync method?
Does it check new User object for existence before calling CreateAsync of IUserStore?
I guess it does (since it calls FindByNameAsync but it seems it does not consider found User object as "existing".
I've found the answer using DotPeek (thanks trailmax for his suggestion).
Yes, UserManager.CreateAsync makes validation (via UserValidator class) and the validator (among other conditions) has the following piece of code:
TUser owner = await this.Manager.FindByNameAsync(user.UserName);
if ((object) owner != null && owner.Id != user.Id)
errors.Add(...);
So, as we see it checks if the user with this UserName already exists BUT the error is thrown only if the Id of that user differs from the Id of User object we are trying to create.
Since we generated Id before insertion and it actually equals to User's email - that caused the problem. The solution is simple: it's better to generate user's Id right before it's inserted into database.
Related
I am working on my Uni project that uses database with two tables: Role and User.
Users.RoleId is a foreign key referring to Role.Id
I generated Controllers for each table/model classes using Scaffolding template "MVC Model with views using Entity Framework", so RolesControler.cs and UsersControler.cs are almost the same.
I can create, edit or delete roles using different views which can be seen here.
As you can see, new role is inserted into the database.
While trying to do the exact thing using views specified for UsersController.cs I can't add or modify a user. I fill all the inputs but submitting won't give any results, page is not refreshing (not redirecting to index.cshtml), no data is added to the database, just nothing happens, I can click "Create" button forever.
I tried adding users manually using SQL query in SQL Server Object Explorer and they appear on the list. What's interesting I can delete them using Delete method. The button reacts, redirect me to main page and deletes record from database.
Also while trying to create new User I can only select Roles that already exists - which is intended - but that suggests that the referrence between User.RoleId and Role.Id is done properly.
Here is code for Create method in UsersControler.cs:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,Surname,DateOfBirth,Login,RoleId,IsDeleted")] User user)
{
if (ModelState.IsValid)
{
_context.Add(user);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["RoleId"] = new SelectList(_context.Roles, "Id", "Id", user.RoleId);
return View(user);
}
I don't really know why everything is fine for Role table/model, but when it comes to User I can only delete records that already exist in database.
EDIT:
I tried inserting new user using invalid date (year 32121) and validation error occurs, which for me indicates that the submit button reacts and validates input data, but if it's valid, it doesn't insert the record to database and doesn't redirect into index page.
May I ask what your model classes for Role and User look like? I suspect that your data types are not matching. I have a nearly similar problem as you, only that I cannot even enter the foreign key
my User controller
my service implementation
In the UserServiceImpl, updateUsers function, do a findbyId or findbyuser to get the ID of the user.
Check if that user is already present and then call the save function within that if.
Sample:
Do a isuserexisting = repository. findbyid or findbyUser
if(isuserexisting != null)
response.setId(isuserexisting.getId());
usersRepository.save(response);
I need to get the user with some username but the function call seemingly does nothing. I'm using the exact same call in a different controller and it's working just fine.
This is the line:
var user = await _userManager.FindByNameAsync(username);
When I put a break point on this line and the line below it, the debugger stops on this line, and when I try to go to the next line it just pauses for a bit and skips the rest of the function. As a result the controller function also doesn't return anything. Both controllers where I use call this function have the exact same declarations for _userManager and use it in the same way. Only difference is that the one that works gets its parameter from a passed objects property, whereas the other one just uses a string.
Please help I'm starting to lose my hair.
Ensure that you initially create the user using the CreateAsync method of a Microsoft.AspNetCore.Identity.UserManager instance. If you create the user by adding it directly using Entity Framework, you won't be able to find it using FindByNameAsync.
Note: Anybody facing this issue because they couldn't create users using "CreateAsync" or they have an already existing set of users, apply this solution.
Make sure the users in your database who where not created using the "CreateAsync" method of the user manager pass through the following.
//Get the corresponding user by id
var usr2 = _userManager.FindByIdAsync("youruserid").Result;
//Apply the following updates to the user model in the database
var res = await _userManager.UpdateSecurityStampAsync(usr2);
await _userManager.UpdateNormalizedEmailAsync(usr2);
await _userManager.UpdateNormalizedUserNameAsync(usr2);
await _userManager.SetLockoutEnabledAsync(usr2, true);
Apply the above code to every user in your database that was not created using "CreateAsync".
After these steps, you'll now be able to Find your user by username and email.
Note: If your users weren't assigned claims, don't forget to assign claims in the Database.
Current project:
ASP.NET 4.7.2
MVC 5
C# 7.2
Repository Pattern
I am a bit stumped on how to create a “virtual” Role under MVC 5.
Normal (persistent) roles I fully understand: you create them in the DB and then assign a user to that role. However, I need to assign a user to a “Role” depending on a status in a completely unrelated table, and where that Role only exists during their session - it does not exist for that user before they log in, and it no longer exists for that user once their session ends.
So for example, since the Active status is dependent on whether a user
is on leave or not, let’s call this table the “on leave” table. Very
simple: primary key, user id foreign key, a required start date and an
optional end date. When a user logs in, I need to flag the user as
either fully active (an actual Active role for just that session) or
inactive (no Active role for that session). This will be determined by
whether,
The user has an entry in the “on leave” table, and if so,
The most recent entry has,
A null end date, or
An end date in the future
If the user has an entry in the table, and the most recent entry has a
null end date or an end date in the future, the user does not get the
Active role. If they don’t meet that requirement, they do get that
role.
This is just one of a number of requirements on the site that will require virtual roles, but is a simplified example to get the point across.
This is also very important because about 95-99% of my needs revolves around the controller and method decorations and session authorizations - I need to be able to [Authorize(Roles = "Active")] and User.Identity.IsInRole("Active"), but I want that role to exist for the user only for that session.
Please understand that the underlying data is fully dynamic: if the data on the back end changes to something that invalidates them for Active status, I want their next login to not include the Active role being applied to their session. This is why I am trying to work with a “virtual” or “temporary” role that the user is not directly associated with in terms of the in-DB data.
Now if it was just one thing that was being checked for (like the On Leave table), I could just ensure that adding an end date would add the user to a fully traditional Active role, but the problem exists for entries in the future -- how would I go around auto-adding the user to that role once that date passes, at least without doing a database-write-and-login-bounce to properly set their authentication and session variables?
Plus, this is just one item of many that need to work in concert to provide a yes/no determination for Active status. This Active status will be drawn from not just the most current entry of the On Leave table, but also a number of different business rules from around the system that can be retrieved from the initial fetching of the user’s profile through _userManager, and all of them being virtual booleans providing a simple cumulative yes-no answer.
The point is, this Active role status will never touch the database, or even need to. This will always be in the context of the currently logged-in user, set when they log in, and dumped/destroyed when they time out or log out.
I suspect that the place to do this is in the SignInAsync where I set all my Claims, I just don’t know how to do this to the user’s session.
If someone can throw some hints on how to set up a virtual Role there, that the system (controller and method decorations) can work with, that would be real swell.
As requested, my login implementation:
var user = await _userManager.FindAsync(model.Username, model.Password);
if(user != null) {
SignInAsync(user, false).Wait();
// Bounce the user to the "Nexus" method, that determines where they should go based on Role, so that the Role can actually be read once it is in the User’s context.
}
And my SignInAsync() Task:
private async Task SignInAsync(IdentityUser user, bool isPersistent) {
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await _userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new UserClaim.Claim("ShortName", user.ShortName));
identity.AddClaim(new UserClaim.Claim("Name", user.Name));
// Snipped for brevity
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
It seems like Microsoft is going toward Claims based authorization. In ASP.NET Identity, roles are there for backward compatibility.
However, you can still set role as Claim. For example,
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
You then apply Authorize attribute to controller or action method as [Authorize(Roles = "Admin")]
Here is the sample code at GitHub.
While learning C# and MVC, I'm using the stock MVC template in VS2013 Preview, I'm trying to get a reference to the User class (what the templates creates in the IdentityModel.cs file) of both the currently logged in user (if applicable) or from a user id guid.
Currently I have this (and it works), but it seems a little convoluted and possibly expensive (I'm not sure how it all works under the hook).
IUser iUser = await Users.Find(User.Identity.GetUserId()); //have to reference Microsoft.AspNet.Identity to access the GetUserId method
IUser iUserFromId = await Users.Find("user id guid placeholder");
User user = iUser != null ? (User)iUser : null;
Is there a cleaner or more efficient way of doing this? Is there away do it without async methods?
The new identity api is intended to be async so you are doing it correctly.
You could write it in one line as:
User user = await Users.Find(User.Identity.GetUserId()) as User;
In RTM it will change a little bit, but the gist of it is the same except the Manager will be have a generic so you don't have to do the cast and will look more like:
User user = await Users.FindAsync(User.Identity.GetUserId())