ASP.NET Core IdentityUser relations - asp.net-core

How can I add a related object to the database?
My IdentityUser:
public class ApplicationUser : IdentityUser
{
public ICollection<Todo> Todos { get; set; }
}
My Todo model:
public class Todo
{
public int Id { get; set; }
public string Name { get; set; }
public ApplicationUser User { get; set; }
}
I have a WebAPI like /api/todos.
[HttpPost]
public async Task<IActionResult> Post([FromBody]string name)
{
var user = await _userManager.GetUserAsync(User);
// _db is IdentityDbContext that I use
var newTodo = new Todo() { Name = "do the laundry" }; // Do I have to specify the user like User = user?
_db.Todos.Add(newTodo);
await _db.SaveChangesAsync();
return CreatedAt(...);
}
Question: Am I doing it right? Do I have to pass the user to the new Todo item?
Can I somehow add the todo like that?:
user.Todos.Add(newTodo);
But how would I save it?

Add the User:
var user = await _userManager.GetUserAsync(User);
var newTodo = new Todo() { Name = "do the laundry", User = user };
_db.Todos.Add(newTodo);
await _db.SaveChangesAsync();
or add to the user and update it:
var user = await _userManager.GetUserAsync(User);
var newTodo = new Todo() { Name = "do the laundry" };
user.Todos.Add(newTodo);
_db.Users.Update(user);
await _db.SaveChangesAsync();
Either way should be fine.

Related

How can i commit in identity aspnetusers table and another table in one transaction

