Passing additional fields as part of PageRemote data annotation - c#

I have a cshtml page in the server side project of my Blazor Webassembly solution for editing user accounts that looks like this:
#page
#model TimesheetExpense.Server.Areas.Identity.Pages.Account.EditModel
#{
ViewData["Title"] = "Edit User";
}
#using TimesheetExpense.Shared.Dtos
<h4>#ViewData["Title"]</h4>
<form id="user-form" method="post">
<input asp-for="Input.UserId" name="UserId" class="form-control" />
<div class="form-group">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" class="form-control" />
<span asp-validation-for="Input.FirstName"></span>
</div>
<div class="form-group">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" class="form-control" />
<span asp-validation-for="Input.LastName"></span>
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email"></span>
</div>
<button id="update-user-button" type="submit" class="btn btn-primary">Save</button>
</form>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
In the code behind, I'm trying to do some custom validation on the email field to check that the email address hasn't been used by another account. However, I don't want it to flag an issue if that email address is in use by the account I'm currently editing.
public partial class EditModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<ApplicationRole> _roleManager;
private readonly IUnitOfWork _unitOfWork;
public EditModel(UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, IUnitOfWork unitOfWork)
{
_userManager = userManager;
_roleManager = roleManager;
_unitOfWork = unitOfWork;
}
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[PageRemote(ErrorMessage = "Email address already in use.", HttpMethod = "POST", PageHandler = "CheckEmail", AdditionalFields = "UserId,__RequestVerificationToken")]
public string? Email { get; set; }
[Required]
public string? FirstName { get; set; }
[Required]
public string? LastName { get; set; }
public int UserId { get; set; }
}
private async Task LoadAsync(ApplicationUser user)
{
var userId = int.Parse(await _userManager.GetUserIdAsync(user));
Input = new InputModel
{
Email = user.Email,
FirstName = user.FirstName == null ? "" : user.FirstName,
LastName = user.LastName == null ? "" : user.LastName,
UserId = userId,
};
}
public async Task<IActionResult> OnGetAsync(string userId)
{
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public JsonResult OnPostCheckEmail(int UserId)
{
var userViaId = _userManager.FindByIdAsync(UserId.ToString()).Result;
var userViaEmail = _userManager.FindByEmailAsync(Input.Email).Result;
bool valid;
if (userViaEmail == null)
valid = true;
else if (userViaEmail == userViaId)
valid = true;
else
valid = false;
return new JsonResult(valid);
}
public async Task<IActionResult> OnPostAsync(string userId)
{
//Code removed for brevity
}
}
Whenever OnPostCheckEmail is called, UserId is always 0, even though the correct user id is showing in the input field on my form. How do I get it to pull through the correct user id?
My project is ASP.NET Core (.net 6.0) and my pages are based on the scaffolded identity pages.

Related

unable to create HttpGet controller to find user detail using identity

