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.
Related
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();
}
Database Used: MySql Server
DotNet Core Version = 2.2
Platform: Windows 10 IIS
When I am trying to save an attendance of an existing employee, the attendance page is trying to create a new employee with null values in Name fields. Since name field is set to not null its failed and showing error message.
Employee Table
namespace payroll_razor_core.Models.repository
{
[Table("Employee")]
[Display(Name = "Employee",Description = "Stores Employee Basic Details.")]
public class Employee
{
[Column("Employee Id")]
[Key]
public string EmployeeId { get; set; }
public string EmployeeName =>
string.Concat(EmployeeFirstName,
string.IsNullOrEmpty(EmployeeMiddleName)?"":" "+EmployeeMiddleName,
string.IsNullOrEmpty(EmployeeLastName) ? "" : " " + EmployeeLastName
);
[Column("Employee First Name")]
[Display(Name = "First Name *")]
[MaxLength(200, ErrorMessage = "Exceeded Character Limit..!!")]
[RegularExpression(#"^[[A-Za-z+[\s]+[A-Za-z]+]*]*", ErrorMessage = "Can accept only characters..!!",
MatchTimeoutInMilliseconds = 1000)]
[Required(ErrorMessage = "Employee First Name is required..!!")]
public string EmployeeFirstName { get; set; }
[Column("Employee Middle Name")]
[Display(Name = "Middle Name *")]
[MaxLength(200, ErrorMessage = "Exceeded Character Limit..!!")]
[RegularExpression(#"^[[A-Za-z+[\s]+[A-Za-z]+]*]*", ErrorMessage = "Can accept only characters..!!",
MatchTimeoutInMilliseconds = 1000)]
public string EmployeeMiddleName { get; set; }
[Column("Employee Last Name")]
[Display(Name = "Last Name *")]
[MaxLength(200, ErrorMessage = "Exceeded Character Limit..!!")]
[RegularExpression(#"^[[A-Za-z+[\s]+[A-Za-z]+]*]*", ErrorMessage = "Can accept only characters..!!",
MatchTimeoutInMilliseconds = 1000)]
public string EmployeeLastName { get; set; }
public ICollection<AttendanceDailyRegister> AttendanceDailyRegisters { get; set; }
}
Attendance Table
[Table("Attendance")]
[Display(Name = "Attendance",Description = "Registers Employee Attendance")]
public class Attendance
{
[Key]
[Column("Attendance Id")]
[Display(Name = "Attendance Id")]
public int AttendanceId { get; set; }
[ForeignKey("EmployeeId")]
[Column("Employee")]
[Display(Name = "Employee")]
public string Employee { get; set; }
public bool Check{ get; set; }
[Column("AttendanceTime")]
[Display(Name = "Attendance Time",AutoGenerateField = true)]
[DisplayFormat(DataFormatString = "{0:dddd, dd/MM/yyyy, h:mm:ss tt}")]
[Timestamp]
public DateTime AttendanceTime { get; set; }
[ForeignKey("Employee")]
public virtual Employee Employees { get; set; }
}
Attendance Create Page
public class CreateModel : PageModel
{
private readonly Data.payroll_app_context _context;
public CreateModel(Data.payroll_app_context context)
{
_context = context;
}
public IActionResult OnGet()
{
ViewData["Employee"] = new SelectList(_context.Employee, "EmployeeId", "EmployeeName");
return Page();
}
[BindProperty]
public AttendanceDailyRegister AttendanceDailyRegister { get; set; }
public async Task<IActionResult> OnPostAsync()
{
//Commented for catching errors.
/*if (!ModelState.IsValid)
{
return Page();
}*/
_context.AttendanceDailyRegister.Add(AttendanceDailyRegister);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Attendance Razor CSHTML Page
#page
#model razor.Pages.attendance.CreateModel
#{
ViewData["Title"] = "Create";
Layout = "~/Pages/Shared/_Layout.cshtml";
}
<h1>Create</h1>
<h4>Attendance</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Attendance.Employee" class="control-label"></label>
<select asp-for="Attendance.Employee" class ="form-control" asp-items="ViewBag.Employee"></select>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="Attendance.Check" /> #Html.DisplayNameFor(model => model.Attendance.Check)
</label>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Here in this page while saving new attendance time of an existing employee, its creating a new employee. I Cannot fix it. Please help me.
The moment I changed the attendance table, the problem got solved. Though I don't clearly understand how it got solved.
[Table("Attendance")]
[Display(Name = "Attendance",Description = "Registers Employee Attendance")]
public class Attendance
{
private readonly Employee employee;
[Key]
[Column("Attendance Id")]
[Display(Name = "Attendance Id")]
public int AttendanceId { get; set; }
[ForeignKey("EmployeeId")]
[Column("Employee")]
[Display(Name = "Employee")]
public string Employee { get; set; }
public bool Check{ get; set; }
[Column("AttendanceTime")]
[Display(Name = "Attendance Time",AutoGenerateField = true)]
[DisplayFormat(DataFormatString = "{0:dddd, dd/MM/yyyy, h:mm:ss tt}")]
[Timestamp]
public DateTime AttendanceTime { get; set; }
[ForeignKey("Employee")]
public virtual Employee Employees => employee
}
I am writing a .NET Core 3.0 MVC Web app. I have a JobApplication model that looks like this:
public class JobApplication
{
[Key]
public int Id{ get; set; }
[Required]
public DateTime CreatedOn { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyy-MM-dd}")]
[Display(Name = "Edited on:")]
public DateTime? EditedOn { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyy-MM-dd}")]
[Display(Name = "Deleted on:")]
public DateTime? DeletedOn { get; set; }
[Required]
public User Applicant { get; set; }
[Required]
public JobOffer JobOffer { get; set; }
[Required]
public ApplicationStatus ApplicationStatus { get; set; }
public string CvHandle { get; set; }
public string AdditionalInformation { get; set; }
}
As you can see, the model holds references to the Job Offer it was created for and the applicant that created the application.
I also have a Controller JobApplicationController that has 2 Create methods:
public async Task<ActionResult> Create(int? id)
{
var offer = await _context.JobOffers.Include(x => x.CreatedFor).FirstOrDefaultAsync(x => x.Id == id.Value);
var user = await _context.Users.FirstOrDefaultAsync(x => x.Name == "Filip");
var model = new JobApplication()
{
JobOffer = offer,
Applicant = user
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([FromForm]JobApplication model)
{
if (!ModelState.IsValid)
{
return View(model);
}
JobApplication ja = new JobApplication
{
...
};
await _context.JobApplications.AddAsync(ja);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
As you can see, one of them returns the Create view, the other gets the model from the view and adds it to the database. I also emphasize that in the first method, the 'JobOffer' and 'Applicant' fields are taken from the database and passed to the form with a model. Here's how the view is set up:
#model HRpest.BL.Model.JobApplication
#{
ViewData["Title"] = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
#Html.Hidden("Offer", Model.JobOffer)
#Html.Hidden("Applicant", Model.Applicant)
<div asp-validation-summary="None" class="text-danger"></div>
<div class="form-group">
<label asp-for="CvHandle" class="control-label"></label>
<input asp-for="CvHandle" class="form-control" />
<span asp-validation-for="CvHandle" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="AdditionalInformation" class="control-label"></label>
<input asp-for="AdditionalInformation" class="form-control" />
<span asp-validation-for="AdditionalInformation" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
It all seems fine. However, when I try to add an application and submit the form, I get an error:
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"|a72c03fc-4f6501d7781e4a9a.","errors":{"JobOffer":["The JobOffer field is required."],"Applicant":["The Applicant field is required."]}}
I don't understand it. I have both fields in my model. How to make this go away?
Thank you so much in advance.
You need to use HiddenFor() Instead of Hidden().
Read here to understand the difference between the two.
I think is because required too. Because navigation property is complex object and should not be required
public virtual User Applicant { get; set; }
public virtual JobOffer JobOffer { get; set; }
public virtual ApplicationStatus ApplicationStatus { get; set; }
In the view, you are saying, #Html.Hidden("Offer", Model.JobOffer). It should instead be #Html.Hidden("JobOffer", Model.JobOffer) because that's the property name. You wouldn't make that mistake if you were using HiddenFor.
On a more abstract level, you are binding directly to the database entity. It's never a good idea. You should use a model to get the posted values and then copy those values to the entity. You can use AutoMapper to do that automatically for you.
I am learning how to use SigninManager, UserManager, etc for my web app, but I am getting this error:
SqlException: Invalid object name 'AspNetUsers'
I was following a demo and trying to modify it to fit my database user requirements. I have a users table in my SQL Server called "VisUser" which additional required information such as lastname, username, email, etc.
Before trying to implement identities I had a model for users then used the same code and inserted it into my applicationuser. From what I've seen online is that after you implement the database context with IdentityUser a series of tables are generated into your database in SQL Server however that never occurs for me.
My 'Accounts' model
public class Accounts
{
[Table("VisUser")]
public class VisUser
{
[Key]
public int UserId { get; set; }
[BindProperty]
[Required]
[MinLength(0), MaxLength(50), DataType(DataType.Text), Display(Name = "FirstName")]
public string FirstName { get; set; }
[BindProperty]
[Required]
[MinLength(0), MaxLength(2), DataType(DataType.Text), Display(Name = "Middle Initial")]
public string MiddleInitial { get; set; }
[BindProperty]
[Required]
[MinLength(1), MaxLength(50), DataType(DataType.Text), Display(Name = "Last Name")]
public string LastName { get; set; }
[BindProperty]
[Required]
[MinLength(4), MaxLength(50), DataType(DataType.Text), Display(Name = "Username")]
public string VisUserName { get; set; }
[BindProperty]
[Required]
[EmailAddress, MaxLength(256), Display(Name = "Email Address")]
public string VisEmail { get; set; }
[BindProperty]
[Required]
[MinLength(6), MaxLength(50), DataType(DataType.Password), Display(Name = "UserPassword")]
public string UserPassword { get; set; }
[BindProperty]
[Required]
[MinLength(6), MaxLength(50), DataType(DataType.Password), Display(Name = "Confirm Password")]
[Compare("UserPassword", ErrorMessage = "The Password does not match the Confirmed Password")]
public string ConfirmPassword { get; set; }
}
}
ApplicationUser:
public class ApplicationUser : IdentityUser
{
[Key]
public int UserId { get; set; }
[BindProperty]
[Required]
[MinLength(0), MaxLength(50), DataType(DataType.Text), Display(Name = "FirstName")]
public string FirstName { get; set; }
[BindProperty]
[Required]
[MinLength(0), MaxLength(2), DataType(DataType.Text), Display(Name = "Middle Initial")]
public string MiddleInitial { get; set; }
[BindProperty]
[Required]
[MinLength(1), MaxLength(50), DataType(DataType.Text), Display(Name = "Last Name")]
public string LastName { get; set; }
[BindProperty]
[Required]
[MinLength(4), MaxLength(50), DataType(DataType.Text), Display(Name = "Username")]
public string VisUserName { get; set; }
[BindProperty]
[Required]
[EmailAddress, MaxLength(256), Display(Name = "Email Address")]
public string VisEmail { get; set; }
[BindProperty]
[Required]
[MinLength(6), MaxLength(50), DataType(DataType.Password), Display(Name = "UserPassword")]
public string UserPassword { get; set; }
[BindProperty]
[Required]
[MinLength(6), MaxLength(50), DataType(DataType.Password), Display(Name = "Confirm Password")]
[Compare("UserPassword", ErrorMessage = "The Password does not match the Confirmed Password")]
public string ConfirmPassword { get; set; }
}
Databasecontext:
public class DatabaseContext : IdentityDbContext<ApplicationUser>
{
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
}
public DbSet<CategoryTB> CategoryTB { get; set; }
public DbSet<VisUser> VisUser { get; set; }
}
What I added into my startup.cs:
services.AddIdentity<ApplicationUser, IdentityRole>()
// adds UserStore and RoleStore for Usermanager and RoleManager
.AddEntityFrameworkStores<DatabaseContext>()
// adds a provider that generates unique keys and hashes for things like forgot password links, phone verification etc
.AddDefaultTokenProviders();
Register page html:
<body>
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
<div class="row">
<div class="col-md-4">
<label asp-for="VisUser.FirstName"></label>
<input asp-for="VisUser.FirstName" class="form-control" />
<span class="alert-danger" asp-validation-for="VisUser.FirstName"></span>
</div>
</div>
<div class="row">
<div class="col-md-4">
<label asp-for="VisUser.MiddleInitial"></label>
<input asp-for="VisUser.MiddleInitial" class="form-control" />
<span class="alert-danger" asp-validation-for="VisUser.MiddleInitial"></span>
</div>
</div>
<div class="row">
<div class="col-md-4">
<label asp-for="VisUser.LastName"></label>
<input asp-for="VisUser.LastName" class="form-control" />
<span class="alert-danger" asp-validation-for="VisUser.LastName"></span>
</div>
</div>
<div class="row">
<div class="col-md-4">
<label asp-for="VisUser.VisEmail"></label>
<input asp-for="VisUser.VisEmail" class="form-control" />
<span class="alert-danger" asp-validation-for="VisUser.VisEmail"></span>
</div>
</div>
<div class="row">
<div class="col-md-4">
<label asp-for="VisUser.VisUserName"></label>
<input asp-for="VisUser.VisUserName" cl class="form-control" />
<span class="alert-danger" asp-validation-for="VisUser.VisUserName"></span>
</div>
</div>
<div class="row">
<div class="col-md-4">
<label asp-for="VisUser.UserPassword"></label>
<input asp-for="VisUser.UserPassword" cl class="form-control" />
<span class="alert-danger" asp-validation-for="VisUser.UserPassword"></span>
</div>
</div>
<div class="row">
<div class="col-md-4">
<label asp-for="VisUser.ConfirmPassword"></label>
<input asp-for="VisUser.ConfirmPassword" cl class="form-control" />
<span class="alert-danger" asp-validation-for="VisUser.ConfirmPassword"></span>
</div>
</div>
<br />
<input type="submit" value="Save" class="btn btn-primary" />
<a class="btn btn-default" href="/allcustomer">Cancel</a>
</form>
</body>
Code behind Register html:
public class RegisterModel : PageModel
{
public string ReturnUrl { get; set; }
private DatabaseContext _context;
[BindProperty]
public VisUser VisUser { get; set; }
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public RegisterModel(DatabaseContext _context, SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
{
DatabaseContext _context;
_userManager = userManager;
_signInManager = signInManager;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
FirstName = VisUser.FirstName,
MiddleInitial = VisUser.MiddleInitial,
LastName = VisUser.LastName,
Email = VisUser.VisEmail,
UserName = VisUser.VisUserName,
UserPassword = VisUser.UserPassword
};
var result = await _userManager.CreateAsync(user, user.UserPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
if (string.IsNullOrWhiteSpace(returnUrl))
{
return LocalRedirect("~/");
}
else
{
return Redirect(returnUrl);
}
}
}
return Page(); //if reaches here then something's wrong (redirects to current page)
}
}
I believe this is all the relevant pieces of code for implementing identityusers.
What I would like to achieve is for the user to be added then have the relevant information stored inside my VisUser table in my SQL Server, then sign the user in so that I have access to who is logged in and can change the navbar appropriately. However, what occurs is that the application returns:
SqlException: Invalid object name 'AspNetUsers'
when I try to pass the user and password into the UserManager in:
var result = await _userManager.CreateAsync(user, user.UserPassword);
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