I am trying to figure out how to find user role name in identity framework.
I have such configuration that there will be only one role assigned to a user.
So, I tried using
public string GetUserRole(string EmailID, string Password)
{
var user = await _userManager.FindAsync(EmailID, Password);
var roleid= user.Roles.FirstOrDefault().RoleId;
}
But what I get is just RoleId and not RoleName.
Can anyone help me to find the RoleName for the user?
In your code, user object represents the AspNetUsers table which has a navigation property Roles which represents the AspNetUserInRoles table and not the AspNetRoles table.
So when you try to navigate through
user.Roles.FirstOrDefault()
it gives you the AspNetUserInRoles which stores UserId and RoleId.
Instead you could try UserManger's GetRoles method which will return you List<string> of roles user is assigned. But as you mentioned it will be only one role hence you can take first value from the result of GetRoles method.
Your function should be similar to one given below:
public async string GetUserRole(string EmailID, string Password)
{
var user = await _userManager.FindAsync(EmailID, Password);
string rolename = await _userManager.GetRoles(user.Id).FirstOrDefault();
return rolename;
}
If you has a list of users you has to do this:
var usuarioManager = Request.GetOwinContext().GetUserManager<UserManager<Usuario>>();
var roleManager = Request.GetOwinContext().Get<RoleManager<IdentityRole>>();
var roles = roleManager.Roles.ToList();
var usuarios = usuarioManager.Users.Include(x => x.Roles).ToList();
usuarios.ForEach((x) =>
{
if (x.Roles.Any())
{
var roleDb = roles.FirstOrDefault(r => r.Id == x.Roles.FirstOrDefault().RoleId);
if (roleDb != null)
x.RoleName = roleDb.Name;
}
});
Related
I am working on a system that should allow users to register by a unique username, or unique email. Register by both is possible as well.
I am using the default identity pages with some modifications, here is a sample of the register code:
public class InputModel
{
//[Required] //Validated in server side based on role
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
//[Required] //Validated in server side based on role
[Display(Name = "Username")]
public string UserName { get; set; }
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
string username = Input.UserName ?? Input.Email;
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = username,
Email = Input.Email,
Name = Input.Name,
};
}
}
Basically, if the user entered a username, Username column will be Input.UserName.
if no username (only email), the Username = Input.Email, because obviously it cannot be empty. Now both username and email are equal as the default.
examples:
Username: a#a , Email: a#a , >> no username
Username: xyz , Email: null , >> no email
Username: abc , Email: a#b , >> user entered both username and email
For now, username is always unique and always required (required by identity not the user), but not the case for the email, it can be null as expected but its not unique, I added this line in the startup.cs for the uniqueness:
services.AddIdentity<IdentityUser, IdentityRole>(options => {
options.User.RequireUniqueEmail = true;
})
but now it cannot be null, it give this validation error:
Email '' is invalid.
Please try to implement a setter for Email that converts empty string to null
I added this line in the startup.cs for the uniqueness: options.User.RequireUniqueEmail = true;
but now it cannot be null, it give this validation error: Email '' is invalid.
In source code of UserManager<TUser>.CreateAsync method, we can find that it will call ValidateUserAsync method to valid user before saving the user.
var result = await ValidateUserAsync(user);
in code of ValidateUserAsync method, we can find it call another method ValidateAsync, like below.
var result = await v.ValidateAsync(this, user);
and in ValidateAsync method, if we configured RequireUniqueEmail to true, it will valid Email of current user by calling ValidateEmail method.
if (manager.Options.User.RequireUniqueEmail)
{
await ValidateEmail(manager, user, errors);
}
Go to definition of ValidateEmail method, we can easily find it will check if the email parameter is null or System.String.Empty, or if value consists exclusively of white-space characters.
// make sure email is not empty, valid, and unique
private async Task ValidateEmail(UserManager<TUser> manager, TUser user, List<IdentityError> errors)
{
var email = await manager.GetEmailAsync(user);
if (string.IsNullOrWhiteSpace(email))
{
errors.Add(Describer.InvalidEmail(email));
return;
}
if (!new EmailAddressAttribute().IsValid(email))
{
errors.Add(Describer.InvalidEmail(email));
return;
}
var owner = await manager.FindByEmailAsync(email);
if (owner != null &&
!string.Equals(await manager.GetUserIdAsync(owner), await manager.GetUserIdAsync(user)))
{
errors.Add(Describer.DuplicateEmail(email));
}
}
you can try to validate if the email or username already exist by using FindByNameAsync or FindByEmailAsync by using the UserManager. If the return is null then it means that the username/email doesn't exist yet.
Here is the sample approach for this:
//private readonly UserManager<IdentityUser> _userManager ;
var isUniqueUserName = await _userManager.FindByNameAsync(UserNameFromInput)==null;
var isUniqueEmail = await _userManager.FindByEmailAsync(EmailFromInput) == null;
if (!isUniqueUserName || !isUniqueEmail)
{
if(!isUniqueUserName)
ModelState.AddModelError("UserName", "UserName already exist.");
if(!isUniqueEmail)
ModelState.AddModelError("Email", "Email already exist.");
return View(registerViewModel);
}
I am using Asp.Net Identity for authentication and my requirement is after login currently I can access only User.Identity.Name which is username only.
Is there any way I can add more properties after login like
User.Identity.UserType
User.Identity.DepartmentId
Reason why I wanna use this to avoid Session to identify the user on Views.
First, you need add custom claim to user. For exmaple after creating user:
await _userManager.Value.AddClaimAsync(user, new Claim("UserType", "SomeType"));
Then create an extension method to read claim value:
public static string GetUserType(this IIdentity identity)
{
if (identity == null)
throw new ArgumentNullException(nameof(identity));
var claim = ((ClaimsIdentity)identity).FindFirst("UserType")?.Value;
return claim ?? string.Empty;
}
And you after login you can access user type:
var userType = User.Identity.GetUserType();
And here is a generic extension to get custom claim:
public static T Get<T>(this IIdentity identity, string propertyName)
{
if (identity == null)
throw new ArgumentNullException(nameof(identity));
var value = ((ClaimsIdentity)identity).FindFirst(propertyName)?.Value;
if (value == null)
return default(T);
var type = typeof(T);
if (type.IsEnum)
return (T)Enum.Parse(typeof(T), value);
return (T)Convert.ChangeType(value, typeof(T));
}
For example, you have an enum for UserType:
public enum UserType
{
Admin,
Editor,
User,
}
var userType = User.Identity.Get<UserType>("UserType");
var representativeId = User.Identity.Get<int>("DepartmentId");
I have the following problem. While using the following code below to change the user's current role i am getting an exception with the message like below:
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult Edit(User user, string role)
{
if (ModelState.IsValid)
{
var oldUser = DB.Users.SingleOrDefault(u => u.Id == user.Id);
var oldRoleId = oldUser.Roles.SingleOrDefault().RoleId;
var oldRoleName = DB.Roles.SingleOrDefault(r => r.Id == oldRoleId).Name;
if (oldRoleName != role)
{
Manager.RemoveFromRole(user.Id, oldRoleName);
Manager.AddToRole(user.Id, role);
}
DB.Entry(user).State = EntityState.Modified;
return RedirectToAction(MVC.User.Index());
}
return View(user);
}
Attaching an entity of type 'Models.Entities.User' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
Does anybody know a good solution to this problem ?
The problem is that your Manager and DB doesn't use the same DbContext. So when you send an user from the context of your DB to the Manager it will handle it as a "new" one - and then you cant remove it from the role. You have two ways to go here. The easiest is to get the User from your Manager.
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult Edit(User user, string role)
{
if (ModelState.IsValid)
{
// THIS LINE IS IMPORTANT
var oldUser = Manager.FindById(user.Id);
var oldRoleId = oldUser.Roles.SingleOrDefault().RoleId;
var oldRoleName = DB.Roles.SingleOrDefault(r => r.Id == oldRoleId).Name;
if (oldRoleName != role)
{
Manager.RemoveFromRole(user.Id, oldRoleName);
Manager.AddToRole(user.Id, role);
}
DB.Entry(user).State = EntityState.Modified;
return RedirectToAction(MVC.User.Index());
}
return View(user);
}
The more elegant way is to start using an DI-framework like AutoFac (https://code.google.com/p/autofac/wiki/MvcIntegration) and set your DbContext as InstancePerApiRequest.
builder.RegisterType<YourDbContext>().As<DbContext>().InstancePerApiRequest();
My roles are managed in the seed method of my DbMigrationsConfiguration class and I renamed it like this:
if (context.Roles.Any(r => r.Name == "User"))
{
var store = new RoleStore<IdentityRole>(context);
var manager = new RoleManager<IdentityRole>(store);
var role = manager.Roles.First(r => r.Name == "User");
role.Name = "NewNameForUser";
manager.Update(role);
}
In Identity Version 6.0.8
It's work for me.
if (role != currentrole)
{
_context.UserRoles.Remove(userIdData);
_context.SaveChanges();
userIdData.RoleId = aspnetroleId.Single();
userIdData.UserId = userId;
_context.UserRoles.Add(userIdData);
_context.SaveChanges();
}
I have a LINQ expression:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = s.LastActive, // DateTime, I want it to be a string filtered through my custom GetFriendlyDate method
}).ToList();
// convert all DateTimes - yuck!
foreach (var userViewModel in users) {
userViewModel.LastActive = DateFriendly.GetFriendlyDate(userViewModel.LastActiveDateTime);
}
This solution is working, but it feels wrong to
have to iterate over all users after getting them from the db, just to reformat a property on every single one
have a DateTime property on my ViewModel just so that it can later be converted to a string and never touched again
Is there any way I can use the GetFriendlyDate method directly within the query?
Possible solutions, worth to mention:
Have a getter property of your ViewModel, which would return transformed string, something like:
public string LastActive
{
get
{
return DateFriendly.GetFriendlyDate(LastActiveDateTime);
}
}
Though it not solves your problem with existing LastActiveDateTime column, transformation will be applied only at moment of usage (in your view, most likely - anyways if you will try use it somewhere latter in query, it will not work for reason you already know), so no need to iterate manually.
Create View, which will transform data on server side; so your data will already be returned in format you need, if you're using DBFirst, probably, it's easiest and fastest solution;
Finally, you can use ToList() twice, once before selecting new ViewModel() (or call AsEnumerable(), or find other way to materialize query). It will fetch data from database and will allow you perform any C#-side functions you want directly in query after ToList(). But, as mentioned before - it's about getting all data, which matched criteria up to ToList() - in most cases it's not appropriate solution.
And here is some additional readings:
How can I call local method in Linq to Entities query?
I tested it in LINQpad and it works.
It still kinda iterates over users (with LINQ) but you don't have to add DateTime property to your viewmodel class. Also you could convert collection of Users to collection of UserViewModel objects with Automapper. It would still iterate over users of course but you wouldn't see it.
Had to create some setup code of course because I don't have your database.
void Main()
{
var db = new List<User> {
new User { LastActive = DateTime.Now, Username = "Adam", Lastname = "Nowak" },
new User { LastActive = DateTime.Now.AddYears(1), Username = "Eve", Lastname = "Kowalska"}
};
// select only properties that you need from database
var users = db
.Select(user => new { Username = user.Username, LastActive = user.LastActive})
.Distinct()
.ToList();
var usersVM = from u in users
select new UserViewModel { Username = u.Username, LastActiveDateTime = DateFriendly.GetFriendlyDate(u.LastActive)};
usersVM.Dump();
}
class User
{
public DateTime LastActive;
public string Username;
public string Lastname;
};
class UserViewModel
{
public string Username;
public string LastActiveDateTime;
}
static class DateFriendly
{
public static string GetFriendlyDate(DateTime date)
{
return "friendly date " + date.Year;
}
}
And this outputs
Username LastActiveDateTime
Adam friendly date 2013
Eve friendly date 2014
There is no direct Concert.ToDate method available for LINQ. But you can try using the DateAdd method from the SqlFunctions class:
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => new
{
s.User.Username,
LastActive=SqlFunctions.DateAdd("d",0, s.LastActive)
})
.ToList().Select(s => new UserViewModel()
{
Username = s.Username,
LastActiveDateTime = s.LastActive
});
Wouldn't the following work?
var users = db.Relationships.Where(i => i.RelationshipId== relId)
.Select(s => s.User).Distinct().Select(s => new UserViewModel() {
Username = s.Username,
LastActiveDateTime = DateFriendly.GetFriendlyDate(s.LastActive)
}).ToList();
I have overridden the membership methods to create a custom membership.
In the account model I've overridden the method CreateUser:
public override MembershipUser CreateUser(string username, string password,
string email, string passwordQuestion, string passwordAnswer,
bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(
username, password, true);
OnValidatingPassword(args);
if (args.Cancel)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
{
status = MembershipCreateStatus.DuplicateEmail;
return null;
}
MembershipUser u = GetUser(username, false);
if (u == null)
{
UserRepository _user = new UserRepository();
// Here I call my new method which has fields I've created in the
// User table; I'm using entity framework.
_user.CreateUser(username, password, email);
status = MembershipCreateStatus.Success;
return GetUser(username, false);
}
else
{
status = MembershipCreateStatus.DuplicateUserName;
}
return null;
}
public MembershipUser CreateUser(string username, string password,
string email)
{
using (CustomMembershipDB db = new CustomMembershipDB())
{
User user = new User();
user.UserName = username;
user.Email = email;
user.PasswordSalt = CreateSalt();
user.Password = CreatePasswordHash(password, user.PasswordSalt);
user.CreatedDate = DateTime.Now;
user.IsActivated = false;
user.IsLockedOut = false;
user.LastLockedOutDate = DateTime.Now;
user.LastLoginDate = DateTime.Now;
//Generate an email key
// user.NewEmailKey = GenerateKey();
db.AddToUsers(user);
db.SaveChanges();
//send mail
// SendMail(user);
return GetUser(username);
}
}
Now here I need to add more two fields like first name and last name but how can I pass it to the above method?
As the override method CreateUser will give me an error if I add parameters like firstname and last name into it :(
You need to implement Custom Membership User. Here is a sample implementation:
http://msdn.microsoft.com/en-us/library/ms366730.aspx
Also take a look at this thread:
Implement Custom MembershipUser and Custom MembershipProvider
Implementing Custom MembershipUser
You can leave the AspNetUsers table intact, and create a new table to store the extra information (linked to the original one). This way you'll not break any existing code in the membership provider.
The original AspNetUsers table has:
[Id],[Email],[EmailConfirmed],[PasswordHash],[SecurityStamp],[PhoneNumber],[PhoneNumberConfirmed],[TwoFactorEnabled],[LockoutEndDateUtc],[LockoutEnabled],[AccessFailedCount],[UserName]
The new table to store extra data can have for example:
[Id],[UserId][DateOfBirth],[Biography], etc.
Where [UserId] is the foreign key to AspNetUsers table.
One advantage of this approach, is that you can create multiple types of users, each storing its related info in a different table, while common data is still in the original table.
How to:
First update the RegisterViewModel to contain the extra data you want.
Update the Register method in the Account Controller, here's the original method updated with the code to insert new profile data:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Start of new code ----------------------------------------
// Get Id of newly inserted user
int userId = user.Id; // Get Id of newly inserted user
// Create a profile referencing the userId
AddUserProfile(userId, model);
// End of new code ----------------------------------------
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
return View(model);
}
Implement the AddUserProfile(int userId, RegisterViewModel model) method as you wish. You'll collect the extra data from the model object along with the userId and save the new profile object in the DB.
Make a class that inherits from MembershipProvider and implement methods that are identical by just calling the SqlMembershipProvider but change others that you want a different Functionality.
Take a look at this article SQLite 3.0 Membership and Role Provider for ASP.NET 2.0
UPDATE:
The Membership system in ASP.NET was designed to create a standardized
API for working with user accounts, a task faced by many web
applications (refer back to Part 1 of this article series for a more
in-depth look at Membership). While the Membership system encompasses
core user-related properties - username, password, email address, and
so on - oftentimes additional information needs to be captured for
each user. Unfortunately, this additional information can differ
wildly from application to application.
Rather than add additional user attributes to the Membership system,
Microsoft instead created the Profile system to handle additional user
properties. The Profile system allows the additional, user-specific
properties to be defined in the Web.config file and is responsible for
persisting these values to some data store.
Reference: Examining ASP.NET's Membership, Roles, and Profile - Part 6
This is how I have accomplished somthing like this. I added event onCreatedUser to CreateUserWizard and when you press CreateUser button it loads method
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
MembershipUser mu = Membership.GetUser(CreateUserWizard1.UserName);
int idOfInsertedUser = (int)mu.ProviderUserKey;
TextBox tb1 = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("FirstName";
string firstName= tb1.Text;
TextBox tb2 = (TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("LastName";
string lastName= tb2.Text;
// now you have values of two more fields, and it is time to call your Database methods for inserting them in tables of choice...
}