I have created authentication controller and there i was able to create HttpPost for login and logout but not HttpGet. what i'm trying is Only authenticated user can hit and return basic information about currently logged in user at a minimum Username,Staffid(if any)
i have created three role that is admin manager and staff.
namespace Web.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
private readonly UserManager<User> userManager;
private readonly SignInManager<User> signInManager;
public object DefaultAuthenticationTypes { get; private set; }
public AuthenticationController(UserManager<User> userManager, SignInManager<User> signInManager)
{
this.userManager = userManager;
this.signInManager = signInManager;
}
[HttpPost("login")]
public async Task<ActionResult<UserDto>> Login(LoginDto dto)
{
var user = await userManager.FindByNameAsync(dto.UserName);
if (user == null)
{
return BadRequest();
}
var password = await signInManager.CheckPasswordSignInAsync(user, dto.Password, true);
if (!password.Succeeded)
{
return BadRequest();
}
await signInManager.SignInAsync(user, false, "Password");
return Ok(new UserDto
{
UserName = user.UserName
});
}
[HttpGet]
[Authorize]
public async Task<string> GetUserProfile()
{
var userName = await userManager.FindByNameAsync(HttpContext.User.Identity.Name);
var userId = userName.Id;
var roleId = User.IsInRole("Staff");
if (!(roleId.ToString() == "Staff"))
{
return userName.ToString();
}
return userId.ToString();
}
[HttpPost("logout")]
public async Task<ActionResult> logout()
{
await signInManager.SignOutAsync();
return Ok();
}
}
}
If you want to use HttpGet,Here is a demo:
Login:
[HttpGet("login")]
public async Task<IActionResult> Login(InputModel Input) {
return Ok();
}
InputModel:
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
View(use method="get" on form):
<form id="account" method="get" asp-controller="Home" asp-action="Login">
<h4>Use a local account to log in.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="Input.RememberMe">
<input asp-for="Input.RememberMe" />
#Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Log in</button>
</div>
</form>
result:
However,when you use HttpGet,your user login infomation will exposed in the url,so it is not safe,you'd better use HttpPost.
If you want to get user detail,you can use this:
var info = await _userManager.GetUserAsync(HttpContext.User);
I have fixed this issues by :
[HttpGet]
[Authorize]
public async Task<object> GetUserProfile()
{
var userName = await userManager.FindByNameAsync(HttpContext.User.Identity.Name);
var userId = userName.Id;
var role = await userManager.GetRolesAsync(userName);
if (User.IsInRole("Staff"))
{
return Ok(new
{
userName,
userId
});
}
return Ok(new
{
userName,
role
});

Role is null at Post request for ASP.NET Core Razor Pages

I have this Razor Pages which show a textbox and a button. Upon clicked, it will create/insert a Role into the table. Problem is when I put a breakpoint at the OnPostAsync method, role parameter is null.
This is my front-end code:
#page
#model CreateModel
#{
ViewData["Title"] = "Create";
}
<h1>Create Role</h1>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
This is my code-behind:
public class CreateModel : PageModel
{
private readonly RoleManager<IdentityRole> _roleManager;
public CreateModel(RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
}
[BindProperty]
public IdentityRole Name { get; set; }
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 3)]
[Display(Name = "Role Name")]
public IdentityRole Name { get; set; }
//public string Name { get; set; }
}
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(IdentityRole role) {
await _roleManager.CreateAsync(role);
return Page();
}
}
The problem may come from the returned type of IdentityRole, in the client, your browser doesn't know how to convert a variable type to IdentityRole. Some popular types are: string, int, List<T>...
So, in this case, I suggest to use a string instead.
public InputModel Input { get; set; }
public class InputModel
{
public string Name { get; set; }
}
public async Task<IActionResult> OnPostAsync() {
await _roleManager.CreateAsync(new IdentityRole { Name = Input.Name });
return Page();
}

Bundle IdentityUser to ticket

