Process ModelState errors from WebAPI Blazor (server-side) call - asp.net-core-webapi

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)?

Related

I am getting null value in the server side from CheckBox in asp.net MVC

When I check the box, it shows it has a false value in the server side model but in the ajax call getting values it shows null values.
In the model class:
[Display(Name = "Is Buyer")]
public bool IsBuyer { get; set; }
In the Razor Page:
<div class="row g-my-4">
<div class="col-md-10 g-pl-0">
<div class="row">
<div class="col-md-1"> </div>
<div class="col-md-7 g-pl-0 g-pr-0" id="IsBuyer">
<label>
#Html.CheckBoxFor(m=>m.IsBuyer)
#*#Html.CheckBoxFor(model => model.IsBuyer, new { id = "CheckBuyer" })*#
#*#Html.CheckBox("IsBuyer")*#
Buyer
</label>
</div>
</div>
</div>
</div>
I tried to use everything but nothing is working.
Getting data values to use ajax call
Here you can see the IsBuyer attribute is null = empty, and here on the server side it's getting false value however I have checked every check box but that's what I am getting back
Try to use usual BeginForm() block and post data like below:
#model Models.ViewModel
#using (Html.BeginForm())
{
<div class="form-group row">
<p>#Html.LabelFor(l => Model.IsBuyer) </p>
#Html.CheckBoxFor(r => Model.IsBuyer)
</div>
<input id="Button" type="submit" value="Save" />
}
In the controller code:
public IActionResult IndexTest()
{
var model = new ViewModel() { IsBuyer = true };
return View(model);
}
[HttpPost]
public IActionResult IndexTest(ViewModel data)
{
if (ModelState.IsValid)
{
// your code here...
}
return RedirectToAction("IndexTest", data);
}
// View data model class
public class ViewModel
{
[Display(Name = "Is Buyer")]
public bool IsBuyer { get; set; }
// Additional propereties is defined here...
}

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

Razor PageModel RedirectToRouteResult does not work when calling OnPost

I'm trying to implement simple Asp.net core web application login flow.
The LoginModel
namespace Trading_System.UI.Pages.Account
{
public class LoginModel : PageModel
{
[BindProperty]
public string Username { get; set; }
[BindProperty]
public string Password { get; set; }
public string ReturnUrl { get; set; }
public string ErrorMessage { get; set; }
private IUserManager m_userManager;
public LoginModel(IUserManager userManager)
{
m_userManager = userManager;
}
public void OnGet(string returnUrl)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPost()
{
var user = m_userManager.GetUser(Username, Password);
if (user == null)
{
ErrorMessage = "Username or password are invalid.";
return Page();
}
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, user.UserName)
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return new RedirectToRouteResult(ReturnUrl);
}
public async Task<IActionResult> OnPostLogout(string returnUrl)
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return new RedirectToPageResult("/index");
}
}
}
The cshtml code of Login
#page
#model Account.LoginModel
#{
ViewData["Title"] = "Login";
}
<div class="login-page">
<div class="page-header">
<h1>Login</h1>
</div>
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Please enter your credentials</h3>
</div>
<div class="panel-body">
<form method="post">
<fieldset>
<div class="form-group">
<label asp-for="Username"></label>
<input class="form-control" placeholder="Username" asp-for="Username" autofocus>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input type="password" class="form-control" placeholder="Password" asp-for="Password" autocomplete="off">
</div>
<div class="form-group">
<button class="btn btn-primary">Login</button>
</div>
#if(!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-warning">#Model.ErrorMessage</div>
}
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
I'm setting the ReturnUrl property when OnGet is called (for example "https://localhost:44389/Account/Login?ReturnUrl=%2FSecretPage") But I've also tried to set it manually so the problem is not there.
After I'm pressing Login I get redirected to the login page no matter what is the ReturnUrl given (I can see in chrome the 302 response).
The url after the submit is "https://localhost:44389/Account/Login?Length=14"
What am I doing wrong? I just want to redirect back to the route I was at before redirected to the login page.
RedirectToRouteResult does a redirect to a route where a route is a named route that is configured through the MVC router.
As your example shows, the ReturnUrl that gets passed to your login action is a relative URL though: /Account/Login?ReturnUrl=%2FSecretPage. So ReturnUrl is "/SecretPage".
A relative URL is usually equal to a named route, so using RedirectToRoute will not work here. What you can do instead is return a LocalRedirectResult:
return LocalRedirect(ReturnUrl);