i have two tables one is identity table aspnetuser and other is the user table that i have created now one of the column in aspnetuser is identity column autogenerated and this is the foreign key in aspnetuser table and primary key in user table ultimately creating one-one relationship.
now i have this need where i want to insert in these two tables at the same time so if after creating record in aspnetuser succeeds but creating record in users table fails i need to rollback the changes.
how can i achieve this? because for now await _userManager.CreateAsync(user, Input.Password);
this piece of code enters record in aspnetuser table as soon as its called...
i found something similar here How to create transaction with asp.net identity? but can't get my head around it as in my case aspnetuser table has a U_ID column which is identity column generated by db and i need to get its value to create a new record in user table
is it even possible?
here's the diagram
one-one relation
and here's the application user
public class ApplicationUser : IdentityUser
{
[PersonalData]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int U_ID { get; set; }
}
UPDATE
as told by #Zhi Lv i tried this
returnUrl ??= Url.Content("~/");
var result = false;
var errorlist = new List<IdentityError>();
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
var user = new ApplicationUser();
if (ModelState.IsValid)
{
using (var transaction = _dbcontext.Database.BeginTransaction())
{
try
{
Guid guid = Guid.NewGuid();
ShortGuid sguid1 = guid;
//insert user to Users table.
var customeruser = new OnlineEarning.Core.DataModel.Models.User()
{
Name = Input.UserName,
Password = Input.Password,
Email = Input.Email,
UserRefNo = sguid1,
EasyPaisaNoJazzCashNo = "2214457896",
PhoneNo = "2214457896",
City = "Rwp",
};
_dbcontext.Users.Add(customeruser);
_dbcontext.SaveChanges();
//get the latest user id.
var u_id = customeruser.Id;
//
user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, U_ID = u_id };
var saveresult = await _userManager.CreateAsync(user, Input.Password);
if (saveresult.Succeeded)
result = true;
else
errorlist = saveresult.Errors.ToList();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}
if (result)
{
//do something
}
foreach (var error in errorlist)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
but now i am getting this exception 'Execution TImeout expired'
UPDATE 2
now i am able to create a transaction and enter records in both table in single transaction with below code
try
{
returnUrl ??= Url.Content("~/");
var result = false;
var errorlist = new List<IdentityError>();
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
var user = new ApplicationUser();
if (ModelState.IsValid)
{
using (TransactionScope transaction = new TransactionScope(System.Transactions.TransactionScopeAsyncFlowOption.Enabled))
{
try
{
Guid guid = Guid.NewGuid();
ShortGuid sguid1 = guid;
//insert user to Users table.
var customeruser = new OnlineEarning.Core.DataModel.Models.User()
{
Name = Input.UserName,
Password = Input.Password,
Email = Input.Email,
UserRefNo = sguid1,
EasyPaisaNoJazzCashNo = "01224457854",
PhoneNo = "01224457854",
City = "Rwp",
};
_dbcontext.Users.Add(customeruser);
_dbcontext.SaveChanges();
//get the latest user id.
var u_id = customeruser.Id;
user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, U_ID = u_id };
var saveresult = await _userManager.CreateAsync(user, Input.Password);
if (saveresult.Succeeded)
{
result = true;
var uservs = await _userManager.FindByEmailAsync(Input.Email);
await _signInManager.SignInAsync(user, isPersistent: false);
var userv = await _userManager.FindByEmailAsync(Input.Email);
}
else
errorlist = saveresult.Errors.ToList();
transaction.Complete();
}
catch (Exception ex)
{
transaction.Dispose();
ModelState.AddModelError(string.Empty, ex.InnerException.Message.ToString());
}
}
if (result)
{
//do something
}
foreach (var error in errorlist)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
catch(Exception ex)
{
return Page();
}
but during debugging i found out i was unable to read or write to both the tables in sql server when transaction was started in debugging..now my question is what if two users try to register at the same time?will only one of them be allowed and other one will get exception?if yes then what to do in this kind of situation...offcourse transaction is must cannot avoid it otherwise data would be inconsistent
As far as I know, only one column per table can be configured as Identity, so, in the ApplicationUser class, since it inheriting from the IdentityUser (it already contains the identity column), even you configure the U_ID as Identity, the database will not generate a value when a row is inserted. Besides, according to the diagram, it seems that you want to configure one-to-one relationship between the aspnetusers table and Users table, if that is the case, I suggest you could try to set the Identity column in the Users table, and use the following code to configure relationships:
User.cs:
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int U_ID { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public string PhoneNo { get; set; }
public string Email { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
ApplicationUser.cs:
public class ApplicationUser:IdentityUser
{
[PersonalData]
public int U_ID { get; set; }
[ForeignKey("U_ID")]
public User User { get; set; }
}
ApplicationDbContext.cs:
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
#pragma warning disable CS0114 // Member hides inherited member; missing override keyword
public DbSet<User> Users { get; set; }
#pragma warning restore CS0114 // Member hides inherited member; missing override keyword
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Then, when register the user, you could refer to the following code to use transaction and insert data.
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _context;
public RegisterModel( UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, ApplicationDbContext context )
{
_userManager = userManager;
_signInManager = signInManager;
_context = context;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
var result = false;
var errorlist = new List<IdentityError>();
var user = new ApplicationUser();
if (ModelState.IsValid)
{
using (var transaction = _context.Database.BeginTransaction())
{
try
{
//insert user to Users table.
var customeruser = new User() { PhoneNo = "12345678", Name = "Tom", Password = "Password01", Email = "tom#hotmail.com" };
_context.Users.Add(customeruser);
_context.SaveChanges();
//get the latest user id.
var u_id = customeruser.U_ID;
//
user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, U_ID = u_id };
var saveresult = await _userManager.CreateAsync(user, Input.Password);
if (saveresult.Succeeded)
result = true;
else
errorlist = saveresult.Errors.ToList();
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
}
}
if (result)
{
//do something
}
foreach (var error in errorlist)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
The result like this:
Reference:
Entity Framework Core Transaction
Using Transactions
One-to-One Relationship Conventions in Entity Framework Core
[Update]
In your scenario, you could insert the AspnetUser first, then get the U_ID and insert a new item into the Users table, please try to change the code as below:
var result = false;
var errorlist = new List<IdentityError>();
var user = new ApplicationUser();
if (ModelState.IsValid)
{
using (var transaction = _context.Database.BeginTransaction())
{
try
{
user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var saveresult = await _userManager.CreateAsync(user, Input.Password);
if (saveresult.Succeeded)
{
result = true;
//get the U_ID
var userid = user.U_ID;
//use the U_ID to create user.
var customeruser = new User() { U_ID = userid, PhoneNo = "12345678", Name = "Tom", Password = "Password01", Email = "tom#hotmail.com" };
_context.Users.Add(customeruser);
_context.SaveChanges();
}
else{
errorlist = saveresult.Errors.ToList();
}
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
}
}
if (result)
{
//do something
}
foreach (var error in errorlist)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}

Why i'm getting an empty array in result of httpget?

I have StatsUserModel ( code below )
StatsUserModel.cs
namespace WebAPI.Models
{
public class StatsUserModel
{
public int DeviceID { get; set; }
public int bloodpress_sys { get; set; }
public int bloodpress_dia { get; set; }
public int saturation { get; set; }
public int BPM { get; set; }
public int veinsdiameter { get; set; }
public string Id { get; set; }
}
}
And i have Users from AspNetUsers
AspNetUsers and Stats structure
And i have AuthenticationContext.cs
namespace WebAPI.Models
{
public class AuthenticationContext : IdentityDbContext
{
public AuthenticationContext(DbContextOptions options):base(options)
{
}
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<StatsUserModel> statsUserModels { get; set; }
}
}
So, I have created StatsController and HttpGet method
StatsController.cs
namespace WebAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class StatsController : ControllerBase
{
private UserManager<ApplicationUser> _userManager;
private AuthenticationContext context;
public StatsController(AuthenticationContext _context, UserManager<ApplicationUser> userManager)
{
context = _context;
_userManager = userManager;
}
[HttpGet]
[Authorize]
public async Task<Object> GetStats(LoginModel model)
{
string userId = User.Claims.First(c => c.Type == "UserID").Value;
var user = await _userManager.FindByIdAsync(userId);
var data = context.statsUserModels.Where(s => s.Id == user.Id);
return data;
}
}
}
Generating a JWT
[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login(LoginModel loginModel)
{
var user = await _userManager.FindByNameAsync(loginModel.UserName);
if(user != null && await _userManager.CheckPasswordAsync(user, loginModel.Password))
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserID", user.Id.ToString())
}),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_applicationSettings.JWT_Secret)), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return Ok(new { token });
}
else
{
return BadRequest(new { message = "Username or password invalid" });
}
}
I use Postman to test login and it's returns me a JWT token and everything works fine, but when i'm passing that login name and password, it returns me an empty array with 200OK code
Fixed, i have made Add-Migration and filled the table once more time and everything worked, Thanks Rena !

