ASP.NET Core 2.2 is not firing Validate method in IValidatableObject - asp.net-mvc

I am trying to do some custom validation but I cannot seem to get the validation method to fire. Please let me know what I might be missing. Thanks!
Following is my code:
Model:
public class TestModel : IValidatableObject
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (FirstName == "Hello")
yield return ValidationResult.Success;
else
yield return new ValidationResult("Error from Validate method");
}
}
View:
#model EswSigningServer.Models.TestModel
#{
ViewData["Title"] = "Test";
}
<h2>Test</h2>
<h4>TestModel</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Test">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Test" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Controller:
public ActionResult Test()
{
TestModel createKey = new TestModel();
return View(createKey);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(IFormCollection collection)
{
try
{
if (ModelState.IsValid)
{
// TODO: Add insert logic here
return RedirectToAction(nameof(Index));
}
else
{
return View();
}
}
catch
{
return View();
}
}
I've also tried adding the validator to a single attribute using the following method and that doesn't fire either:
public class MyValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return new ValidationResult("Hello");
}
}

IValidatableObject is a class-level validation. When model binding , it will call the Validate method determines whether the specified object is valid .If you want to get the validation method to fire , you should change the parameter type of Test method to TestModel as shown:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(TestModel model)

Related

how can i show the data that the user entered in the file input when he want to edit it

for your information the file is saved as a binary data
here is is the model:
public class Movie
{
[Key]
public int MovieId { get; set; }
public string MovieName { get; set; }
public string MovieDescription { get; set; }
public byte[] MovieImage { get; set; }
public string MovieTrailer { get; set; }
//FK
public int AdminId { get; set; }
//Navigation
public Admin Admin { get; set; }
public ICollection<Event> Events { get; set; }
public AddingCategory AddingCategory { get; set; }
public ViewingMovie ViewingMovie { get; set; }
}
}
And here is the controller :
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.tblMovies.FindAsync(id);
if (movie == null)
{
return NotFound();
}
ViewData["AdminId"] = new SelectList(_context.Set<Admin>(), "AdminId", "Email", selectedValue: movie.AdminId);
return View(movie);
}
// POST: Movies/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("MovieId,MovieName,MovieDescription,MovieImage,MovieTrailer,AdminId")] Movie movie, IFormFile MovieImage)
{
if (id != movie.MovieId)
{
return NotFound();
}
if (MovieImage != null)
{
//This code is used to copy image to DataBase
using (var myStream = new MemoryStream())
{
await MovieImage.CopyToAsync(myStream);
movie.MovieImage = myStream.ToArray();
}
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.MovieId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["AdminId"] = new SelectList(_context.Set<Admin>(), "AdminId", "Email", movie.AdminId);
return View(movie);
}
and the last here is the edit view:
<div class="row">
<div class="col-md-4">
<form asp-action="Edit" method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="MovieId" />
<div class="form-group">
<label asp-for="MovieName" class="control-label"></label>
<input asp-for="MovieName" class="form-control" />
<span asp-validation-for="MovieName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="MovieDescription" class="control-label"></label>
<input asp-for="MovieDescription" class="form-control" />
<span asp-validation-for="MovieDescription" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="MovieImage" class="control-label"></label>
<input asp-for="MovieImage" class="form-control" type="file" />
<span asp-validation-for="MovieImage" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="MovieTrailer" class="control-label"></label>
<input asp-for="MovieTrailer" class="form-control" />
<span asp-validation-for="MovieTrailer" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="AdminId" class="control-label"></label>
<select asp-for="AdminId" class="form-control" asp-items="ViewBag.AdminId"></select>
<span asp-validation-for="AdminId" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
all i want is to show what the user entered in the file input and if he want to edit it or keep as it is.
i have been struggling with this problem in days PLEASE somebody help me out

InvalidOperationException: Model item passed in the ViewDataDictionary is of type 'System.Object'

I encountered the problem when I was creating a Create page. Which lead me to an error message which says:
Error Message:
Im not entirely sure if my model name is correct.
Below are my following codes:
Songs Controller:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.SqlClient;
using System.Data;
using System.Data.SqlClient;
using System;
using Garcia___MVC.Models;
namespace Garcia___MVC.Controllers
{
public class SongsController : Controller
{
private const string ConnectionString =
#"*";
// GET: SongsController
[HttpGet]
public ActionResult Index()
{
DataTable dtbSongs = new DataTable();
using(SqlConnection sqlCon = new SqlConnection(ConnectionString))
{
sqlCon.Open();
SqlDataAdapter sqlDa = new SqlDataAdapter("SELECT * FROM Songs",sqlCon);
sqlDa.Fill(dtbSongs);
}
return View(dtbSongs);
}
// GET: SongsController/Create
[HttpGet]
public ActionResult Create(object model)
{
return View(model);
}
// POST: SongsController/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: SongsController/Edit/5
public ActionResult Edit(int id)
{
return View();
}
// POST: SongsController/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
// GET: SongsController/Delete/5
public ActionResult Delete(int id)
{
return View();
}
// POST: SongsController/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id, IFormCollection collection)
{
try
{
return RedirectToAction(nameof(Index));
}
catch
{
return View();
}
}
}
}
Create Page that can't be accessed:
#model Garcia___MVC.Models.SongsModel
#{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>SongsModel</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="SongID" class="control-label"></label>
<input asp-for="SongID" class="form-control" />
<span asp-validation-for="SongID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SongTitle" class="control-label"></label>
<input asp-for="SongTitle" class="form-control" />
<span asp-validation-for="SongTitle" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Artists" class="control-label"></label>
<input asp-for="Artists" class="form-control" />
<span asp-validation-for="Artists" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Album" class="control-label"></label>
<input asp-for="Album" class="form-control" />
<span asp-validation-for="Album" class="text-danger"></span>
</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");}
}
SongsModel.cs
using System;
namespace Garcia___MVC.Models
{
public class SongsModel
{
public int SongID { get; set; }
public string SongTitle { get; set; }
public string Artists { get; set; }
public string Album { get; set; }
}
}
Would appreciate any advice or tips on how I can go about this. Will be very much appreciated.
I was expecting a page which you can create the Song title, Artists, Album, and Song ID.
The return type of the action in the songsController must be SongsModel.
The type you specify at the top of the view page determines the return type.
I'm sorry for my bad english.

