I am fairly new to MVC5/ASP.NET Identity and I have found an issue that has stumped me a bit.
I am writing a small form for my ASP.NET MVC5 application that will allow an admin user (member of the Admins role) to review the users that have signed up to the site and edit the details of and assign roles to those users. When the form is submitted, if a role has been assigned to the user the UserManager.AddToRole method is called.
I noticed after this that once this is done, that user is then unable to log into the application. Looking in the database, it appears when AddToRole is called, the PasswordHash field is set to null. Is this normal and if not how to I get around this issue?
For reference my relevant code is below
Controller
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")]
public ActionResult Details(EditUserViewModel model)
{
model.Save();
return RedirectToAction("List", "Account");
}
Relevant view models
public class EditUserViewModel
{
public string UserId { get; set; }
public string UserName { get; set; }
[Required]
[StringLength(255)]
[Display(Name = "Email address")]
public string Email { get; set; }
[StringLength(255)]
[Display(Name = "First name")]
public string FirstName { get; set; }
[StringLength(255)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[StringLength(255)]
[Display(Name = "Mobile Number")]
public string MobileNumber { get; set; }
public IList<EditUserRolesViewModel> UserRoles { get; set; }
public void Save()
{
using (ApplicationDbContext context = new ApplicationDbContext())
{
ApplicationUser user = new ApplicationUser()
{
Id = this.UserId,
UserName = this.UserName,
Email = this.Email,
FirstName = this.FirstName,
LastName = this.LastName,
MobileNumber = this.MobileNumber
};
context.Users.Attach(user);
context.Entry(user).Property(x => x.Email).IsModified = true;
context.Entry(user).Property(x => x.FirstName).IsModified = true;
context.Entry(user).Property(x => x.LastName).IsModified = true;
context.Entry(user).Property(x => x.MobileNumber).IsModified = true;
context.SaveChanges();
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
foreach (var row in this.UserRoles)
{
if (row.RowChanged)
{
if (row.RoleAssigned)
{
UserManager.AddToRole(this.UserId, row.RoleName);
}
else
{
UserManager.RemoveFromRole(this.UserId, row.RoleName);
}
}
}
}
}
}
public class EditUserRolesViewModel
{
public string RoleId { get; set; }
[Display(Name = "Role name")]
public string RoleName { get; set; }
[Display(Name = "Assigned")]
public bool RoleAssigned { get; set; }
public bool RowChanged { get; set; }
}
As I see from your code, you have attached partially initialized object user to context.Users. As a result when UserManager gets control: AddToRole, it tries to update Database. And you'll have lot empty or null fields in the current users row.
You can fix doing any of the following (both will help):
instead of
ApplicationUser user = new ApplicationUser() use user = UserManager.FindById(UserId)
after assigning values from viewmodel EntityFramework will take care of modified fields.
use another context when dealing with roles
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
Related
So I'm working on an api controller and I have an update method like this:
[HttpPost("update")]
public async Task<IActionResult> Update([FromBody] UpdateModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = _userManager.Users
.Include(u => u.Address)
.FirstOrDefault(u => u.UserName == model.Email);
if (user == null)
{
return BadRequest(new {Message = "User doesn't exist"});
}
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.Address = model.Address;
await _userManager.UpdateAsync(user);
return Ok(new {Message = "User has been updated successfully"});
}
When updating the user through an api call, the user gets updated, except for the address.
This is the address:
public class Address : Entity
{
public string AddressLine { get; set; }
public string PostalCode { get; set; }
public string City { get; set; }
}
And the update model:
public class UpdateModel
{
[Required]
[MinLength(3)]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
public Address Address { get; set; }
}
I've done proper migrations, and I tried setting a breakpoint on the user variable. The variable indeeds hold an Address before updateAsync is called.
The user's firstName and lastName both get updated in a POST request, but the address remains null.
Does anyone know why this would happen?
I think you are set one to one mapping between User & Address. So first of all check the relation mapping & still not work then try to make new variable of Address and after that assign this new variable to user.Address
I'm getting this error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.ConsoleUserInfoes_dbo.ConsolesCheckBoxes_consoleId". The conflict occurred in database "aspnet-ForePlay-20180525122039", table "dbo.ConsolesCheckBoxes", column 'ConsoleId'.
I'm using Entity Framework and ASP.NET MVC 5 and IdentityUser and try to insert data form checkListBox to table into my database.
This is happening on the register view, when user need to register and fill the form.
public class ConsoleUserInfo
{
[Key]
public int identity { get; set; }
[Required]
[StringLength(255)]
[ForeignKey("User")]
public string userid { get; set; }
[Required]
[ForeignKey("consolesCheckBox")]
public int consoleId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual ConsolesCheckBox consolesCheckBox { get; set; }
}
This is the table that need to get a user id (form applictionUser) and consoleId
(form ConsolesCheckBox )
This is the ApplicationUserUser model class:
public class ApplicationUser : IdentityUser
{
[Required]
[StringLength(255)]
override
public string UserName { get; set; }
[Required]
[StringLength(50)]
public string Phone { get; set; }
public byte[] UserPhoto { get; set; }
public virtual UserAddress Address { get; set; }
public virtual ICollection<ConsolesCheckBox> consoleCheckBox { get; set; }
}
and this is the checkBoxList table:
public class ConsolesCheckBox
{
[Key]
public int ConsoleId { get; set; }
public string ConsoleName { get; set; }
public bool IsChecked { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUser { get; set; }
}
This is my account controller, all in the register get and post
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
//using database
using (ApplicationDbContext dbo = new ApplicationDbContext())
{
//data will save list of the consoleCheckBoxItem
var data = dbo.consolesCheckBox.ToList();
// because the view is request a common model, we will create new one
CommenModel a = new CommenModel();
a.ConsolesCheckBoxList = data;
// we will need to return common model, that way we will return a
return View(a);
}
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register([Bind(Exclude = "UserPhoto")]CommenModel model)
{
if (ModelState.IsValid)
{
// To convert the user uploaded Photo as Byte Array before save to DB
byte[] imageData = null;
if (Request.Files.Count > 0)
{
HttpPostedFileBase poImgFile = Request.Files["UserPhoto"];
using (var binary = new BinaryReader(poImgFile.InputStream))
{
imageData = binary.ReadBytes(poImgFile.ContentLength);
}
}
var user = new ApplicationUser
{
UserName = model.registerViewModel.Email,
Email = model.registerViewModel.Email,
Phone = model.registerViewModel.Phone
};
user.UserPhoto = imageData;
var result = await UserManager.CreateAsync(user, model.registerViewModel.Password);
//after the user create, we will use the id and add the id to the userAddress table include
// Address, longitude and latitude.
using (ApplicationDbContext dbo = new ApplicationDbContext())
{
var currentUserId = user.Id;
var pasinfo = dbo.userAddress.FirstOrDefault(d => d.Userid == currentUserId);
if (pasinfo == null)
{
pasinfo = dbo.userAddress.Create();
pasinfo.Userid = currentUserId;
dbo.userAddress.Add(pasinfo);
}
pasinfo.Address = model.useraddress.Address;
pasinfo.latitude = model.useraddress.latitude;
pasinfo.longitude = model.useraddress.longitude;
dbo.SaveChanges();
foreach (var item in model.ConsolesCheckBoxList.Where(x => x.IsChecked).Select(x => x.ConsoleId))
{
var consoleUserInfo = new ConsoleUserInfo
{
userid = currentUserId,
consoleId = item
};
dbo.consoleUserInfo.Add(consoleUserInfo);
}
dbo.SaveChanges();
}
}
}
In the register GET I have a common model, because I used 3 models in the view
this is the common model:
public class CommonModel
{
public UserAddress useraddress { get; set; }
public RegisterViewModel registerViewModel { get; set; }
public List<ConsolesCheckBox> ConsolesCheckBoxList { get; set; }
}
I need your help here, I've been trying to fix this all day.
I'm new to MVC and have been pulling my hair out with this one. I'm already going bold so need someone to rescue what's left!
I'm using asp.net Identity and trying to create a one-to-one relationship between RegisterViewModel and StoreDetails and really hope someone can help me out!
Here is my RegisterViewModel:
public class RegisterViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public virtual StoreDetails StoreDetails { get; set; }
}
Here is my StoreDetails model:
public class StoreDetails
{
[Key, ForeignKey("RegisterViewModel")]
public string StoreName { get; set; }
public virtual RegisterViewModel RegisterViewModel { get; set; }
}
Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser {
// PERSONAL DETAILS
UserName = model.Email,
Email = model.Email,
FirstName = model.FirstName,
LastName = model.LastName
};
var storeDetails = new StoreDetails {
// STORE DETAILS
StoreName = model.StoreDetails.StoreName
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
// UPDATE DB WITH STORE DETAILS DATA
var db = new ApplicationDbContext();
db.StoreDetails.Add(new StoreDetails
{
StoreName = model.StoreDetails.StoreName
});
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
What i'm looking to achieve is on registration the StoreDetails table is generated with the following input:
#Html.TextBoxFor(m => m.StoreDetails.StoreName)
If you need any further details please ask.
Thanks
RegisterViewModel is not a real entity i.e person that gets saved into the database by EF. All it is is a carrier object that passes the values of email and password into the real User/person object called ApplicationUser. This is a view-model and not a real model.
in your example you added StoreDetails in the RegisterViewModel which is correct. But you also need to add this in your ApplicationUser class as a property.
public class ApplicationUser {
public virtual StoreDetails StoreDetails { get; set; }
Then run your migrations.
Adding anything to the RegisterViewModel is made to pass data from the view layer in a way to safe guard the object thats going into your db.
i.e
doing this doesnt do anything because the viewmodel doesnt live in the db
public class StoreDetails
{
[Key, ForeignKey("RegisterViewModel")] //this will not work remove
public string StoreName { get; set; }
public virtual RegisterViewModel RegisterViewModel { get; set; }// remove
}
public Ienumerable<StoreDetails> StoreDetails { get; set; }
public int StoreDetailsId { get; set; }
In controller:
using (var db = ApplicationDbContext.Create())
{
model.StoreDetails = db.StoreDetails.ToList();
}
I want to assign a list of stores to a particular client, based on the Identity Model in MVC 5. I want to be able to register a user/client using the default registration in the MVC 5 example code and be able to add a store, then assign the store to the user.
I'm having trouble being able to create a working viewmodel that incorporates the idenity model with a new list.
Here is what I have so far:
Model:
public class ApplicationUser : IdentityUser
{
//[Required]
[Display(Name = "Client/Company Name")]
public string ClientName { get; set; }
public List<Store> Stores { get; set; }
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;
}
}
I tried to add to the existing default dbcontext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public DbSet<Store> Stores { get; set; }
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
My controller blows up when I try to set my store clientname to the user clientname. Clientname or any other property is unavailable for ApplicationUser.
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Stores
public ActionResult Index(ClientStoreViewModel viewModel)
{
var clients = from s in db.Stores
join c in db.Users
on s.ClientName equals u.ClientName
select new ClientStoreViewModel() { Stores = s, Users = u };
return View(clients);
}
Store class:
public class Store
{
public int Id { get; set; }
//public int ClientId { get; set; }
[Display(Name = "Client Name")]
public string ClientName { get; set; }
[Display(Name = "Store Name")]
public string Brand { get; set; }
[Display(Name = "Store Number")]
public string StoreNumber { get; set; }
[Display(Name="Store Login URL")]
public string LoginURL { get; set; }
}
Finally my viewmodel:
public class ClientStoreViewModel
{
public ApplicationUser Users { get; set; }
public Store Stores { get; set; }
public ClientStoreViewModel()
{
Stores = new Store();
Users = new ApplicationUser();
}
}
I have set Identity so I have separate table for user profile data (firstname, lastname etc.).
Problem is that I have joined those tables via Email field.
I want to change this so I can connect tables by UserID instead.
What I need is to get new userID for created user and use that as foreign key to UserProfileInfo object.
public class ApplicationUser : IdentityUser<int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
public string Email { get; set; }
public string ConfirmationToken { get; set; }
public bool IsConfirmed { get; set; }
public virtual UserProfileInfo UserProfileInfo { get; set; }
}
public class UserProfileInfo
{
public int Id { get; set; }
public string EmailId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
When I create new user I have:
var user = new ApplicationUser()
{
UserName = model.UserName,
Email = model.Email,
ConfirmationToken = confirmationToken,
IsConfirmed = false,
UserProfileInfo = new UserProfileInfo { EmailId = model.Email }
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
SendEmailConfirmation(model.Email, model.UserName, confirmationToken);
return RedirectToAction("RegisterStepTwo", "Account");
}
else...
Why are you doing a join in the first place? ApplicationUser is your user, and the whole point of inheriting from IdentityUser is to allow you extend the user object. Just put your properties like FirstName and LastName directly on ApplicationUser.