Why is my validation code for .net core 3. not working as intended?

I am trying to make a validation for my report form where the report can only be submited if the email = to "Sample#email.com". My problem is the code works perfectly without the validation but when I include the validation code if (!ModelState.IsValid){return View("Create");} into the my controller the validation works perfectly but when the email is correct it just refreshes the page without submiting it or redirecting it to the submit view
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Submit(Reports report)
{
if (!ModelState.IsValid)
{
return View("Create");
}
_reportRepository.CreateReport(report);
return View();
Validation Code:
public class EmailValidation : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var report = (Reports)validationContext.ObjectInstance;
if (report.Email == "Sample#email.com")
{
return ValidationResult.Success;
}
return new ValidationResult("Invalid email");
}
}
Report:
public class Reports
{
[Key]
public int ReportId { get; set; }
[Required(ErrorMessage = "Please enter email.")]
[Display(Name = "Email :")]
[EmailValidation(ErrorMessage ="enter valid email")]
public string Email { get; set; }
}
Create View:
#model Reports
#using Microsoft.AspNetCore.Identity
#inject UserManager<IdentityUser> UserManager
<body>
<form asp-action="Submit" method="post" role="form">
<div class="container1">
<div class=" form-group row">
<label asp-for="Email" class="col-md-2 control-label"></label>
<div class="col-md-3">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group row float-right">
<div class="col-md-offset-2 col-md-5 float-md-left">
<input type="submit" class="btn btn-primary" value="Submit" />
</div>
</div>

ModelState.AddModelError not displaying error in view - ASP.NET

I have been banging my head against the wall trying to solve this issue.
I have this view controller
public class LoginController : Controller
{
public ActionResult Index(LoginClass model)
{
return View(model);
}
[HttpPost]
public ActionResult Login(LoginClass model, string ReturnUrl)
{
if (!this.ModelState.IsValid)
{
return this.View(model);
}
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
&& !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError(string.Empty, "The user name or password provided is incorrect");
}
}
return RedirectToAction("Index", "Login", model);
}
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
}
and here is my view:
<section id="login">
<div class="container">
<form action="~/Login/Login" id="Login" method="post">
<div class="row">
<div class="col-md-12">
<p>
<label for="username">Username</label>
<input type="text" id="username" name="username" class="form-control" />
</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p>
<label for="password">Password</label>
<input type="password" id="password" name="password" class="form-control" />
</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p>
<input type="submit" id="submit" name="submit" value="Login" class="btn btn-default" />
</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p>
<label for="password">Remember Me?</label>
<input type="checkbox" id="chkPersist" name="chkPersist" />
</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p>
#Html.ValidationSummary()
</p>
</div>
</div>
</form>
</div>
</section>
My issue is that my error message is not appearing when I enter the wrong username and password. Why is not displaying?
The problem is with the last line in your Login method. You are calling RedirectToAction instead of View. This has the consequence that you lose all your view specific state including the model state and validation errors that you built up in your Login Action. You can change your Login method like so (I simplified it a little), really the only change is replacing RedirectToAction("Index", "Login", model) with View(model) on the last line.
If you do want to redirect in the event of authentication failure and you did want to use RedirectToAction then see my other answer I posted here.
[HttpPost]
public ActionResult Login(LoginClass model, string ReturnUrl)
{
if (!this.ModelState.IsValid)
return this.View(model);
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
&& !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
ModelState.AddModelError(string.Empty, "The user name or password provided is incorrect");
return this.View(model); // return to the same view to show your error message
// return RedirectToAction("Index", "Login", model); // do not redirect
}
You need to add a placeholder by using the Html Helper function to display the message.
<div>#Html.ValidationMesssage("KeyName")</div>
to your view so that you can display the validation message.
The KeyName comes from controller
ModelState.AddModelError("KeyName", "The user name or password provided is incorrect");
Also you may need to ensure client side validation is enabled in your web.config.(usually they are)
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

Resources