I can't make client side validation working on an ASP.NET application

my issue
I try to create a custom validator with client validation.
But client validation side doesn't work, I can't understand why.
This is a .NET Core 3.1 application.
my code
The validator:
public class ExcludeCharAttribute : ValidationAttribute, IClientValidatable
{
private readonly string _chars;
public ExcludeCharAttribute(string chars)
: base("{0} contains invalid character.")
{
_chars = chars;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var valueAsString = value.ToString();
for (int i = 0; i < _chars.Length; i++)
{
if (valueAsString.Contains(_chars[i]))
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
}
return ValidationResult.Success;
}
//new method
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationParameters.Add("chars", _chars);
rule.ValidationType = "exclude";
yield return rule;
}
}
For testing I created this test dto:
public class TestDto
{
[ExcludeChar("123456")]
public string Prop1 { get; set; }
[Required]
public string Prop2 { get; set; }
}
and that form:
<div class="row">
<div class="col-md-4">
<form asp-action="TestValidator">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Prop1" class="control-label"></label>
<input asp-for="Prop1" class="form-control" />
<span asp-validation-for="Prop1" class="text-danger"></span>
<label asp-for="Prop2" class="control-label"></label>
<input asp-for="Prop2" class="form-control" />
<span asp-validation-for="Prop2" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script src="~/js/validator.js"></script>
}
And of course this validation js (validator.js):
$.validator.unobtrusive.adapters.addSingleVal("exclude", "chars");
$.validator.addMethod("exclude", function (value, element, exclude) {
if (value) {
for (var i = 0; i < exclude.length; i++) {
if (jQuery.inArray(exclude[i], value) != -1) {
return false;
}
}
}
return true;
});
What happen
When testing no client validation is run.
When inspecting code I don't see any data-* attributes for Prop1:
But it's OK for Prop2.
I think this is why it does not work.
What I tested
Tested other online samples.
Tested I loaded all the JS:
Tested CDN version of the scripts
What I need
Now I have no idea what to do. Does anybody have an ideau about what is lacking?
thank you
I copied your custom validation code and test in my side but it worked well, could you pls check the differences between your code and mine? I'm sure the view code is the same, and in validation class you may check the using packages.
validation class:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace WebAppMvc.Models
{
public class ExcludeCharAttribute : ValidationAttribute, IClientValidatable
{
private readonly string _chars;
public ExcludeCharAttribute(string chars)
: base("{0} contains invalid character.")
{
_chars = chars;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var valueAsString = value.ToString();
for (int i = 0; i < _chars.Length; i++)
{
if (valueAsString.Contains(_chars[i]))
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
}
return ValidationResult.Success;
}
//new method
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationParameters.Add("chars", _chars);
rule.ValidationType = "exclude";
yield return rule;
}
}
}
Model:
using System.ComponentModel.DataAnnotations;
namespace WebAppMvc.Models
{
public class UserModel
{
[Key]
[ExcludeChar("123456")]
public string id { get; set; }
public int userId { get; set; }
}
}
And this is my controller method:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create([Bind("id,userId")] UserModel user)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
return View(user);
}
This is the view:
#model WebAppMvc.Models.UserModel
<h4>Test</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="id" class="control-label"></label>
<input asp-for="id" class="form-control" />
<span asp-validation-for="id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="userId" class="control-label"></label>
<input asp-for="userId" class="form-control" />
<span asp-validation-for="userId" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>

