Iterating through async task to update user role - c#

I am writing an Edit method for my C.R.U.D application but I am stuck with updating user role. I am trying to iterate through user roles and assign user role is not already assigned role. I get an error saying,foreach cannot operate on variables of type 'Task<IList<string>>' because 'Task<IList<string>>' does not contain a public instance definition for 'GetEnumerator' How can I fix this ? OR What is a better way to approach this?
[HttpPost]
public async Task<IActionResult> Edit(EditUserViewModel editUserViewModel)
{
var user = _userManager.Users.Where(e => e.Id == editUserViewModel.id).SingleOrDefault();
user.FirstName = editUserViewModel.FirstName;
user.LastName = editUserViewModel.LastName;
user.MiddleName = editUserViewModel.MiddleName;
user.Dob = editUserViewModel.Dob;
var AllUserRoles = _userManager.GetRolesAsync(user);
foreach(var userRole in AllUserRoles)
{
if (userRole != editUserViewModel.RoleToBeAssigned)
{
await _userManager.AddToRoleAsync(user, editUserViewModel.RoleToBeAssigned);
}
}
await _userManager.UpdateAsync(user);
return View(user);
}

The GetRolesAsync returns a Task that represents the asynchronous operation, containing a list of role names. So you can't directly iterate on that using a foreach. You should use the await keyword to wait the result and than iterate. Example: var AllUserRoles = await _userManager.GetRolesAsync(user);
You can see here the GetRolesAsync method details.

Related

Get user roles before returning to client ASP.NET Core

I need to create a controller that will get and delete all users and a certain user, respectively and I've figured out the part to get the users, add them to a list and return the list. But I struggle with the get roles part. Initially I tried a Linq Select and that works just fine (and looks decent) for simply getting the roles but doesn't work for getting the roles for that specific user as well. I've also tried a much worse methods: getting all the users from db in a list, foreach the list and for every iteration in the foreach, create a new object of the type I want to return and assign the role for that user to the respective object property. Doesn't work AND it's ugly.
TL;DR : is this the proper way to get roles in a select and does the 'Role' property need to be IList 100%?
Here's my GetRoles method:
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult GetUsers()
{
var userDbList = _userManager.Users.ToList();
if (userDbList is null) return BadRequest(AppResources.UsersDoNotExist);
// my initial select. GetUsersModel is just a model for the object
// I return and contains Name, Email, UserName and Role. As you can see,
// the method is async but I get a cannot implicitly convert System.Collections
// .Generic.IList<string> to string (I suspect I need to set Role as a List<string>?)
var userList = userDbList.Select(async user => new GetUsersModel
{
Name = $"{user.FirstName} {user.LastName}",
UserName = user.UserName,
Email = user.Email,
Role = await _userManager.GetRolesAsync(user)
});
/*var userList = new List<GetUsersModel>();
foreach (var user in userDbList)
{
userList.Add(new GetUsersModel
{
Name = $"{user.FirstName} {user.LastName}",
UserName = user.UserName,
Email = user.Email,
Role = await _userManager.GetRolesAsync(user)
});
}*/
return Ok(userList);
}
I have a similar method for deleting a user, only I first check request data and remove the user if everything's ok and then create a list to include in an object (along with a message if the removal was successful) to return.
When you use async lambda in a select, this return IEnumerable<Task<T>>. In you case, the variable userList is type of IEnumerable<Task<GetUsersModel>>. You need to wait all tasks and get the result like :
public IActionResult GetUsers()
{
var userDbList = _userManager.Users.ToList();
if (userDbList is null) return BadRequest(AppResources.UsersDoNotExist);
IEnumerable<Task<GetUsersModel>> userListTasks = userDbList.Select(async user => new GetUsersModel
{
Name = $"{user.FirstName} {user.LastName}",
UserName = user.UserName,
Email = user.Email,
Role = await _userManager.GetRolesAsync(user)
});
IEnumerable<GetUsersModel> userList = userListTasks.Select(t => t.Result);
return Ok(userList);
}
If the method UserManager.GetRolesAsync isn't thread safe, then :
public IActionResult GetUsers()
{
var userDbList = _userManager.Users.ToList();
if (userDbList is null) return BadRequest(AppResources.UsersDoNotExist);
IEnumerable<GetUsersModel> userList = userDbList.Select(user => new GetUsersModel
{
Name = $"{user.FirstName} {user.LastName}",
UserName = user.UserName,
Email = user.Email,
Role = _userManager.GetRolesAsync(user).Result
});
return Ok(userList);
}

