Entity Framework - list AspNetUsers with roles on admin page - asp.net

I am working on an admin page where someone can go in and make some changes in the database. I want them to be able to view all non-admin users and edit them as well. After digging around online, I have figured out how to list out the users by email address and I have a Roles column. But the value showing up under Roles is not quite correct. It is the UserID in the AspNetUserRoles table.
Here is my AdminController code that is grabbing the users and roles:
using (var db = new ApplicationDbContext())
{
model.AppUsers = db.Users
.Include("Roles")
.Select(u =>
new ManagerUserViewModel
{
UserID = u.Id,
UserName = u.UserName,
Email = u.Email,
Roles = u.Roles.ToList()
}
).ToList();
};
return View(model);
And here is my ManagerUserViewModel:
public class ManagerUserViewModel
{
public String UserID { get; set; }
public String Email { get; set; }
public String UserName { get; set; }
public IEnumerable<IdentityUserRole> Roles { get; set; }
}
Let me know if any other code is needed.
Here is what I am getting output on the page now:

Can you access the UserManager instance? If so it has a GetRoles function which takes a userId and returns a List of roles.
using (var db = new ApplicationDbContext())
{
model.AppUsers = db.Users
.Include("Roles")
.Select(u =>
new ManagerUserViewModel
{
UserID = u.Id,
UserName = u.UserName,
Email = u.Email
}
).ToList();
};
foreach(var user in model.AppUser){
user.Roles = userManager.GetRoles(user.UserId);
}
You'll need to update your viewmodel
public IEnumerable<String> Roles { get; set; }
If you don't have the UserManager you should be able to get it from the OwinContext
var userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();

Related

Asp.net Mvc and Core , How it works SelectListItem

I have user edit problem in my Edit actions.. when it comes to departmentDropdownlist
When I click a user to edit let say he/she belongs to administration, I want then show
administration in dropdownlist and offcourse below that all departments in the same dropdownlist.
But right now when I click Edit It shows me all departments in Id order.. like I want to add a new user.
I have role dropdown list. tha's woking fine. If the user is Employee .. it shows me first Employee then the rest of Role list.. If the user is Admin , then it shows me Admin
then below that the rest of role list. But when it comes to department it shows me the first departmentname which DeptId = 1 and so on. I tried in both Asp.Net Mvc and Core. Here is my EditUser and EditUserViewModel
[HttpGet]
public async Task<IActionResult> EditUser(string id)
{
EditUserViewModel model = new EditUserViewModel();
model.ApplicationRoles = roleManager.Roles.Select(r => new SelectListItem
{
Text = r.Name,
Value = r.Id
}).ToList();
model.DepartmentNames = context.Departments.Select(s => new SelectListItem
{
Text = s.DeptName,
Value = s.DeptId.ToString()
}).ToList();
if (!String.IsNullOrEmpty(id))
{
ApplicationUser user = await userManager.FindByIdAsync(id);
if (user != null)
{
model.Name = user.Name;
model.Email = user.Email;
model.ApplicationRoleId = roleManager.Roles.Single(r => r.Name == userManager.GetRolesAsync(user).Result.Single()).Id;
model.DeptId = context.Departments.Single(r => r.DeptName == context.Sites.....??????); //How to do here
// ViewBag.DeptId = new SelectList(context.Departments, "DeptId", "DeptName", model.DeptId); // Even like this , shows the first id departmentname
}
}
return PartialView("_EditUser", model);
}
My EditUserViewModel
public class EditUserViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<SelectListItem> ApplicationRoles { get; set; }
public string ApplicationRoleId { get; set; }
public List<SelectListItem> DepartmentNames { get; set; }
public int DeptId { get; set; } // I have DeptId too in my AspNetUser table
}
Since the User table has DepartmentId, I don't know why you don't assign model.DeptId to it.
So instead of
model.DeptId = context.Departments.Single(r => r.DeptName == context.Sites.....??????);
You can just assign the DepartmentId from the user table, i.e.,
model.DeptId = user.DepartmentId;
On Razor when you build the dropdown using TagHelper, it should select the correct dropdown option value based on the id.
<select class="form-control" asp-for="DeptId" asp-items="DepartmentNames"></select>

MVC one-to-many database not updated