Model binding null values in MVC Core

I am trying to post data from form to action method but looks like model binder is not binding to the entities and I am getting null values in the action method, your help is much appreciated
please find the code snippet below.
Action method
[HttpPost]
public async Task<IActionResult> Update(EditRoleViewModel model)
{
if (ModelState.IsValid)
{
var role = await _Roleame.FindByIdAsync(model.Id);
if (role == null)
{
return RedirectToAction("notfound");
}
role.Name = model.RoleName;
var result = await _Roleame.UpdateAsync(role);
if (result.Succeeded)
{
return RedirectToAction("getAllRoles", "Administrator");
}
}
else {
ModelState.AddModelError(string.Empty, "Error");
}
return View();
}
Model
public class EditRoleViewModel
{
public EditRoleViewModel()
{
names = new List<string>();
}
public string Id;
[Required]
public string RoleName;
public List<string> names;
}
View
#model FirstCoreApplication.Model.EditRoleViewModel
<script src="~/twitter-bootstrap/js/bootstrap.js"></script>
<link href="~/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
#{
ViewBag.Title = "Edit Role";
}
<h1>Edit Role</h1>
<form method="post" class="mt-3" asp-controller="Administrator" asp-action="Update">
<div class="form-group row">
<label asp-for="Id" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="Id" disabled class="form-control">
</div>
</div>
<div class="form-group row">
<label asp-for="RoleName" class="col-sm-2 col-form-label"></label>
<div class="col-sm-10">
<input asp-for="RoleName" class="form-control">
<span asp-validation-for="RoleName" class="text-danger"></span>
</div>
</div>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">Update</button>
<a asp-action="ListRoles" class="btn btn-primary">Cancel</a>
</div>
</div>
<div class="card">
<div class="card-header">
<h3>Users in this role</h3>
</div>
<div class="card-body">
#if (Model.names.Any())
{
foreach (var user in Model.names)
{
<h5 class="card-title">#user</h5>
}
}
else
{
<h5 class="card-title">None at the moment</h5>
}
</div>
<div class="card-footer">
Add Users
Remove Users
</div>
</div>
</form>
enter code here
I've spent couple of hours looking for an error in my code and the reason for the binder not working in my case was using a model with public fields rather than public properties:
public class EditRoleViewModel
{
public EditRoleViewModel()
{
names = new List<string>();
}
public string Id { get; set; }
[Required]
public string RoleName { get; set; }
public List<string> names { get; set; }
}
The form data you post does not match the property of your model.You do not post the parameter names .
public List names { get; set; }

How do I store user input into database using ASP.NET Core MVC?

I am struggling to find a solution on how to add user input like first name, last name, zipcode.... to the database. The email is already stored in database during signUp.
I tried looking at some resource but they seem complicated.
HomeController
public async Task<IActionResult> ProfileView(ApplicationUser users)
{
return View();
}
Application user model:
public class ApplicationUser: IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public int ZipCode { get; set; }
}
Profile view:
<form>
<div class="form-row">
<div class="form-row col-md-6">
<div class="col">
<label for="firstName">First Name:</label>
<input type="text" class="form-control" placeholder="First name">
</div>
<div class="col col-md-6">
<label for="lastName">Last Name:</label>
<input type="text" class="form-control" placeholder="Last name">
</div>
</div>
<div class="form-group col-md-6">
<label for="inputEmail4">Email</label>
<input type="email" class="form-control" id="inputEmail4" placeholder="Email">
</div>
</div>
Database context:
public class DataBaseContext: IdentityDbContext<ApplicationUser>
{
public DataBaseContext(DbContextOptions<DataBaseContext> options): base(options)
{
}
public new DbSet<SignUp> Users { get; set; }
public DbSet<Products> Products { get; set; }
}
When the user fills out the information in the view it should store it in an existing database.
Here is a working example on modifying user's profile after logining :
Profile View :
#model MVC2_2Project.Models.ApplicationUser
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="FirstName"></label>
<input asp-for="FirstName" class="form-control" placeholder="First name"/>
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName"></label>
<input asp-for="LastName" class="form-control" placeholder="Last name"/>
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
HomeController:
public class HomeController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ApplicationDbContext _context;
public HomeController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ApplicationDbContext context)
{
_userManager = userManager;
_signInManager = signInManager;
_context = context;
}
public async Task<IActionResult> ProfileView()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return View(user);
}
[HttpPost]
public async Task<IActionResult> ProfileView(ApplicationUser users)
{
if (!ModelState.IsValid)
{
return View();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
user.FirstName = users.FirstName;
user.LastName = users.LastName;
_context.Users.Update(user);
await _context.SaveChangesAsync();
await _signInManager.RefreshSignInAsync(user);
return RedirectToAction();
}
}

Resources