Asp.net Core Role Expiration

Is there a way to remove a user from a role after a given timespan? When I try something like the below code, I get a null exception once the Delay continues in sessionExpired()...
public async Task<IActionResult> PurchaseSession(PurchaseSessionViewModel model)
{
var user = await _userManager.GetUserAsync(User);
await _userManager.AddToRoleAsync(user, "Active");
await _signInManager.RefreshSignInAsync(user);
// no await
sessionExpired(user);
return RedirectToAction(nameof(Index));
}
private async void sessionExpired(ApplicationUser user)
{
await Task.Delay(10000);
await _userManager.RemoveFromRoleAsync(user, "Active");
}
Note, I understand why the exception occurs but I'd like to retain this type of role-based authorization since [Authorize(Roles = "Active")] provides the functionality I'm after. Is there another way to do this?
Your problem is that your user variable is local and thus is deleted after your first function ends.
You may use a closure (as a lambda function). It is a block of code which maintains the environment in which it was created, so you can execute it later even if some variables were garbage collected.
EDIT: If you want to know why my previous solution didn't work, it is probably because user was being disposed by Identity at a time or another, so here is another try:
public async Task<IActionResult> PurchaseSession(PurchaseSessionViewModel model)
{
var user = await _userManager.GetUserAsync(User);
await _userManager.AddToRoleAsync(user, "Active");
await _signInManager.RefreshSignInAsync(user);
// We need to store an ID because 'user' may be disposed
var userId = user.Id;
// This create an environment where your local 'userId' variable still
// exists even after your 'PurchaseSession' method ends
Action sessionExpired = async () => {
await Task.Delay(10000);
var activeUser = _userManager.FindById(userId);
await _userManager.RemoveFromRoleAsync(activeUser, "Active");
};
Task.Run(sessionExpired);
return RedirectToAction(nameof(Index));
}

ASP MVC ConfirmEmailAsync not working

I register a user, receive a token via email which looks like this:
Please confirm your account by clicking here
I click the link and I can see that the ConfirmEmail method in AccountController fires:
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
And that result.Succeeded is true.
Everything appears fine, but when trying to log in after completing this process I get taken to the page telling me my account is locked
Locked out.
This account has been locked out, please try again later.
What couldI be doing wrong? Do I need to manually change the lockout flag in the db? If so, what is the point of the ConfirmEmailAsync method?
ConfirmEmailAsync just sets the EmailConfirmed on the user account record to true. From UserManager (edited for brevity):
public virtual async Task<IdentityResult> ConfirmEmailAsync(TUser user, string token)
{
...
var store = GetEmailStore();
...
await store.SetEmailConfirmedAsync(user, true, CancellationToken);
return await UpdateUserAsync(user);
}
Where GetEmailStore returns the IUserEmailStore (which is implemented by UserStore by default), which sets the flag:
public virtual Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken))
{
...
user.EmailConfirmed = confirmed;
return Task.CompletedTask;
}
The error you're getting indicated that the LockoutEnabled flag on the user account is true. You can set this to false by calling the SetLockoutEnabledAsync method on the UserManager.
There is also a SupportsUserLockout flag on the UserManager which unlocks accounts by default on creation. In order to set this you will need to create your own UserManager and override this flag to false.
At first, I had challenges getting these to work and after a series of research no success. Finally, I got the root of the problem(s) and fixed them thus sharing my experience. Follow the following process and I am sure it will help.
Step 1
Goto Startup.cs and remove the code below if you have it initialised;
services.Configure<RouteOptions>(options =>
{
options.LowercaseUrls = true;
//options.LowercaseQueryStrings = true; //(comment or remove this line)
});
Step 2 For GenerateEmailConfirmationTokenAsync() / ConfirmEmailAsync()
2a. On registering new user for token generation go as thus;
var originalCode = await userManager.GenerateEmailConfirmationTokenAsync(user);
var code = HttpUtility.UrlEncode(originalCode);
var confirmationLink = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, token = code }, Request.Scheme);
2b. On receiving confrimationLink for email confirmation, go as thus
var originalCode = HttpUtility.UrlDecode(token);
var result = await userManager.ConfirmEmailAsync(user, originalCode);
if (result.Succeeded)
{
return View(); //this returns login page if successful
}
For GeneratePasswordResetTokenAsync() and ResetPasswordAsync()
a.
var originalCode = await userManager.GeneratePasswordResetTokenAsync(user);
var code = HttpUtility.UrlEncode(originalCode);
var passwordResetLink = Url.Action("ResetPassword", "Account",
new { email = model.Email, token = code }, Request.Scheme);
b.
var orginalCode = HttpUtility.UrlDecode(model.Token);
var result = await userManager.ResetPasswordAsync(user, orginalCode, model.Password);
if (result.Succeeded)
{
return View("ResetPasswordConfirmation");
}

