How to Edit Image in Existing Member - asp.net

I Use bootstrap cards and i want picture can also edit on existing user or i Edit picture of any member
This is my Controller
This is my edit Functionality where i can edit My Member so i want i update all existing user picture
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var member = await _context.Member.FindAsync(id);
if (member == null)
{
return NotFound();
}
return View(member);
}
// POST: HomePage/Edit/5
// To protect from overposting attacks, 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("MemberId,Name,Gender,DOB,MaritalStatus,Address,PhoneNo,Skills,Hobbies,JobTitle,Technology")] Member member)
{
if (id != member.MemberId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(member);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MemberExists(member.MemberId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(member);
}
This is a View of my project this is same as create class
<div class="row">
<div class="form-group col-md-4">
<label class="col-md-2 control-label">MemberImage</label>
<div class="col-md-10">
<div id="chooseFile">
<input class="form-control" type="file" name="photo" accept="image/*" />
</div>
</div>
</div>
</div>
when i edit the one existing member
2.i add image
and save it will save the img but now they cannot save image
Exception Occurs when i open edit
*Edit Page Code
<div class="row">
<div class="form-group col-md-4">
<label class="col-md-2 control-label">MemberPicture</label>
<div class="col-md-10">
<img src="~/ImageName/Cover/#Model.Member.ImageName"
class="rounded-square"
height="50" width="75"
style="border:1px"
asp-append-version="true" accept="image/*" />
<span>#Model.Member.ImageName</span>
<div id="chooseFile">
<input class="form-control" type="file" name="photo" accept="image/*" />
</div>
</div>
</div>
</div>
View Model
using System;
using TeamManagement.Models;
namespace TeamManagement.ViewModel
{
public class MemberViewModel
{
public Member Member { get; set; }
public IFormFile? Photo { get; set; }
}
}

This is my edit Functionality where I can edit My Member so I want I update all existing user picture?
If you look into the Member List, it usually contains rows of users along with Id. So we have to find the particular Id and then need to retrieve the value of that Id finally we will update the existing value with the new value. As seen on the screenshot below
Algorithm
From The Member List Click On Particular Member Id
Find The Member Information By that Id
Load The Edit Page With That Id Same As Create Member Page
After Required Change Submit the Edit Page Which Containing the Member Model Data With A ID
Save the Image Into Folder First Same As Create
Search The Member Object By Id
Set New Value Into The Member Object You Have Found In Step 6
Save The Context And Redirect To Member List
Controller Action For Loading Edit Page
public async Task<IActionResult> EditMember(int memberId)
{
var memeber = await _context.Members.FindAsync(memberId); // Getting member by Id from database
return View(new MemberViewModel() { Member = memeber });
}
View Model
public class MemberViewModel
{
public Member Member { get; set; }
public IFormFile? Photo { get; set; }
}
View Edit
#model DotNet6MVCWebApp.Models.MemberViewModel
<div>
<form asp-action="EditMember" method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly"></div><input type="hidden" asp-for="Member.MemberId" />
<div>
<h4><strong>Member Details</strong> </h4>
<table class="table table-sm table-bordered table-striped">
<tr>
<th> <label asp-for="Member.Name"></label></th>
<td> <input asp-for="Member.Name" class="form-control" placeholder="Enter member name" /><span asp-validation-for="Member.Name"></span></td>
</tr>
<tr>
<th> <label asp-for="Member.Gender"></label></th>
<td>
<select asp-for="Member.Gender" class="form-control">
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
<span asp-validation-for="Member.Gender"></span>
</td>
</tr>
<tr>
<th> <label asp-for="Member.DOB"></label></th>
<td> <input asp-for="Member.DOB" class="form-control" placeholder="Enter animal category" /><span asp-validation-for="Member.DOB"></span></td>
</tr>
<tr>
<th> <label asp-for="Photo"></label></th>
<td>
<img src="~/ImageName/Cover/#Model.Member.ImageName"
class="rounded-square"
height="50" width="75"
style="border:1px"
asp-append-version="true" accept="image/*" />
<span>#Model.Member.ImageName</span>
<div id="chooseFile"><input type="file" name="photo" accept="image/*" /></div>
</td>
</tr>
<tr>
<th> <button type="submit" class="btn btn-primary" style="width:107px">Update</button></th>
<td> </td>
</tr>
<tr>
<th>#Html.ActionLink("Back To List", "MemberList", new { /* id=item.PrimaryKey */ }, new { #class = "btn btn-success" })</th>
<td> </td>
</tr>
</table>
</div>
</form>
</div>
Here make sure your src="~/ImageName/Cover/#Model.Member.ImageName" is correct as per your picture location. Otherwise picture will not be displayed
Controller When Submit Edit
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditMember(MemberViewModel model, IFormFile photo)
{
if (photo == null || photo.Length == 0)
{
return Content("File not selected");
}
//Save The Picture In folder
var path = Path.Combine(_environment.WebRootPath, "ImageName/Cover", photo.FileName);
using (FileStream stream = new FileStream(path, FileMode.Create))
{
await photo.CopyToAsync(stream);
stream.Close();
}
//Bind Picture info to model
model.Member.ImageName = photo.FileName;
//Finding the member by its Id which we would update
var objMember = _context.Members.Where(mId => mId.MemberId == model.Member.MemberId).FirstOrDefault();
if (objMember != null)
{
//Update the existing member with new value
objMember!.Name = model.Member.Name;
objMember!.Gender = model.Member.Gender;
objMember!.DOB = model.Member.DOB;
objMember!.ImageName = model.Member.ImageName;
objMember!.ImageLocation = path;
await _context.SaveChangesAsync();
}
return RedirectToAction("MemberList");
}
Here we will update the _context.SaveChangesAsync() instead of adding new data. This is the key point for edit. Update the information which we have got by ID
Output

Related

Trying to write CRUD fucntions for ASP .NET Core razor pages. Why doesn't my delete function work?

GitHub
Delete.cshtml.cs
#region snippet_All
using Project.Models; //using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace Project.Pages.Albums //ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly Chinook _context;
public DeleteModel(Chinook context)
{
_context = context;
}
[BindProperty]
public Album Album { get; set; }
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
Album = await _context.Albums
.AsNoTracking()
.FirstOrDefaultAsync(m => m.AlbumId == id);
if (Album == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var album = await _context.Albums.FindAsync(id);
if (album == null)
{
return NotFound();
}
try
{
_context.Albums.Remove(album);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
}
}
#endregion
Delete.cshtml
#page
#model Project.Pages.Albums.DeleteModel
#{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">#Model.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Album</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.Album.Title)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.Album.Title)
</dd>
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.Album.ArtistId)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.Album.ArtistId)
</dd>
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.Album.AlbumId)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.Album.AlbumId)
</dd>
</dl>
<form method="Delete">
<input type="hidden" asp-for="AlbumId" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
Album.cshtml
#page
#model Project.Pages.AlbumsModel
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div class="row">
<h1 class="display-2">Albums</h1>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Album ID, artist ID, Album title</th>
</tr>
</thead>
<tbody>
#foreach (string album in Model.Albums)
{
<tr>
<td>#album</td>
<td><a asp-page="./Delete" asp-route-id="#album">Delete</a></td>
</tr>
}
</tbody>
</table>
</div>
<div class="row">
<p>Enter a title & ArtistID for a new album: </p>
<form method="POST">
<div><input asp-for="Album.Title" /></div>
<div><input asp-for="Album.ArtistId" /></div>
<input type="submit" />
</form>
#* <p>Enter a value and Album ID for a new album title: </p>
<form method="POST">
<div><input asp-for="Album.Title" /></div>
<div><input asp-for="Album.AlbumId" /></div>
<input type="submit" />
</form> *#
</div>
Album.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Project.Models;
using System;
namespace Project.Pages
{
public class AlbumsModel : PageModel
{
private Chinook db;
public AlbumsModel(Chinook injectedContext)
{
db = injectedContext;
}
public IEnumerable<string> Albums { get; set; }
public void OnGet()
{
ViewData["Title"] = "Chinook Web Site - Albums";
// Albums = db.Albums.Select(s => s.Title);
Albums = db.Albums.Select(s => s.AlbumId.ToString() + ". " + s.ArtistId + ". " + s.Title);
}
[BindProperty]
public Album Album { get; set; }
public IActionResult OnPost()
{
if (ModelState.IsValid)
{
db.Albums.Add(Album);
db.SaveChanges();
return RedirectToPage("/albums");
}
return Page();
}
// void RemoveAlbum(int albumId)
// {
// var album = db.Albums.Find(albumId);
// if (album == null)
// throw new ArgumentOutOfRangeException();
// db.Albums.Remove(album);
// db.SaveChanges();
// }
public IActionResult DeleteAlbum(int AlbumId)
{
var album = db.Albums.Find(AlbumId);
if (album == null) return Page();
db.Albums.Remove(album); db.SaveChanges(); return RedirectToPage("/albums");
}
}
}
My project builds and runs fine, but the Delete button does nothing but refresh the page. I've tried reloading the page, but that makes no odds. I'm not convinced <td><a asp-page="./Delete" asp-route-id="#album">Delete</a></td> is right, but when I try to plug the AlbumId in there instead of just album, I always get errors. Any help please? TIA.
According to your code and description, it looks that in the Album.cshtml page, you will list the albums, and if click the "delete" hyperlink, it will redirect to the Delete.cshtml page and show the selected album, after that, click the "Delete" button to delete the item and redirect to the Index page. If that is the case, please refer the following steps to modify your code.
First, in the Album.cshtml page:
Since the Delete.cshtml page located in the "Albums" folder, when you add the hyperlink (<a> tag), the asp-page attribute should like this: asp-page="/Albums/Delete".
The <a> tag like this: <a asp-page="/Albums/Delete" asp-route-id="#album">Delete</a>.
Second, in the Delete.cshtml.cs file:
If you check the Album.cshtml.cs file or use F12 developer tool to check the parameter, you can see the id is a string value, instead of int type. So, change the code as below:
public async Task<IActionResult> OnGetAsync(string? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
//using string.split method to split the id parameter, and get the albumid.
var albumid = System.Convert.ToInt32(id.ToString().Split('.')[0]);
Album = await _context.Albums
.AsNoTracking()
.FirstOrDefaultAsync(m => m.AlbumId == albumid);
if (Album == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}
return Page();
}
and in the Delete.cshtml page, you should also add the tag helper reference at the head of the page, and use asp-route-id attribute to pass the albumid to the Post method, code like this:
#page "{id?}"
#model Project.Pages.Albums.DeleteModel
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div>
<form method="post">
#*<input type="hidden" asp-for="Album.AlbumId" />*#
<input type="submit" value="Delete" asp-route-id="#Model.Album.AlbumId" class="btn btn-danger" /> |
<a asp-page="/Index">Back to List</a>
</form>
</div>
After modified, the result as below (After test your sample on my machine, I have deleted them):
Here are some references about Asp.net Core Razor Page routing and event handler, you could refer them:
Razor Pages in ASP.NET Core
Razor Pages Routing
Handler Methods in Razor Pages
The detail updated page resource as below:
album.cshtml:
#page
#model Project.Pages.AlbumsModel
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div class="row">
<h1 class="display-2">Albums</h1>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Album ID, artist ID, Album title</th>
</tr>
</thead>
<tbody>
#foreach (string album in Model.Albums)
{
<tr>
<td>#album</td>
<td><a asp-page="/Albums/Delete" asp-route-id="#album">Delete</a></td>
</tr>
}
</tbody>
</table>
</div>
<div class="row">
<p>Enter a title & ArtistID for a new album: </p>
<form method="POST">
<div><input asp-for="Album.Title" /></div>
<div><input asp-for="Album.ArtistId" /></div>
<input type="submit" />
</form>
#* <p>Enter a value and Album ID for a new album title: </p>
<form method="POST">
<div><input asp-for="Album.Title" /></div>
<div><input asp-for="Album.AlbumId" /></div>
<input type="submit" />
</form> *#
</div>
album.cshtml.cs:
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Project.Models;
using System;
namespace Project.Pages
{
public class AlbumsModel : PageModel
{
private Chinook db;
public AlbumsModel(Chinook injectedContext)
{
db = injectedContext;
}
public IEnumerable<string> Albums { get; set; }
public void OnGet()
{
ViewData["Title"] = "Chinook Web Site - Albums";
// Albums = db.Albums.Select(s => s.Title);
Albums = db.Albums.Select(s => s.AlbumId.ToString() + ". " + s.ArtistId + ". " + s.Title);
}
[BindProperty]
public Album Album { get; set; }
public IActionResult OnPost()
{
if (ModelState.IsValid)
{
db.Albums.Add(Album);
db.SaveChanges();
return RedirectToPage("/albums");
}
return Page();
}
// void RemoveAlbum(int albumId)
// {
// var album = db.Albums.Find(albumId);
// if (album == null)
// throw new ArgumentOutOfRangeException();
// db.Albums.Remove(album);
// db.SaveChanges();
// }
public IActionResult DeleteAlbum(int AlbumId)
{
var album = db.Albums.Find(AlbumId);
if (album == null) return Page();
db.Albums.Remove(album); db.SaveChanges(); return RedirectToPage("/albums");
}
}
}
Delete.cshtml:
#page "{id?}"
#model Project.Pages.Albums.DeleteModel
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#{ ViewData["Title"] = "Delete"; }
<h1>Delete</h1>
<p class="text-danger">#Model.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Album</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.Album.Title)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.Album.Title)
</dd>
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.Album.ArtistId)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.Album.ArtistId)
</dd>
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.Album.AlbumId)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.Album.AlbumId)
</dd>
</dl>
<form method="post">
#*<input type="hidden" asp-for="Album.AlbumId" />*#
<input type="submit" value="Delete" asp-route-id="#Model.Album.AlbumId" class="btn btn-danger" /> |
<a asp-page="/Index">Back to List</a>
</form>
</div>
Delete.cshtml.cs:
#region snippet_All
using Project.Models; //using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace Project.Pages.Albums //ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly Chinook _context;
public DeleteModel(Chinook context)
{
_context = context;
}
[BindProperty]
public Album Album { get; set; }
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnGetAsync(string? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
var albumid = System.Convert.ToInt32(id.ToString().Split('.')[0]);
Album = await _context.Albums
.AsNoTracking()
.FirstOrDefaultAsync(m => m.AlbumId == albumid);
if (Album == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var album = await _context.Albums.FindAsync(id);
if (album == null)
{
return NotFound();
}
try
{
//_context.Albums.Remove(album);
//await _context.SaveChangesAsync();
return RedirectToPage("/Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("/Albums/Delete",
new { id, saveChangesError = true });
}
}
}
}
#endregion

