EF update ApplicationUser(inherit from IdentityUser) with many to many relationship collections - asp.net

Customize Identity user object contains list object region as with code. When user is registering he can request what are the regions he can work on and what is his role. Once user register email message goes to admin to review user and approve registration request. Until admin approved user is lock. Amin can modify the user region selections and request role if necessary. So Problem come in here. How could I update application user and his regions and role? I tried below but it gave me an exception. Do I need to first update application user then retrieve it and add regions and roles make a send update?( many DB calls)
The property 'Regions' on type 'ApplicationUser' is not a primitive or complex property. The Property method can only be used with primitive or complex properties. Use the Reference or Collection method.)
ApplicationUser
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
//Extended Properties
public DateTime? BirthDate { get; set; }
//Key Mappings
public virtual ICollection<Region> Regions { get; set; }
public virtual string DisplayName { get; set; }
public virtual string UserAccountApproverId { get; set; }
public virtual ApplicationUser UserAccountApprover { get; set; }
}
Regions
public class Region:AuditableBase
{
public string RegionCode { get; set; }
public string Description { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUsers { get; set; }
}
Code snippet for Update ApplicationUser
public int ApproveNewUser(UserModel userModel)
{
try
{
ApplicationUser user = new ApplicationUser()
{
Id = userModel.Id,
UserName = userModel.EmailAddress,
LockoutEnabled = false
};
_ctx.Users.Attach(user);
var entry = _ctx.Entry(user);
entry.Property(e => e.LockoutEnabled).IsModified = true;
if (userModel.CheckedRegionsUpdated)
{
AddRegionsToUser(userModel.SelectedRegions, user);
entry.Property(e => e.Regions).IsModified = true;
}
return _ctx.SaveChanges();
}
catch (OptimisticConcurrencyException ex)
{
var objectContext = ((IObjectContextAdapter)_ctx).ObjectContext;
objectContext.Refresh(RefreshMode.ClientWins, _ctx.Users);
return _ctx.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
}
private void AddRegionsToUser(IList<Region> regionsToAdd, ApplicationUser appUser)
{
appUser.Regions = new List<Region>();
var regionsIds = regionsToAdd.Select(x => x.Id).ToArray<int>();
List<Region> regionssFromDb =
this._ctx.Regions.Where(rg => regionsIds.Contains(rg.Id)).ToList();
foreach (Region region in regionssFromDb)
{
appUser.Regions.Add(region);
}
}

Here is link it is gave me an answer.
Many to Many Relationships not saving
Thanks Lot to Slauma

Related

Limit fields available for editing using Razor pages and Entity framework CORE with ViewModel

User class
public class MyUser {
public string Forename { get; set; }
public string Surname { get; set; }
public string PWD { get; set; }
}
Password edit page
public class EditPasswordModel : PageModel {
[BindProperty]
public UserMain objRecord { get; set; }
public async Task<IActionResult> OnGetAsync() {
objRecord = await _context.MyUser
.SingleOrDefaultAsync(m => m.UserID == 666);
return Page();
}
public async Task<IActionResult> OnPostAsync() {
_context.Attach(objRecord).State = EntityState.Modified;
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
In this password changing page, I only want to update the PWD field.
The option to reload the record from the table in OnPostAsync() then copy everything over into objRecord except for PWD works, but feels wrong and wasteful.
I've used ViewModels to extend what's brought into a View, combining classes to be used in the View for example, but is there a way to use a ViewModel to limit which fields are available?

Extended IdentityUser not saving

Allright! So I extended my IdentityUser but it is not saving my Users anymore. The code samples can be viewed below. I am trying to seed my database with an admin user but it is not being stored. I have tried running a debugger on my seeds, but it doesn't trigger on anything. So I'm kinda lost. So, here is the code.
My extended User Class:
public class User : IdentityUser, IBaseEntity
{
public virtual ICollection<TimeLogEntry> TimeLogEntries { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsRemoved { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
return userIdentity;
}
}
Then ofcourse the Context that inherits from the IdentityDbContext:
public class Context : IdentityDbContext<User>, IContext
{
public Context()
: base("MyConnection", throwIfV1Schema: false)
{
Configuration.ProxyCreationEnabled = false;
}
// DBSETS
public static Context Create() => new Context();
// MODELBUILDER
}
and finaly my seed:
protected override void Seed(Context context)
{
// Launch debugger on seeds
//if (System.Diagnostics.Debugger.IsAttached == false)
// System.Diagnostics.Debugger.Launch();
SeedUsers(context);;
}
private void SeedUsers(Context context)
{
var manager = new UserManager<User>(new UserStore<User>(context));
var adminUser = new User
{
UserName = "Admin",
Email = "admin#test.com"
};
if (!manager.Users.Any())
{
manager.Create(adminUser, "Admin");
}
}
I hope someone can help me with this!
The Seed() that is inside your initializer will only run when your database is recreated - for instance when you are using DropCreateDatabaseIfModelChanges.
If you use migrations (MigrateDatabaseToLatestVersion initializer), there is another Seed() that runs every time you apply the migration using update-database.
http://blog.oneunicorn.com/2013/05/28/database-initializer-and-migrations-seed-methods/

Entity Framework Code First relationship save Issue

Having some troubles with saving new Employee to database.
Project Model(useless fiels removed):
public class Project{
public virtual ICollection<EmployeeManager.Employee> Employees { get; set; }
}
EmployeeManager.Employee Model:
public class Employee{
public virtual ApplicationUser User { get; set; }
public virtual ProjectsManager.Project ProjectObj { get; set; }
}
Function:
public async Task AddNewEmployeeAsync(string projectId, string email, string fullname){
var projectInfo = await GetByIdAsync(projectId);
var userInfo = new ApplicationUser();
if (await _context.Users.AnyAsync(_ => _.Email == email)){
userInfo = await _context.Users.FirstAsync(_ => _.Email == email);
}
else{
....
userInfo = user;
}
projectInfo.Employees.Add(new EmployeeManager.Employee{
User = userInfo
});
_context.Entry(projectInfo).State = EntityState.Modified;
_context.SaveChanges();
}
And after all that, i have no changes in database.
Why?
If i use _context.Employees.Add() -> it creates new project entity in database.
For Entity Framework to create the correct relationships (one to many) without configuration (FluentApi) you need to make a couple of changes to Employee class
public class Employee
{
public virtual ApplicationUser User { get; set; }
public int ProjectId { get; set; } //foreign key maybe a string as defined in your Project class
public virtual ProjectsManager.Project Project { get; set; }
}

IdentityUser: "Name cannot be null or empty"

I've been trying to inherit IdentityUser to make my own class which uses Identity and still writes to my database and I keep getting this error when I try to call my registration post method:
{
"$id": "1",
"Message": "The request is invalid.",
"ModelState": {
"$id": "2",
"": [
"Name cannot be null or empty."
]
}
}
I tried number of things, but nothing works.For example when I try to set UserName field of IdentityUser it says it's impossible because it doesn't exist in the context.
The important thing to mention would be that I am using ADO.NET database first model for the account :)
This is the class:
public partial class Account :IdentityUser
{
public Account()
{
this.Families = new HashSet<Family>();
}
public long idAccount { get; set; }
public string email { get; set; }
public string password { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public virtual ICollection<Family> Families { get; set; }
}
This is my authentication repository class:
public class AuthRepository :IDisposable
{
private DAKPAKEntities _ctx;
private UserManager<Account> _userManager;
public AuthRepository()
{
_ctx = new DAKPAKEntities();
_userManager = new UserManager<Account>(new UserStore<Account>(_ctx));
}
public async Task<IdentityResult> RegisterUser(Account userModel)
{
Account user = new Account
{
firstName = userModel.firstName,
lastName = userModel.lastName,
email=userModel.email
};
var result = await _userManager.CreateAsync(user,userModel.password);
return result;
}
}
And this is the controller that calls the repository:
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public async Task<IHttpActionResult> Register(Account userModel)
{
IdentityResult result = await _repo.RegisterUser(userModel);
IHttpActionResult errorResult = GetErrorResult(result);
if (errorResult != null)
{
return errorResult;
}
return Ok();
}
I am new to this, and am out of options to try. I did almost everything that's usually suggested for this type of error, please help :)
It looks like you haven't done everything that is required in order to change the way ASPNet Identity stores the user information in the database.
Suggest you start here: Overview of Custom Storage Providers for ASP.NET Identity

ASP.NET Identity, multiple users with the same username, but belonging to different companies. Override FindAsync?

I'm new with MVC and EF, and I'm working on a project where I use Identity for user and role management. In the predefined login function from Visual Studio there's a function named FindAsync, used to get the user by username and password. In my case there can be more than one user with the same username and password, but they will belong to different companies. I would like to know the best approach; should I override the FindAsync function (and other functions if necessary) and add another parameter, or should I implement user and role management myself, without Identity? Or is there another, even better solution?
If the best solution is to override the function, then I'm a bit unsure of how I should do it, since the source code for UserManager is not yet open.
Below you can find the login function and takeouts from my models:
public async Task<ActionResult> Login(Login model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public class Company
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class User : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
[Required]
public virtual int CompanyId { get; set; }
[Required]
public virtual Company Company { get; set; }
}
Any help is more than welcome!
I think I would create my own implementation of UserManager and create a method like FindByCompanyAsync(string userName, string password, string companyName)
In this method you could search the user by getting all users of the UserManager and use a LINQ expression to filter it on username and company name.
I have found this thread, which addresses the same problem as mine:
How to implement Multi-tenant User Login using ASP.NET Identity
It seesms like a good approach, and I will try to make my own implementation of UserStore just like he did.
I sent in the companyId to the UserStore and overrode the FindByNameAsync function to filter on both userName and companyId. What I don't like is that I coulden't use the GetUserAggregateAsync function since it's private. Is there any other disadvantages with my solution?
public class UserStore : UserStore<User>
{
public int companyId = -1;
public UserStore(DbContext context, int companyId)
: base(context)
{
this.companyId = companyId;
}
public override Task<User> FindByNameAsync(string userName)
{
if (companyId < 0)
{
throw new Exception("There is no companyId associated with the UserStore.");
}
return QueryableExtensions.FirstOrDefaultAsync<User>(QueryableExtensions.Include<User, ICollection<IdentityUserLogin>>(QueryableExtensions.Include<User, ICollection<IdentityUserClaim>>(QueryableExtensions.Include<User, ICollection<IdentityUserRole>>(this.Users, (User u) => u.Roles), (User u) => u.Claims), (User u) => u.Logins), (User u) => u.UserName.ToUpper() == userName.ToUpper() && u.CustomerId == this.companyId);
}
}

Resources