Delete user from default mvc application database

I am trying to delete an already existing user from a database, which was created automatically when creating MVC application.
The database consists of tables:
AspNetUsers
AspNetUserRoles
AspNetUserLogins
AspNetUserClaims
AspNetRoles
In my code it looks like this:
var user = new ApplicationUser { UserName = model.email, Email = model.email };
var context = new ApplicationDbContext();
context.Users.Attach(user);
context.Users.Remove(user);
context.SaveChangesAsync();
return RedirectToAction("OperationSuccess", "Account");
I have also tried this:
var user = new ApplicationUser { UserName = model.email, Email = model.email };
var context = new ApplicationDbContext();
UserManager.DeleteAsync(user);
But it doesn't help at all. The application itselt does not break and does not show any errors, but the user is still in the database. How do I delete it?
Try this code:
public async Task<IdentityResult> DeleteUser(string email)
{
var user = UserManager.Users.FirstOrDefault(x => x.Email == email);
if(user == null) return null;
var result = await UserManager.DeleteAsync(user); //here result has two properties Errors and Succeeded.
return result;
}
Also, your code is not working because you are creating the object yourself and assigning only two properties yourself in spite of fetching the data from database.
Hi I think you have some versioning problem and its seems that you need to give one extra paramater to the DeleteAsync method.
Kindly refer the below link, since they had same kind of issue and resolved it.
https://stackoverflow.com/a/24594440/3397630
Hope it may give you some idea for your solution too.
Thanks
Karthik
Hope below code will help you to fix your problem
[HttpPost]
public async Task<ActionResult> Delete(string userId)
{
// Check for for both ID and exit if not found
if (String.IsNullEmpty(userId))
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var user = UserManager.Users.SingleOrDefault(u => u.Id == Userid);
// Look for user in the UserStore
// If not found, exit
if (user == null)
{
return HttpNotFound();
}
var results = await UserManager.DeleteAsync(user); // Remove user from UserStore
// If the statement is a success
if (results.Succeeded)
{
// Redirect to Users page
return RedirectToAction("Index", "Users");
}
else
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}

Handling Session in async methods

I am new to MVC5 and trying to implement sessions with in async methods in Controllers. I have created method like this
public async Task<ViewResult> Index()
{
string currentUserId = User.Identity.GetUserId();
var userId = Convert.ToInt64(HttpContext.Session["UserId"]);
var userDetail = await service.GetBy(currentUserId),
}
This session thing, always fails with the following message
Server Error in Application. Unable to cast object of type
'System.Threading.Tasks.Task`1[System.Int64]' to type
'System.IConvertible'.
(Sorry, I am not able to post screenshot of actual error, as do not have enough reputation points on this site)
I also tried to do it like this
var userId = await Task<long>.FromResult(Convert.ToInt64(HttpContext.Session["UserId"]));
and
var userId = await (Session["UserId"] as Task<long>);
and
var userId = await Convert.ToInt64(HttpContext.Session["UserId"]);
But, it always gives the same message. Can somebody please point me to the correct approach. Also, if we can not use session with in async methods, then what would be the best solution for it.
The error implies that HttpContext.Session["UserId"] is storing a Task instead of an actual int64. That means the somewhere in your code, where you store the userId in the session you are probably forgetting to await a task for its result.
Probably looks like:
HttpContext.Session["UserId"] = GetUserIdAsync();
Instead of:
HttpContext.Session["UserId"] = await GetUserIdAsync();
You could also await the stored task as you've tried to do (with the wrong type conversion):
var userId = await ((Task<Int64>)HttpContext.Session["UserId"]);

Categories

Resources