Trying to update my users password using AddPasswordAsync but passwords always left as null

So i've got 2 tables one for Editing user roles and one fore editing user accounts the roles table allows me to delete and add roles perfectly fine but i'm having trouble with updating passwords. The passwords seem to be getting deleted but not updated with the new one specified.
<br>
<h3>Roles Table</h3>
<table class="table table-striped">
<thead>
<tr> <th>Id</th><th>User Roles</th> </tr>
</thead>
<tbody>
#foreach (var roles in Model.roles)
{
<tr>
<td> #roles.Id</td>
<td> #roles.Name</td>
<td><a class="btn btn-sm btn-danger order-button float-right" asp-page="/ManageRoles" asp-route-id="Delete" asp-page-handler="Delete">Delete Roles </a></td>
</tr>
}
</tbody>
</table>
<form method="post">
<div class="flex">
<div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<div class="input-group">
<input type="text" class="form-control" asp-for="#Model.roleName">
</div>
</div>
</form>
<table class="table table-striped" style="margin-top: 100px;">
<thead>
<tr> <th>Id</th><th>User Account</th> </tr>
</thead>
<tbody>
#foreach (var users in Model.users)
{
<tr>
<td> #users.Id</td>
<td> #users.Email</td>
<td><a class="btn btn-sm btn-danger order-button float-right" asp-page="/ManageRoles" asp-route-id="DeleteUser" asp-page-handler="DeleteUser">Delete User </a></td>
<td>
<form method="post" asp-page-handler="Update" asp-route-id="#users.Id">
<div class="input-group">
<input type="text" class="form-control" asp-for="#Model.NewPassword">
<button type="submit" class="btn btn-default">Update Password </button>
</div>
</form>
</td>
</tr>
}
</tbody>
</table>
So heres my Page model class below
{
private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<AppUser> _userManager;
public List <IdentityRole> roles { get; set; }
[BindProperty(SupportsGet = true)]
public string Id { get; set; }
[BindProperty]
public string roleName { get; set; }
public List <AppUser> users { get; set; }
[BindProperty]
public string userId { get; set; }
[BindProperty]
public string NewPassword { get; set; }
public AdminDashboardModel(RoleManager<IdentityRole> roleManager, UserManager<AppUser> userManager)
{
_roleManager = roleManager;
_userManager = userManager;
}
public void OnGet()
{
roles = _roleManager.Roles.ToList();
users = _userManager.Users.ToList();
}
public async Task <IActionResult> OnGetDeleteAsync()
{
var role = await _roleManager.FindByIdAsync(Id);
await _roleManager.DeleteAsync(role);
return RedirectToPage("/AdminDashboard");
}
public async Task<IActionResult> OnPostAsync()
{
if (roleName != null)
await _roleManager.CreateAsync(new IdentityRole(roleName.Trim()));
return RedirectToPage("/AdminDashboard");
}
public async Task<IActionResult> OnGetDeleteUserAsync()
{
var user = await _userManager.FindByIdAsync(Id);
await _userManager.DeleteAsync(user);
return RedirectToPage("/AdminDashboard");
}
public async Task<IActionResult> OnPostUpdateAsync()
{
var user = await _userManager.FindByIdAsync(Id);
await _userManager.RemovePasswordAsync(user);
await _userManager.AddPasswordAsync(user, NewPassword);
return RedirectToPage("/AdminDashboard");
}
}
}
Try it with this bit of code:
public async Task<IActionResult> OnPostUpdateAsync()
{
var user = await _userManager.FindByIdAsync(Id);
var token = await _userManager.GeneratePasswordResetTokenAsync(user)
var result = await _userManager.ResetPasswordAsync(user, token, NewPassword);
//validate result
if(result.Succeeded) {
return RedirectToPage("/AdminDashboard");
}
//handle errors
throw new Exception()
}
You don't actually need to validate the result, but things tend to go sideways. Always better to have error handling.
The generated token validates the action of changing the password against the system. If a user would want to reset their password, because they forgot theirs, you would also need to generate such a token, send it to their email and then let the user choose a new password.
Alternatively you could just use UserManager.UpdatePasswordHash:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.usermanager-1.updatepasswordhash?view=aspnetcore-5.0
var result = await _userManager.UpdatePasswordHash(user, NewPassword, true|false);