This is a homework assignment. Where I have to make a website with MVC 2.2 and Make it possible to make a ticket with some basic info and link IdentityUser to the ticket.
The Class:
public class Ticket
{
public int ID { get; set; }
[Required]
public string Klant { get; set; }
[Required]
public string Applicatie { get; set; }
[Required]
public string Onderwerp { get; set; }
[Required]
public string Omschrijving { get; set; }
public DateTime Datum { get; set; }
public string Status { get; set; }
}
The Controller:
public class TicketsController : Controller
{
private readonly ApplicationDbContext _context;
UserManager<IdentityUser> _userManager;
public TicketsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
public TicketsController(ApplicationDbContext context)
{
_context = context;
}
// GET: Tickets
public async Task<IActionResult> Index()
{
return View(await _context.Ticket.ToListAsync());
}
This is added to the controller at the moment.
UserManager<IdentityUser> _userManager;
public TicketsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
So I was asking myself how to do this and I would really appreciate it if someone knows what to do and can explain it well.
Thanks in advance.
If you need more info just ask.
Here is a working scenario for creating one ticket with one IdentityUser.
Ticket
public class Ticket
{
public int ID { get; set; }
[Required]
public string Klant { get; set; }
[Required]
public string Applicatie { get; set; }
[Required]
public string Onderwerp { get; set; }
[Required]
public string Omschrijving { get; set; }
public DateTime Datum { get; set; }
public string Status { get; set; }
public string IdentityUserId { get; set; }
public virtual IdentityUser IdentityUser { get; set; }
}
ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Ticket> Tickets { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Ticket>().HasOne(t => t.IdentityUser).WithMany();
}
}
TicketsController
public class TicketsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<IdentityUser> _userManager;
public TicketsController(ApplicationDbContext context
, UserManager<IdentityUser> userManager)
{
_context = context;
_userManager = userManager;
}
// GET: Tickets
public async Task<IActionResult> Index()
{
var applicationDbContext = _context.Tickets.Include(t => t.IdentityUser);
return View(await applicationDbContext.ToListAsync());
}
// GET: Tickets/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var ticket = await _context.Tickets
.Include(t => t.IdentityUser)
.FirstOrDefaultAsync(m => m.ID == id);
if (ticket == null)
{
return NotFound();
}
return View(ticket);
}
// GET: Tickets/Create
public IActionResult Create()
{
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id");
return View();
}
// POST: Tickets/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,Klant,Applicatie,Onderwerp,Omschrijving,Datum,Status,IdentityUserId")] Ticket ticket)
{
if (ModelState.IsValid)
{
_context.Add(ticket);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id", ticket.IdentityUserId);
return View(ticket);
}
// GET: Tickets/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var ticket = await _context.Tickets.FindAsync(id);
if (ticket == null)
{
return NotFound();
}
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id", ticket.IdentityUserId);
return View(ticket);
}
// POST: Tickets/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Klant,Applicatie,Onderwerp,Omschrijving,Datum,Status,IdentityUserId")] Ticket ticket)
{
if (id != ticket.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(ticket);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TicketExists(ticket.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["IdentityUserId"] = new SelectList(_context.Users, "Id", "Id", ticket.IdentityUserId);
return View(ticket);
}
// GET: Tickets/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var ticket = await _context.Tickets
.Include(t => t.IdentityUser)
.FirstOrDefaultAsync(m => m.ID == id);
if (ticket == null)
{
return NotFound();
}
return View(ticket);
}
// POST: Tickets/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var ticket = await _context.Tickets.FindAsync(id);
_context.Tickets.Remove(ticket);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool TicketExists(int id)
{
return _context.Tickets.Any(e => e.ID == id);
}
}
Create.cshtml
#model CoreIdentity2.Models.Ticket
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Ticket</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Klant" class="control-label"></label>
<input asp-for="Klant" class="form-control" />
<span asp-validation-for="Klant" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Applicatie" class="control-label"></label>
<input asp-for="Applicatie" class="form-control" />
<span asp-validation-for="Applicatie" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Onderwerp" class="control-label"></label>
<input asp-for="Onderwerp" class="form-control" />
<span asp-validation-for="Onderwerp" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Omschrijving" class="control-label"></label>
<input asp-for="Omschrijving" class="form-control" />
<span asp-validation-for="Omschrijving" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Datum" class="control-label"></label>
<input asp-for="Datum" class="form-control" />
<span asp-validation-for="Datum" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Status" class="control-label"></label>
<input asp-for="Status" class="form-control" />
<span asp-validation-for="Status" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IdentityUserId" class="control-label"></label>
<select asp-for="IdentityUserId" class ="form-control" asp-items="ViewBag.IdentityUserId"></select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

how can i display a list of roles in Razor Pages view?

