I am currently doing an ASP.NET Web Application under MVC in C# using Individual User Accounts. I need to get the Role names for a user. In a previous version the following code would have worked:
foreach (var role in userObject.Roles)
{
appUser.RolesForUser.Add(role.Role.Name);
}
but now after doing Update 3, I now get:
Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole' does not contain a definition for 'Role' and no extension method 'Role' accepting a first argument of type 'Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole' could be found (are you missing a using directive or an assembly reference?
I would like to know how I can get role names with the new standard. Thanks for any solutions!
Try this:
//Get user roles
var userRoles = await UserManager.GetRolesAsync(user.Id);
Edited
If you have the userName instead of the userId, you have to retrieve the user using FindByNameAsync instead of FindByIdAsync.
var user = await UserManager.FindByNameAsync(myName);
Once you have the user object, you will be able to get the associated roles just doing:
var userRoles = await UserManager.GetRolesAsync(user.Id);
Example:
//
// GET: /Users/Edit/Bob
public async Task<ActionResult> Edit(string userName)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return HttpNotFound();
}
var userRoles = await UserManager.GetRolesAsync(user.Id);
return View(new EditUserViewModel()
{
Id = user.Id,
Email = user.Email,
RolesList = RoleManager.Roles.ToList().Select(x => new SelectListItem()
{
Selected = userRoles.Contains(x.Name),
Text = x.Name,
Value = x.Name
})
});
}
Related
I am trying to debug a NullReferenceException in a .NET Core API and Angular application and I'm out of ideas.
I am trying to update a property of a User (the "About" section)
Update text area screengrab
In the backend code, in AuthController, I have a Login method that creates the claims and it seems to work fine:
[HttpPost("login")]
public async Task<IActionResult> Login(LoginViewModel loginViewModel)
{
// login the user
var userFromRepo = await _authRepository.Login(loginViewModel.Email.ToLower(), loginViewModel.Password);
// check that user is logged in
if (userFromRepo == null)
return Unauthorized();
// create claims using user id and main email
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userFromRepo.Id),
new Claim(ClaimTypes.Name, userFromRepo.MainEmail),
new Claim(ClaimTypes.Name, userFromRepo.FirstName)
};
// generate key from secret token
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));
// generate hash and credentials
var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
// create token descriptions
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = cred
};
// instantiate token handler
var tokenHandler = new JwtSecurityTokenHandler();
// create token
var token = tokenHandler.CreateToken(tokenDescriptor);
// write token and return request
return Ok(new
{
token = tokenHandler.WriteToken(token),
});
}
I then have a method that updates a property of a User:
[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(string id, UserForUpdateDto userForUpdateDto)
{
// this is a check if user ID that is updating the profile matches the ID in the token
if (id != User.FindFirst(ClaimTypes.NameIdentifier).Value)
{
return Unauthorized();
}
var userFromRepo = await _doMoreRepo.GetUser(id);
userFromRepo.About = userForUpdateDto.About;
await _doMoreRepo.UpdateUser(id);
return NoContent();
}
During debugging I get an 500 error with a message
System.NullReferenceException: Object reference not set to an instance of an object
on the line of code:
User.FindFirst(ClaimTypes.NameIdentifier).Value
It works OK in Postman so my guess is that somehow I am not getting anything from the NameIdentifier but I don't know why?
I'm just not sure where to look any more.
My front end Angular code is as follows - Login method that adds the token to the storage:
login(model: any) {
return this.http.post(this.url + 'login', model)
.pipe(
map((response: any) => {
const user = response;
if (user) {
localStorage.setItem('token', user.token);
this.decodedToken = this.jwtHelper.decodeToken(user.token);
console.log('This is decoded token');
console.log(this.decodedToken);
}
})
);
}
Update Profile method:
updateProfile() {
this.userService.updateUser(this.authService.decodedToken.nameid, this.user).subscribe(next => {
this.alertify.success('Profile updated');
this.editForm.reset(this.user);
}, error => {
console.log(error);
this.alertify.error(error);
});
}
I looked at very similar issue here so I double checked the solution but in my case I do have the tokenGetter() included in my app.module.ts
export function tokenGetter() {
return localStorage.getItem('token');
}
and the import:
JwtModule.forRoot({
config: {
tokenGetter,
}
})
To investigate it more and to make sure that I have narrowed down to possible problem area, in my Controller when I replace the:
public async Task<IActionResult> UpdateUser(string id, UserForUpdateDto userForUpdateDto)
{
// this is a check if user ID that is updating the profile matches the ID in the token
if (id != User.FindFirst(ClaimTypes.NameIdentifier).Value)
{
return Unauthorized();
}
var userFromRepo = await _doMoreRepo.GetUser(id);
userFromRepo.About = userForUpdateDto.About;
await _doMoreRepo.UpdateUser(id);
return NoContent();
}
with actual value like this:
public async Task<IActionResult> UpdateUser(string id, UserForUpdateDto userForUpdateDto)
{
if (id != "user ID value")
{
return Unauthorized();
}
var userFromRepo = await _doMoreRepo.GetUser(id);
userFromRepo.About = userForUpdateDto.About;
await _doMoreRepo.UpdateUser(id);
return NoContent();
}
This works ok without any errors and the property is updated ok.
What am I missing?
EDIT:
This is my updateUser() method where the http.put request is made:
updateUser(id: string, user: User) {
// console.log('user ID is: ' + id);
// console.log('User object passed to updateUser() is: ');
// console.log(user);
return this.http.put(this.baseUrl + 'user/' + id, user);
}
Which hits the UpdateUser() in the UserController.cs at the back-end.
Thanks to the information provided by Panagiotis Kanavos I investigated it further and the request was missing the authentication header.
Because I am using JwtModule:
export function tokenGetter() {
return localStorage.getItem('token');
}
JwtModule.forRoot({
config: {
tokenGetter,
}
})
I thought that my token should be added to the headers automatically without having to add it manually.
The mistake I made was not including correct whitelistedDomains options inside the JwtModule configuration and that is why it was not added to the request header.
The solution was to whitelist the correct domains like this:
JwtModule.forRoot({
config: {
tokenGetter: tokenGetter,
whitelistedDomains: ['localhost:5000', 'localhost:5001'],
blacklistedRoutes: ['localhost:5000/auth']
}
})
I am trying to add a user to a role after successfully creating the user.
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (!ModelState.IsValid) return View(model);
var user = new ApplicationUser
{
UserName = model.PhoneNumber,
PhoneNumber = model.PhoneNumber,
NationalId = model.NationalId,
FullName = model.FullName
};
var result = await _userManager.CreateAsync(user, model.NationalId);
if (result.Succeeded)
{
var res = await _userManager.AddToRoleAsync(user, "Admin");
await _signInManager.SignInAsync(user, false);
_logger.LogInformation(3, "Applicant created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
// If we got this far, something failed, redisplay form
return View(model);
}
But, I get this error.
An unhandled exception occurred while processing the request.
InvalidOperationException: Role ADMIN does not exist.
Update:
I called the
var myrole = await _roleManager.FindByNameAsync("Admin");
and it returned null. but when i inspect
var roles = _roleManager.Roles
i get all the roles including "Admin"
I found the problem in the seed method. i do not understand it however.
in the seed method i used the RoleStore to add roles.
var roles = new[] {"Admin", "Applicant", "Student", "Role1", "Role2", "Role3", "Role4"};
foreach (var role in roles)
{
var roleStore = new RoleStore<IdentityRole>(context);
if (!context.Roles.Any(r => r.Name == role))
await roleStore.CreateAsync(new IdentityRole(role));
}
the roles where created successfully in the database table AspNetRoles.
but when acted upon, the roles were never found.
i replaced the RoleStore with RoleManager
await _roleManager.CreateAsync(new IdentityRole(role));
and like magic, it all worked out. i will do further research on the difference and the cause to understand it more.
I have a method that gets all the users that i have in my db, simply put i do this:
var allUsers = context.Users.ToList();
What i can't figure it out is that when i debug the roles property it is empty:
but in dbo.UserRoles:
What am I missing here?
EDIT:
My registration method:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
UserManager.AddToRole(user.Id, model.UserRole.ToString());
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return PartialView("~/Views/Account/Register.cshtml",model);
}
EDIT 2:
When getting the roles like this:
var roles = context.Roles.ToList();
I can see all the roles and I can also see which users have the specific role:
EDIT 3:
Tried turning lazyloading off and on
this.Configuration.LazyLoadingEnabled = true;
this.Configuration.LazyLoadingEnabled = false;
Still doesn't give me the roles data.
You have to load related entities you want to use with Include like this :
var allUsers = context.Users.Include(u => u.Roles).ToList();
Then you should be able to access user roles.
More info about that topic here
So far I have not been able to solve this they way I want. I made a work arround that works:
I created a method that got me each individual user role like so:
public string GetRole(string userId)
{
var role = UserManager.GetRoles(userId);
return role[0];
}
and in my original Getuser method i called the my recently developed method:
public UserModel GetUsers()
{
var allUsers = context.Users.Include("Roles").ToList();
var model = new UserModel
{
Users = allUsers.Select(x => new OverWatchUser
{
Email = x.Email,
EmailConfirmed = x.EmailConfirmed,
FirstName = x.FirstName,
LastName = x.LastName,
OrgId = x.OrgId,
Role = GetRole(x.Id)
}).ToList()
};
return model;
}
This gives me the data I want but I consider this a dirty fix and I hope someone out there has a proper solution to the problem.
You can use context.Roles instead of context.Users as follows and filter the target user roles. The trick is to filter the target user roles in the where method.
string Id="the_required_user_Id";
var roles = context.Roles
.Include(r => r.Users)
.Where(r => (r.Users.Select(u => u.UserId).Contains(Id)))
.ToList();
This worked fine for me, hopefully this helps someone
I'm creating a user registration system using .NET Core, Identity Core, and MVC Core. I'm able to create users and create roles in the database.
Here's the form on the view that lets me select a user and select a role to add:
#using (Html.BeginForm("AddRoleToUser", "Roles"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<p>
Username : #Html.DropDownList("UserName", (IEnumerable<SelectListItem>)ViewBag.Users, "Select ...")
Role Name: #Html.DropDownList("RoleName", (IEnumerable<SelectListItem>)ViewBag.Roles, "Select ...")
</p>
<input type="submit" value="Save" />
}
These drop-down lists are populated with users and roles that already exist in the database. They allow me to select Users , and the name of a role that I've already created. For example, I have a role with the name "admin", this form lets me select the string "admin".
Here's the action that handles adding a role to a user:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddRoleToUser(string UserName, string RoleName)
{
try
{
ApplicationUser user = _db.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
await _userManager.AddToRoleAsync(user, RoleName);
PrepopulateDropDownMenus();
ViewBag.ResultMessage = "Role created successfully!";
return View("Manage", "Roles");
}
catch (Exception ex)
{
Console.WriteLine(ex);
return View("Manage");
}
}
The action never adds the role to the user, and the exception reads "Role "ADMIN" does not exist." with no inner exception. I've tried turning the RoleName in the action parameters to all-caps, but it still does not find the role. I've also tried using the role ID instead of the name, which was also unsuccessful.
This exact code worked when I built this app using Identity 3.0 with MVC 6. It seems like something has changed in moving over to Identity Core.
Any thoughts?
Edit
Here's the code I'm using to populate the drop-down lists in RolesController via the Viewbag:
private void PrepopulateDropDownMenus()
{
var rolesList = _db.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
var usersList = _db.Users.OrderBy(u => u.UserName).ToList().Select(uu => new SelectListItem { Value = uu.UserName.ToString(), Text = uu.UserName }).ToList();
ViewBag.Roles = rolesList;
ViewBag.Users = usersList;
}
Here's how I add Identity in Startup.cs in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddEntityFramework()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
}
Here's the route in RolesController.cs I use to create a new role in the database:
[HttpPost]
public IActionResult Create(string rolename)
{
_db.Roles.Add(new IdentityRole()
{
Name = rolename
});
_db.SaveChanges();
ViewBag.ResultMessage = "Role created successfully!";
return RedirectToAction("Index");
}
I can't post comments yet to ask you, so, does your error say user admin does not exist, or role does not exist? I tried to duplicate your code on my end, and if the user doesn't exist you'll get a "user can't be null" error. However, if the role doesn't exist, you'll get a "Role [role] does not exist" error.
I assume that you already have the role added into your database? Here is some code I used in my seed method that essentially does what you want, minus using the views to do it:
// Add the Admin role to the database
IdentityResult roleResult;
bool adminRoleExists = await _roleManager.RoleExistsAsync("Admin");
if (!adminRoleExists)
{
_logger.LogInformation("Adding Admin role");
roleResult = await _roleManager.CreateAsync(new IdentityRole("Admin"));
}
// Select the user, and then add the admin role to the user
ApplicationUser user = await _userManager.FindByNameAsync("sysadmin");
if (!await _userManager.IsInRoleAsync(user, "Admin"))
{
_logger.LogInformation("Adding sysadmin to Admin role");
var userResult = await _userManager.AddToRoleAsync(user, "Admin");
}
EDIT
The way you're adding roles right now leaves the NormalizedName field in the Role table null, which I believe is used by the framework for adding roles to users. Try one of the following to add a role to the database instead of what you're currently doing:
var result = await _roleManager.CreateAsync(new IdentityRole(rolename));
Or this may also work (haven't tested this one though):
[HttpPost]
public IActionResult Create(string rolename)
{
_db.Roles.Add(new IdentityRole()
{
Name = rolename,
NormalizedName = rolename.ToUpper()
});
_db.SaveChanges();
ViewBag.ResultMessage = "Role created successfully!";
return RedirectToAction("Index");
}
Make sure when you are creating an AspNetRole the NormalizedName should not be null in order for the UserManager to work properly.
NormalizedName should be upper-case using .ToUpper()
Adding it straight to the database is a bad idea and violates every concept of encapsulation, and NormalizedName is not something you should be computing yourself.
See this answer
Replace this code:
var roleStore = new RoleStore<IdentityRole>(context);
await roleStore.CreateAsync(new IdentityRole(role));
with following:
var roleManager = services.GetService<RoleManager<IdentityRole>>();
await roleManager.CreateAsync(new IdentityRole(role));
This is the thing: if you are using major versions of identity (for example 6.0), the table AspNetRoles contains a Column named NormalizedName, if the string is null or differes from the role name written in capital letters you wont't be able to find the role name. You may insert manually (MMSMI) the string or delete per procedure all roles and recreate them.
I know I have done this before and I have many ideas but so far from what I have done to make it work it has failed my code looks like this: ( by the way I'm using Identity).
var manager = new UserManager();
ApplicationUser user = manager.Find(UserName.Text, Password.Text);
if (user != null)
{
IdentityHelper.SignIn(manager, user, RememberMe.Checked);
IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
}
else
{
FailureText.Text = "Invalid username or password.";
ErrorMessage.Visible = true;
}
I have thought about using something like this how would i go about doing so?
var userId = GetUserId();
// After they log In
Response.Redirect("~/" + userId + ".aspx");
Currently it rediercts me back to the home page. Thank you in advance.
public static void RedirectToReturnUrl(string returnUrl, HttpResponse response)
{
UserManager manager = new UserManager();
var user = manager.FindById(User.Identity.GetUserId());
if (!String.IsNullOrEmpty(returnUrl) && IsLocalUrl(returnUrl))
{
response.Redirect(returnUrl);
}
else
{
response.Redirect("~/residentpages/" + user + ".aspx");
}
}
only problem so far is that the User.Identity.GetUserId part, visual basic says the "User" in front of the Identity does not exist in the current context.
From what I can tell, you're getting slightly confused or I haven't understood you.
If you have a controller say, ProfileController then you will have an action on that controller called Details.
In this action you hit the database and get the specific profile for that user. To navigate here you do this:
public ActionResult Login()
{
// however you set the log in status
return RedirectToAction("Details", "ProfileController", new { id = userId });
}
This will then call the action we previously discussed:
public ActionResult Details(string id)
{
using (var context = contextFactory.Create())
{
var profile = context.UserProfiles.FirstOrDefault(x => x.Id == id);
return View(profile);
}
}
I have my context built using a factory hence why I do it this way, so it can be tested etc. However, you can just use the pure context as long as you are handling its disposed.