Add Roles to User using checkboxes

I am developing a Razor application and I need the Admin to assign or update roles of the Users. I am using a checkbox to carry out this action. So far, I have been able to populate the view to show the users and their respective roles ticked in the checkbox, but I haven't been able to update their roles, as anytime a checkbox is ticked to add a role, it doesn't add the role to the user. I think this line is the culprit: var selectedRoles = model.Where(x => x.Selected).Select(y => y.RoleName);.
This is the Model:
public class ManageUserRolesViewModel
{
public string RoleId { get; set; }
public string RoleName { get; set; }
public bool Selected { get; set; }
}
This is the Page:
<form method="post">
<div class="card">
<div class="card-header">
<h2>Manage User Roles</h2>
Add / Remove Roles for #Model.UserID
</div>
<div class="card-body">
#foreach (var x in Model.UserRoles)
{
<div class="form-check m-1">
<input type="hidden" asp-for="#x.RoleId" />
<input type="hidden" asp-for="#x.RoleName" />
<input asp-for="#x.Selected" class="form-check-input" />
<label class="form-check-label" asp-for="#x.Selected">
#x.RoleName
</label>
</div>
}
<div asp-validation-summary="All" class="text-danger"></div>
</div>
<div class="card-footer">
<input type="submit" value="Update" class="btn btn-primary" style="width:auto" />
<a asp-page="/Account/UserManagement/UserList" class="btn btn-primary" style="width:auto">Cancel</a>
</div>
</div>
</form>
This is the Page Model:
public class ManageModel : PageModel
{
private readonly RoleManager<IdentityRole> _roleManager;
private readonly BankAssesmentApplicationIdentityDbContext _db;
private readonly UserManager<IdentityUser> _userManager;
public ManageModel(
RoleManager<IdentityRole> roleManager,
UserManager<IdentityUser> userManager,
BankAssesmentApplicationIdentityDbContext db)
{
_db = db;
_userManager = userManager;
_roleManager = roleManager;
}
public IList<ManageUserRolesViewModel> UserRoles = new List<ManageUserRolesViewModel>();
public string UserID { get; set; }
public async Task<IActionResult> OnPostAsync(List<ManageUserRolesViewModel> model, string userId)
{
UserID = userId;
if (ModelState.IsValid)
{
IdentityUser user = await _userManager.FindByNameAsync(userId);
if (user == null)
{
return Page();
}
var roles = await _userManager.GetRolesAsync(user);
var result = await _userManager.RemoveFromRolesAsync(user, roles);
if (!result.Succeeded)
{
ModelState.AddModelError("", "Cannot remove user existing roles");
return Page();
}
var selectedRoles = model.Where(x => x.Selected).Select(y => y.RoleName);
await _userManager.AddToRolesAsync(user, selectedRoles);
if (!result.Succeeded)
{
ModelState.AddModelError("", "Cannot add selected roles to user");
return Page();
}
return RedirectToPage("/Account/UserManagement/UserList");
}
return Page();
}
public async Task<IActionResult> OnGetAsync(string userId)
{
UserID = userId;
var user = await _userManager.FindByEmailAsync(userId);
if (user == null)
{
return Page();
}
var model = new List<ManageUserRolesViewModel>();
foreach (var role in _roleManager.Roles.ToList())
{
ManageUserRolesViewModel roles = new ManageUserRolesViewModel
{
RoleId = role.Id,
RoleName = role.Name,
};
UserRoles.Add(roles);
if (await _userManager.IsInRoleAsync(user, role.Name))
{
roles.Selected = true;
}
else
{
roles.Selected = false;
}
model.Add(roles);
}
return Page();
}
}
Firsly,you need know that for each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.Your backend wants to receive a list model,so what you pass should be [index].PropertyName.But what you did will result in serveral inputs with the same name,the model binding system could not match the value for the list.
Then you need know that asp-for="#x.Selected" will generate the value for checkbox,but it will not change the value when you change the checkbox state,you need create a click event to change the value:
<input asp-for="#x.Selected" onclick="$(this).val(this.checked ? true : false)"/>
What you need change like below:
<form method="post">
<div class="card">
<div class="card-header">
<h2>Manage User Roles</h2>
Add / Remove Roles for #Model.UserID
</div>
<div class="card-body">
#*Begin change*#
#{ int i = 0;}
#foreach (var x in Model.UserRoles)
{
<div class="form-check m-1">
<input type="hidden" asp-for="#x.RoleId" name="[#i].RoleId"/>
<input type="hidden" asp-for="#x.RoleName" name="[#i].RoleName"/>
<input asp-for="#x.Selected" name="[#i].Selected" class="form-check-input" onclick="$(this).val(this.checked ? true : false)"/>
<label class="form-check-label" asp-for="#x.Selected">
#x.RoleName
</label>
</div>
i++;
}
#*End change*#
<div asp-validation-summary="All" class="text-danger"></div>
</div>
<div class="card-footer">
<input type="submit" value="Update" class="btn btn-primary" style="width:auto" />
<a asp-page="/Account/UserManagement/UserList" class="btn btn-primary" style="width:auto">Cancel</a>
</div>
</div>
</form>
Result:
#user:3843256 have a look at this sample it works ok, the only difference is you are using for each, that means bind does not take place, change to counter based index binding
Checkbox list binding