I am trying to add a list of the roles in my asp.net core web application to a Razor Page but I'm not sure how to do this. I want the user to be able to choose what role a new user being registered is to be.
The input model itself looks like this:
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Payroll")]
[DataType(DataType.Text)]
public string Payroll { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
public List<String> Roles { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} 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; }
}
As below, I currently have hardcoded values on my view (excerpt):
<div class="form-group">
<label asp-for="Input.Roles"></label>
<select asp-for="Input.Roles" class="form-control">
<option value="Administrator">Administrator</option>
<option value="NormalUser" selected>NormalUser</option>
</select>
</div>
This works for what i want it to do, but I would like to replace this with a dynamic list of roles from my application.
I have the following in my OnGet() method
var roles = _roleManager.Roles.Select(x => x.Name).ToList();
InputModel vm = new InputModel();
vm.Roles = roles;
ReturnUrl = returnUrl;
What I'm asking is how to get the list of roles into a shape that I can use on the page itself.
Thanks.
edit... full code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using MailTracker.Models;
namespace MailTracker.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_roleManager = roleManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Payroll")]
[DataType(DataType.Text)]
public string Payroll { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
public List<SelectListItem> RoleList { get; set; }
public string SelectedRole { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} 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 void OnGet(string returnUrl = null)
{
var roles = _roleManager.Roles.Select(x => x.Name).ToList();
InputModel vm = new InputModel
{
RoleList = roles.Select(x => new SelectListItem { Text = x, Value = x }).ToList()
};
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new ApplicationUser {
UserName = Input.Email,
Email = Input.Email,
Name = Input.Name,
Payroll = Input.Payroll
};
string role = Input.SelectedRole;
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
//add default NormalUser role to user
await _userManager.AddToRoleAsync(user, role);
//
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
and the full page itself:
#page
#model RegisterModel
#{
ViewData["Title"] = "Register";
}
<h2>#ViewData["Title"]</h2>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Payroll"></label>
<input asp-for="Input.Payroll" class="form-control" />
<span asp-validation-for="Input.Payroll" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.RoleList"></label>
<select asp-for="Input.SelectedRole" asp-items="Model.Input.RoleList" class="form-control"></select>
</div>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Register</button>
</form>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
You can do these steps to load the list into <select> tag helper:
1) Create a List<SelectListItem> property in viewmodel (e.g. InputModel) and a string property to hold selected value.
[Required]
public string SelectedRole { get; set; }
public List<SelectListItem> RoleList { get; set; }
2) Convert existing List<string> property using LINQ Select to the List<SelectListItem> object and assign it to RoleList property.
vm.RoleList = vm.Roles.Select(x => new SelectListItem { Text = x, Value = x }).ToList();
3) Finally, use asp-items tag helper to bind it into <select> element:
<select asp-for="Input.SelectedRole" asp-items="Input.RoleList" class="form-control"></select>
Update:
As alternative, you can add List<SelectListItem> as a PageModel property with expression-bodied members:
public List<SelectListItem> RoleList => vm.Roles.Select(x => new SelectListItem { Text = x, Value = x }).ToList();
And set Model.RoleList for asp-items attribute:
<select asp-for="Input.SelectedRole" asp-items="Model.RoleList" class="form-control"></select>
If you don't want to add List<SelectListItem> property, you can use ViewData assigned with options list:
ViewData["RoleList"] = vm.Roles.Select(x => new SelectListItem { Text = x, Value = x }).ToList();
And bind to the page like this:
<select asp-for="Input.SelectedRole" asp-items="#((List<SelectListItem>)ViewData["RoleList"])"></select>
The <option> tag will be created automatically, no need to hardcode it anymore.
Note: Avoid binding List<string> directly to asp-for attribute, since asp-for for <select> tag intended to have single value (i.e. selected value from <option> list), also you should remove RequiredAttribute for Roles property as the attribute is now unnecessary and may trigger model validation error.
Additional reference: The Select Tag Helper
Do it like this: <select asp-for="Role" asp-items="Model.RolesItems"></select>
public List<SelectListItem> RolesItems
public string Role
More here

Updating AspNetUsers with extra properties won't work