I have added an ApplicationCompany class to my MVC project like this:
public class ApplicationCompany
{
public Guid Id { get; set; }
public String Name { get; set; }
public virtual List<ApplicationUser> Users { get; set; }
public ApplicationCompany()
{
Id = Guid.NewGuid();
Users = new List<ApplicationUser>();
}
}
In the ApplicationUser class I have added:
public virtual ApplicationCompany Company { get; set; }
Now, when I debug an instance of ApplicationUser, I can see it has the Company property, and I can see the ApplicationCompany table in my database. When a user registers, I'd like to add a Company linked to them. I am doing the following but it is not saving the company:
using (var context = new ApplicationDbContext())
{
if (ModelState.IsValid)
{
var user = new ApplicationManager
{
UserName = model.Email,
Email = model.Email
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var company = new ApplicationCompany();
user.Company = company;
context.SaveChanges();
...
}
}
}
Any ideas why?
The DbContext instance you use into the if (result.Succeeded) block doesn't know anything about the user instance you just set before calling context.SaveChanges(). Modify your code by attaching user instance into the context or just changing the state of the user to EntityState.Modified.
if (result.Succeeded)
{
var company = new ApplicationCompany();
user.Company = company;
context.Entry(user).State = EntityState.Modified;
context.Entry(company).State = EntityState.Added;
context.SaveChanges();
}
Edit 1:
Attaching an entity of type 'ApplicationUser' failed because another entity of the same type already has the same primary key value
Because another context own the user instance, then you must instantiate the DbContext into the if block (don't forget to suppress the root instance you create at the start of your method) like this:
if (result.Succeeded)
{
using (var context = new ApplicationDbContext())
{
var company = new ApplicationCompany();
user.Company = company;
context.Entry(user).State = EntityState.Modified;
context.Entry(company).State = EntityState.Added;
context.SaveChanges();
}
}
Edit 2: Overriding CreateAsync method in ApplicationUserManager class
The better approcach, IMHO, is to oveeride CreateAsync method in ApplicationUserManager class. by doing that you don't need to add the company updates into your controller's action.
public class ApplicationUserManager : UserManager<ApplicationUser>
{
// ...
public override async Task<IdentityResult> CreateAsync(ApplicationUser user, string password)
{
var company = new ApplicationCompany();
user.Company = company;
return await base.CreateAsync(user, password);
}
}
The problem is that, you are using a different DbContext object to insert the User than the one you are using to insert the company. You should be using the same DbContext object.
Change your UserManager.CreateAsync method to accept the DbContext object and use it there to add the new user
public Task<SomeDTO> CreateAsync(ApplicationDbContext db, ApplicationUser user,
string password)
{
//to do : Set the password
//Use db to add the new User
db.ApplicationUsers.Add(user);
await db.SaveChangesAsync();
// to do : return something
}
And when you call it from your action method, make sure to pass the DbContext.
var result = await UserManager.CreateAsync(context,user, model.Password);
That should fix your problem.

How to use new MVC5 Authentication with existing database

I've looked through the current literature but I'm struggling to workout exactly how to make the new IdentityStore system work with your own database.
My database's User table is called tblMember an example class below.
public partial class tblMember
{
public int id { get; set; }
public string membership_id { get; set; }
public string password { get; set; }
....other fields
}
currently users login with the membership_id which is unique and then I use the id throughout the system which is the primary key. I cannot use a username scenario for login as its not unique enough on this system.
With the examples I've seen it looks like the system is designed to me quite malleable, but i cannot currently workout how to get the local login to use my tblmember table to authenticate using membership_id and then I will have access the that users tblMember record from any of the controllers via the User property.
http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx
Assuming you are using EF, you should be able to do something like this:
public partial class tblMember : IUserSecret
{
public int id { get; set; }
public string membership_id { get; set; }
public string password { get; set; }
....other fields
/// <summary>
/// Username
/// </summary>
string UserName { get { return membership_id; set { membership_id = value; }
/// <summary>
/// Opaque string to validate the user, i.e. password
/// </summary>
string Secret { get { return password; } set { password = value; } }
}
Basically the local password store is called the IUserSecretStore in the new system. You should be able to plug in your entity type into the AccountController constructor like so assuming you implemented everything correctly:
public AccountController()
{
var db = new IdentityDbContext<User, UserClaim, tblMember, UserLogin, Role, UserRole>();
StoreManager = new IdentityStoreManager(new IdentityStoreContext(db));
}
Note the User property will contain the user's claims, and the NameIdentifier claim will map to the IUser.Id property in the Identity system. That is not directly tied to the IUserSecret which is just a username/secret store. The system models a local password as a local login with providerKey = username, and loginProvider = "Local"
Edit: Adding an example of a Custom User as well
public class CustomUser : User {
public string CustomProperty { get; set; }
}
public class CustomUserContext : IdentityStoreContext {
public CustomUserContext(DbContext db) : base(db) {
Users = new UserStore<CustomUser>(db);
}
}
[TestMethod]
public async Task IdentityStoreManagerWithCustomUserTest() {
var db = new IdentityDbContext<CustomUser, UserClaim, UserSecret, UserLogin, Role, UserRole>();
var manager = new IdentityStoreManager(new CustomUserContext(db));
var user = new CustomUser() { UserName = "Custom", CustomProperty = "Foo" };
string pwd = "password";
UnitTestHelper.IsSuccess(await manager.CreateLocalUserAsync(user, pwd));
Assert.IsTrue(await manager.ValidateLocalLoginAsync(user.UserName, pwd));
CustomUser fetch = await manager.Context.Users.FindAsync(user.Id) as CustomUser;
Assert.IsNotNull(fetch);
Assert.AreEqual("Custom", fetch.UserName);
Assert.AreEqual("Foo", fetch.CustomProperty);
}
EDIT #2: There's also a bug in the implementation of IdentityAuthenticationmanager.GetUserClaims that is casting to User instead of IUser, so custom users that are not extending from User will not work.
Here's the code that you can use to override:
internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
internal const string DefaultIdentityProviderClaimValue = "ASP.NET Identity";
/// <summary>
/// Return the claims for a user, which will contain the UserIdClaimType, UserNameClaimType, a claim representing each Role
/// and any claims specified in the UserClaims
/// </summary>
public override async Task<IList<Claim>> GetUserIdentityClaims(string userId, IEnumerable<Claim> claims) {
List<Claim> newClaims = new List<Claim>();
User user = await StoreManager.Context.Users.Find(userId) as IUser;
if (user != null) {
bool foundIdentityProviderClaim = false;
if (claims != null) {
// Strip out any existing name/nameid claims that may have already been set by external identities
foreach (var c in claims) {
if (!foundIdentityProviderClaim && c.Type == IdentityProviderClaimType) {
foundIdentityProviderClaim = true;
}
if (c.Type != ClaimTypes.Name &&
c.Type != ClaimTypes.NameIdentifier) {
newClaims.Add(c);
}
}
}
newClaims.Add(new Claim(UserIdClaimType, userId, ClaimValueTypes.String, ClaimsIssuer));
newClaims.Add(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String, ClaimsIssuer));
if (!foundIdentityProviderClaim) {
newClaims.Add(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String, ClaimsIssuer));
}
var roles = await StoreManager.Context.Roles.GetRolesForUser(userId);
foreach (string role in roles) {
newClaims.Add(new Claim(RoleClaimType, role, ClaimValueTypes.String, ClaimsIssuer));
}
IEnumerable<IUserClaim> userClaims = await StoreManager.Context.UserClaims.GetUserClaims(userId);
foreach (IUserClaim uc in userClaims) {
newClaims.Add(new Claim(uc.ClaimType, uc.ClaimValue, ClaimValueTypes.String, ClaimsIssuer));
}
}
return newClaims;
}

entity code first many-to-many relations saving

I have spent a day with this problem. There are these entitites:
public class UserRole
{
[Key]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Privilege> Privileges { get; set; }
}
public class Privilege
{
[Key]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<UserRole> Roles { get; set; }
}
And this configuration in context:
modelBuilder.Entity<UserRole>().HasMany<Privilege>(a => a.Privileges).WithMany(a => a.Roles)
.Map(m =>
{
m.MapLeftKey("RoleId");
m.MapRightKey("PrivilegeId");
m.ToTable("RolesPrivilegesMapping");
});
And this test code:
UserRole role;
//get role entity from context
//write privileges count
using (VaultContext context = new VaultContext(connectionString))
{
role = context.UserRoles.Where(a => a.Name == "Users").FirstOrDefault();
Console.WriteLine(role.Privileges.Count.ToString()); //writes 0
}
//make offline changes
//the privilege already exists in database
role.Privileges.Add(new Privilege() { Id = 1, Name = "Login" });
//save changes to database
using (VaultContext context = new VaultContext(connectionString))
{
List<Privilege> privileges = new List<Privilege>();
foreach (Privilege p in role.Privileges)
{ privileges.Add(p); }
foreach (Privilege p in privileges)
{ context.Privileges.Attach(p); }
role.Privileges.Clear();
context.UserRoles.Attach(role);
context.Entry(role).State = System.Data.EntityState.Modified;
foreach (Privilege p in privileges)
{ role.Privileges.Add(context.Privileges.Find(p.Id)); }
context.SaveChanges();
}
//reload the role entity
//write privileges count
using (VaultContext context = new VaultContext(connectionString))
{
Console.WriteLine(context.UserRoles.Where(a => a.Name == "Users").FirstOrDefault().Privileges.Count.ToString()); //writes 0
}
The Users role and Login privilege is already in database. So I try to add privilege Login to Users role.
But relations are not saved in database - the RolesPrivilegesMapping table doesn't contain record with id corresponding with Users role and Login privilege.
If I simply add directly - it works, but it is not my target:
new UserRole()
{
Name = "Administrators",
Privileges = new List<Privilege>()
{
new Privilege() { Name = "Login" }
}
}
Could anyone advice me how to add only many-to-many relations if all entities exist in database?
I'm using EF 5.0. I have tried it with EF 4.3 with same result.
If all your wanting to do is attach existing entries in your database, could you do somethign a bit simpler?
using( VaultContext context = new VaultContext( ) )
{
role = context.UserRoles.FirstOrDefault(a => a.Name == "Users");
Privilege p = context.Privileges.FirstOrDefault(pr => pr.Name == "Login");
role.Privileges.Add(p);
context.SaveChanges();
}
Thank you.
This is my solution: (role is changed offline)
UserRole dbRole = context.UserRoles.Find(role.Id);
dbRole.Privileges.Clear();
foreach (Privilege p in role.Privileges)
{
dbRole.Privileges.Add(context.Privileges.Find(p.Id));
}
context.Entry(dbRole).State = System.Data.EntityState.Modified;

MCV3 Entity Framework 4 Code First: Adding Sub Objects to the db

I am trying to teach my self MVC3 and EF4 using code first and the DbContext generator, so forgive me if this is a silly question.
Basically I have a user class, and an email class; this is because i want each user to be able to have multiple email addresses. the classes are set up like this:
public class User
{
[Key]
public int Id { get; set; }
public string User_Name { get; set; }
public string Password { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public virtual ICollection<Email> Emails { get; set; }
}
public class Email
{
[Key]
public int Id { get; set; }
public string Address { get; set; }
public User User { get; set; }
}
I am happily manipulating the user class using the CRUD methods build by MVC3 and inserting users programmatically to "seed" the db with test data, the latter i am doing like so by overriding the Seed method in the DropCreateDatabaseAlways class like so:
public class dbInitializer : DropCreateDatabaseAlways<UserContext>
{
protected override void Seed(UserContext context)
{
var Users = new List<User>
{
new User { User_Name = "uname",
Password = "pword",
First_Name = "fname",
Last_Name = "sname",
}
};
Users.ForEach(u => context.Users.Add(u));
}
}
Now i would also like to add so email addresses, and because of the way i set up my classes code first obviously realises that each user can have multiple email addresses and each email addresses can belong only to one user because when creating a new user or email object the intellisense (VS10) presents me with Emails and User properties that are not actually part of either class.
My question is this: How do i add an email address to a user as its created, and how do i add an email address to a user that has been created previously?
Add emails to user as it is created:
protected override void Seed(UserContext context)
{
var Users = new List<User>
{
new User { User_Name = "uname",
Password = "pword",
First_Name = "fname",
Last_Name = "sname",
Emails = new[]
{
new Email { Address = "email1#domain.tld" },
new Email { Address = "email2#domain.tld" },
}
}
};
Users.ForEach(u => context.Users.Add(u));
}
To add an email to a previously created user, you first need a reference to the user:
var user = Users.First();
user.Emails.Add(new Email { Address = "email3#domain.tld" });
Reply to comments:
new[] is shorthand for new Email[] (new Email array).
Technically Eranga's answer is a little more flexible. In mine, since arrays are fixed length, you can't add an Email to the Emails collection after it has been initialized as an array. You would need to either use List<Email> as in Eranga's answer, or convert to a list like so before invoking .Add():
user.Emails.ToList().Add(new Email { Address = "email3#domain.tld" });
I prefer arrays when possible because they are easier to type, less verbose in the code, and I generally add everything in the object initializer, so I don't need to add it again later.
That said, after you save the User and get it back out of the db, your Emails collection property will not be a fixed-length array, and can be added to without having to invoke .ToList().
Adding Email to a new user
protected override void Seed(UserContext context)
{
var Users = new List<User>
{
new User { User_Name = "uname",
Password = "pword",
First_Name = "fname",
Last_Name = "sname",
Emails = new List<Email> { new Email { Addess = "foo#bar.baz" } }
}
};
Users.ForEach(u => context.Users.Add(u));
}
Similarly for existing user.
user.Emails.Add(new Email { Addess = "foo#bar.baz" });
user.Emails.Add will issue a database request to load the Email collection (ie. Lazy Loading). Alternate way to do this is
var email = new Email { Addess = "foo#bar.baz", User = user };
context.Emails.Add(email);

Resources