Process ModelState errors from WebAPI Blazor (server-side) call

When I am making a WebAPI call I want to find out how to pass back ModelState errors to my Blazor application.
The DataAnnotations all validate correctly but, if I do any other types of validation (once past the ModelState.IsValid call), I can't get those errors that I add to the ModelState to pass back to Blazor. What am I missing?
Blazor page
...
<EditForm Model="#_user" OnValidSubmit="#Update">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="container">
<div class="row">
<div class="col-md-3">Name:</div>
<div class="col-md-9"><InputText id="Name" #bind-Value="#_user.UserName" class="form-control" /></div>
</div>
#if (_user.isNew)
{
<div class="row">
<div class="col-md-3">Password:</div>
<div class="col-md-9"><InputText id="Name" #bind-Value="#_user.Password" class="form-control" type="password" /></div>
</div>
<div class="row">
<div class="col-md-3">Validate your password:</div>
<div class="col-md-9"><InputText id="Name" #bind-Value="#_user.ValidatePassword" class="form-control" type="password" /></div>
</div>
}
<div class="row">
<div class="col-md-3">Email:</div>
<div class="col-md-9"><InputText id="Name" #bind-Value="#_user.Email" class="form-control" /></div>
</div>
<div class="row">
<div class="col-md-3">Roles:</div>
<div class="col-md-9">
#foreach (IdentityRole role in _roles)
{
bool isChecked = _user.UserRoles.Any(r => r.Id == role.Id);
<input type="checkbox" Id="#role.Id" name="#role.Id"
Class="form-control" checked="#isChecked" #onchange="#(e => RoleChecked(e, role.Id))" />
#role.Name
}
</div>
</div>
<button type="submit" class="btn btn-#buttonClass">#buttonText</button>
</div>
</EditForm>
}
#functions {
[Parameter]
string id { get; set; } = "";
private UserViewModel _user { get; set; }
...
private async Task Update()
{
if (id != "")
{
await apiClient.UpdateUserAsync(_user);
}
else
{
await apiClient.AddUserAsync(_user);
}
UriHelper.NavigateTo("admin/users");
}
...
WebAPI Controller
[HttpPost]
public async Task<IActionResult> Post([FromBody] UserViewModel applicationUser)
{
if (applicationUser == null)
{
_logger.LogError($"ApplicationUser object null");
return BadRequest("ApplicationUser object is null");
}
if (!ModelState.IsValid)
{
_logger.LogWarn("ApplicationUser object invalid");
return BadRequest(ModelState);
}
else
{
_logger.LogDebug($"Creating ApplicationUser {applicationUser.UserName}");
var obj = new ApplicationUser();
obj.Map(applicationUser);
IdentityResult result = await _userManager.CreateAsync(obj, applicationUser.Password);
if (result.Succeeded)
{
//put the newly created user back on top of the parameter for role creation
applicationUser.Map(obj);
await IdentityHelpers.UpdateUserRoles(_userManager, applicationUser);
return CreatedAtRoute("", new { id = applicationUser.Id }, applicationUser);
}
else
{
_logger.LogWarn("ApplicationUser object could not be created");
result.Errors.ToList().ForEach(e => ModelState.AddModelError(e.Code, e.Description));
return BadRequest(ModelState);
}
}
}
How do I pass back ModelState errors to Blazor so that it will respond to those in the same way that it would DataAnnotations (or model validation)?