Goodevening. I am stuck on something I can't seem to fix my self. I created a razor page called Address.cshtml.cs (model) and Address.cshtml (view) in my project for users to be able to add their user information AFTER registering. And var result = await _userManager.UpdateAsync(user); doesn't seem to work for it. I tried two ways to update it in the database:
First try
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AccountChange(UserModel model)
{
if (ModelState.IsValid)
{
// Get the current application user
var user = await _userManager.GetUserAsync(User);
//Update the details
user.name = model.name;
user.surname = model.surname;
user.street = model.street;
user.streetnumber = model.streetnumber;
user.city = model.city;
user.zipcode = model.zipcode;
// Update user address
var result = await _userManager.UpdateAsync(user);
}
//await _signInManager.RefreshSignInAsync(User);
_logger.LogInformation("User added their address information successfully.");
StatusMessage = "Your address information has been added.";
return RedirectToPage();
}
}
Second try
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(UserModel model)
{
if (ModelState.IsValid)
{
UserModel u = await _userManager.FindByIdAsync(model.Id);
u.name = model.name;
u.surname = model.surname;
u.street = model.street;
u.streetnumber = model.streetnumber;
u.city = model.city;
u.zipcode = model.zipcode;
await _userManager.UpdateAsync(u);
return RedirectToAction("Index");
}
return RedirectToPage();
}
How can I fix this? I've added all of the necessary code below.
Address.cshtml.cs
namespace bytme.Areas.Identity.Pages.Account.Manage
{
public class AddressModel : PageModel
{
private readonly UserManager<UserModel> _userManager;
private readonly SignInManager<UserModel> _signInManager;
private readonly ILogger<AddressModel> _logger;
public AddressModel(
UserManager<UserModel> userManager,
SignInManager<UserModel> signInManager,
ILogger<AddressModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string StatusMessage { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Name")]
[StringLength(100, ErrorMessage = "Invalid input. Maximum is 100 characters.")]
public string name { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Surname")]
[StringLength(100, ErrorMessage = "Invalid input. Maximum is 100 characters.")]
public string surname { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Street")]
[StringLength(48, ErrorMessage = "The longest street name in the Netherlands is 48 characters.")]
public string street { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "House Number")]
[StringLength(5, ErrorMessage = "The longest house number in the Netherlands is 5 characters.")]
public string streetnumber { get; set; }
//[DataType(DataType.Text)]
//[Display(Name = "House Number Addition", Description = "For example A or II")]
//[StringLength(6, ErrorMessage = "
//public string streetnumberadd { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "City")]
[StringLength(28, ErrorMessage = "The longest place name in the Netherlands is 28 characters.")]
public string city { get; set; }
[Required]
[DataType(DataType.PostalCode)]
[Display(Name = "Postal Code")]
[RegularExpression(#"^[1-9][0-9]{3}\s?[a-zA-Z]{2}$", ErrorMessage = "Invalid zip, for example: 1234AB")]
public string zipcode { get; set; }
}
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AccountChange(UserModel model)
{
if (ModelState.IsValid)
{
// Get the current application user
var user = await _userManager.GetUserAsync(User);
//Update the details
user.name = model.name;
user.surname = model.surname;
user.street = model.street;
user.streetnumber = model.streetnumber;
user.city = model.city;
user.zipcode = model.zipcode;
// Update user address
var result = await _userManager.UpdateAsync(user);
}
//await _signInManager.RefreshSignInAsync(User);
_logger.LogInformation("User added their address information successfully.");
StatusMessage = "Your address information has been added.";
return RedirectToPage();
}
}
}
Address.cshtml
#page
#model AddressModel
#inject SignInManager<UserModel> SignInManager
#using Microsoft.AspNetCore.Identity
#using bytme.Models;
#{
ViewData["Title"] = "Add Address Information";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#Html.Partial("_StatusMessage", Model.StatusMessage)
#{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<div>
<h3>Change your account settings</h3>
<hr />
<div class="row">
<div class="col-md-3">
<partial name="_ManageNav" />
</div>
<div class="col-md-9">
<div class="row">
<div class="col-md-6">
<h4>#ViewData["Title"]</h4>
<form id="change-password-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.name"></label>
<input asp-for="Input.name" class="form-control" />
<span asp-validation-for="Input.name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.surname"></label>
<input asp-for="Input.surname" class="form-control" />
<span asp-validation-for="Input.surname" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.street"></label>
<input asp-for="Input.street" class="form-control" />
<span asp-validation-for="Input.street" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.streetnumber"></label>
<input asp-for="Input.streetnumber" class="form-control" />
<span asp-validation-for="Input.streetnumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.city"></label>
<input asp-for="Input.city" class="form-control" />
<span asp-validation-for="Input.city" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.zipcode"></label>
<input asp-for="Input.zipcode" class="form-control" />
<span asp-validation-for="Input.zipcode" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
UserModel.cs
namespace bytme.Models
{
public class UserModel : IdentityUser
{
public override string Id { get; set; }
public override string Email { get; set; }
public override string UserName { get; set; }
public override string PasswordHash { get; set; }
public string zipcode { get; set; }
public string city { get; set; }
public string street { get; set; }
public string streetnumber { get; set; }
public string name { get; set; }
public string surname { get; set; }
}
}
ApplicationDbContext.cs
namespace bytme.Data
{
public class ApplicationDbContext : IdentityDbContext<UserModel>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
public DbSet<bytme.Models.Item> Items { get; set; }
public DbSet<bytme.Models.ItemCategories> ItemCategories { get; set; }
public DbSet<bytme.Models.UserModel> UserModels { get; set; }
public DbSet<bytme.Models.OrderHistory> OrderHistories { get; set; }
public DbSet<bytme.Models.OrderMain> OrderMains { get; set; }
public DbSet<bytme.Models.OrderStatus> OrderStatuses { get; set; }
public DbSet<bytme.Models.WishlistModel> WishlistModels { get; set; }
public DbSet<bytme.Models.ShoppingCartModel> ShoppingCartModels { get; set; }
}
}
After checking the code, I found several problems.
1. I have doubts about your post method AccountChange. The following conventions should be followed. According to Microsoft docs about razor pages, there are generated several default method handlers, like: OnGet OnPost OnGetAsync OnPostAsync etc. However, if you want to use custom handler name, it has to follow some naming convention as well.
Method should start with
OnPost[Get,...]<Handlername>[Async|NoAsync](its optional).
So your method shoud be named like OnPostAccountChangeAsync()
If you define such a method, you has to tell a view, that you want to use the specified handler. You tell it with asp-page-handler directive, so your form shoud looks like this:
<form id="change-password-form" method="post">
//...
<button type="submit" class="btn btn-default" asp-page-handler="AccountChange">Submit</button>
</form>
2. You are binding your property Input
[BindProperty]
public InputModel Input { get; set; }
However, in your method OnPostAccountChangeAsync(), you are trying to accces in the parameter instance of type UserModel, which gonna be null. Its not binded. In the good case you will get a null exception. In bad one, you will update your entity with null properties. So your method OnPostAccountChangeAsync() should accept at parameter instance of InputModel or you can access this property directly inside the body of method (you can get rid of parameter)
OnPostAccountChangeAsync(InputModel Input)
{
//...
}
3. Its minor thing, however, it improves the readability of your code. Please be consistent in naming of your variables. Consitency makes better orientation in your code. In Csharp properties should start with a capital letter (PascalCasing). Do not hesitate, and check C# naming convention
To your question about UserManager in comment section:
You can look at Identity as wrapper (rather say api), which provides useful methods for user management, besides that it offer other things. As I said, if you look at your package dependecies, you can spot that Entity Framework is shipped within Identity nuget package. What does it mean? It opens you possibility to not be only dependent on some "mysterous" Identity. You can clearly use Entity Framework for e.g. saving new user into table AspNetUsers. Clearly said, you are not limited. However, if you already use Identity, it is better to use its available methods.

Categories

Resources