Upgrading single role management to multiple role management

I'm Working on a project that had authorization implemented with One user has One role.
Now we want to convert that relation to many to many but in the asp.net core authorization it went wrong.
[Serializable]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Required]
public Guid? Id { get; set; }
public virtual IList<UserRole> UserRoles { get; set; } = new List<UserRole>();
[NotMapped]
public string Token { get; set; }
/**/
[Serializable]
public class UserRole
{
public Guid UserId { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
}
[Serializable]
public class Role
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore]
public int Id { get; set; }
public string Name { get; set; }
}
}
while our database and mapping works perfect. the authorization in asp.net core fails.
autorization service:
public async Task<DTO_User> Authenticate(string username, string password)
{
var users = await _userRepo.GetAll();
var user = users.Where(u => u.Username == (username) && u.Password == (password)).FirstOrDefault();
if (user == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username.ToString()),
};
var roles = await this._userRepo.GetUserRoles(user.Id.Value.ToString());
var claimsWithRoles = roles.ToList().Select(role => new Claim(ClaimTypes.Role, role.Name));
var allClaims = claims.Concat(claimsWithRoles);
tokenDescriptor.Subject = new ClaimsIdentity(allClaims);
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
// remove password before returning
user.Password = null;
return _mapper.Map<DTO_User>(user);
}
**Controller**
[Route("api/[controller]")]
[ApiController]
[Authorize]
[EnableCors("CorsPolicy")]
public class SessionController : ControllerBase
{
[HttpGet]
[Route("active")]
public async Task<IActionResult> GetAllActive()
{
}
}
}
but where getting the exception:

How to register a use with role in .net core?

I wants to create a new user with role. I'm using entity migration. so i get the all default tables. Without roles I'm able to register a new user but when i add the roles drop down I'm facing a error Cannot convert from string to ourproject.entitites.user
Please help and teach me where i did the mistake. I'm new to .net core technology.
I'm facing a problem with user.Id
await _userManager.AddToRoleAsync(user.Id, model.UserRoles);
ControllerCode:
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterUserModel model)
{
if (ModelState.IsValid)
{
var user = new User { UserName = model.Username, Email = model.Email,
PhoneNumber = model.PhoneNumber };
//var phoneNo = new User { PhoneNumber = model.PhoneNumber };
var createResult = await _userManager.CreateAsync(user, model.Password);
if (createResult.Succeeded)
{
await _userManager.AddToRoleAsync(user.Id, model.UserRoles);
return RedirectToAction("Details", "Home");
}
else
{
ViewBag.Name = new SelectList(_Context.Roles.Where(u => !u.Name.Contains("Admin"))
.ToList(), "Name", "Name");
foreach (var error in createResult.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
}
return View();
}
Registermodel Code:
public class RegisterUserModel
{
[Required,MaxLength(256)]
public string Username { get; set; }
[Required, DataType(DataType.Password)]
public string Password { get; set; }
[Required, DataType(DataType.Password), Compare(nameof(Password))]
public string ConfirmPassword { get; set; }
[Required, DataType(DataType.EmailAddress),MaxLength(256)]
public string Email { get; set; }
[Required,DataType(DataType.PhoneNumber)]
public string PhoneNumber { get; set; }
[Display(Name ="UserRoles")]
public string UserRoles { get; set; }
}
User Entity Code:
using Microsoft.AspNetCore.Identity;
namespace OurProject.Entities
{
public class User : IdentityUser
{
}
}
Here is a functional code for .net core 2.0. In AddToRoleAsync method I am sending user object itself.
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
string roleName = "Manager";
var isExistRole = await this._roleManager.RoleExistsAsync(roleName);
if (!isExistRole)
{
await this._roleManager.CreateAsync(
new IdentityRole
{
Id = Guid.NewGuid().ToString(),
Name = roleName
});
}
await _userManager.AddToRoleAsync(user, roleName);
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}

ASP.NET Core Identity Extend Not Saving

I'm attempting to extend the ASP.NET Identity. I feel like I have most of the parts. The model and user objects all properly populate. However, when I check the database for the new record inserted via the CreateAsync function, the new fields are all NULL. What am I missing?
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName
, Organization = model.Organization };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id.ToString(), code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// Signin settings
options.SignIn.RequireConfirmedEmail = false;
options.SignIn.RequireConfirmedPhoneNumber = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
public class ApplicationUser : IdentityUser
{
public String FirstName;
public String LastName;
public String Organization;
}
You need to use auto-implemented properties in your ApplicationUser class instead of just using public fields. That might be the problem.
public class ApplicationUser : IdentityUser
{
public String FirstName { get; set; }
public String LastName { get; set; }
public String Organization { get; set; }
}