Asp.net core razor pages [BindProperty] doesnt work on collections

Im trying to use [BindProperty] annotation in asp.net core razor pages in order to Bind an Ilist<T> collection of one of my model classes so i can edit some of them at once, but it doesnt work at all, every time in OnPostAsync function the collection is empty, and neither the changes that i made on data nor it default values wont post back to the server, but when its a singel object [BindProperty] works fine and the values post back and can be changed, i also tried wraping a collection (i.e list<T>) in an object but it didnt work either way, so is there any way for doing so or i should lets say send a edit request for every object in that collection and edit them one by one(which cant be done in razor pages easilly and need some ajax calls)??
For binding IList between RazorPage and PageModel, you will need to use Product[i].Name to bind property.
Here are complete steps.
Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
PageModel
public class IndexModel : PageModel
{
private readonly CoreRazor.Data.ApplicationDbContext _context;
public IndexModel(CoreRazor.Data.ApplicationDbContext context)
{
_context = context;
}
[BindProperty]
public IList<Data.Product> Product { get; set; }
public async Task OnGetAsync()
{
Product = await _context.Product.ToListAsync();
}
public async Task OnPostAsync()
{
var product = Product;
}
}
View
<form method="post">
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Product[0].Name)
</th>
<th></th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Product.Count(); i++)
{
<tr>
<td>
<input hidden asp-for="Product[i].Id" class="form-control"/>
<input asp-for="Product[i].Name" class="form-control" />
</td>
<td>
<a asp-page="./Edit" asp-route-id="#Model.Product[i].Id">Edit</a> |
<a asp-page="./Details" asp-route-id="#Model.Product[i].Id">Details</a> |
<a asp-page="./Delete" asp-route-id="#Model.Product[i].Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>

Resources