I'm new to .net and am attempting to write some slick custom validation for a CustomerInfo form that I'm submitting. To do this, I'm implementing IValidatableObject on the model class and following this template from microsoft's documentation.
However, no matter what, I cannot get this to trigger. At this point, I've removed any conditionals from the Validate method just to see if I can get it to fire on a submission. I should note that above the Validate method, I see that vsCode has a "0 References" note that seems suspicious. What might I be missing?
I have looked into a few similar posts here and here The first is not the same situation and I'm sufficiently new to the framework that I can't tell if the second is.
Below is the model to which I'm referring. This is the only file that I have modified to accommodate the IValidatableObject:
using CS_Test.Mappers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.Json.Serialization;
using System.ComponentModel.DataAnnotations;
namespace CS_Test.Models
{
public class CustomerInfo : IValidatableObject
{
public string ServiceType {get; set;}
[DisplayName("First Name")]
public string FirstName {get; set;}
[DisplayName("Middle")]
public string MiddleName {get; set;}
[DisplayName("Last Name")]
public string LastName {get; set;}
public string BusinessName {get; set;}
[Required]
[StringLength(9, ErrorMessage = "Tax id must be 9 digits.")]
[DisplayName("Tax Id")]
public string TaxId {get; set;}
[Required]
[DisplayName("Security Password")]
public string SecurityPassword {get; set;}
[Required]
[DisplayName("Date Of Birth")]
public DateTime DateOfBirth {get; set;}
public string auto_generate_loginField {get; set;}
[Required]
[RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
ErrorMessage = "Not a valid email.")]
[DisplayName("Email Address")]
public string Email {get; set;}
[Required]
[StringLength(10, ErrorMessage = "Phone number must be 10 digits.")]
[DisplayName("Phone Number")]
public string Phone {get; set;}
[Required]
[DisplayName("Address")]
public string Address1Field {get; set;}
[DisplayName("Address2")]
public string Address2Field {get; set;}
[Required]
public string City {get; set;}
[Required]
public string State {get; set;}
[Required]
[DisplayName("Zip Code")]
public string Zip {get; set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
yield return new ValidationResult(
"Test test test",
new[] { nameof(FirstName)});
}
}
}
And (for what it's worth) here's the form that I'm submitting.
@model Models.CustomerInfo
<form asp-controller="ServiceOrder" asp-action="CreatePost" class="row g-3" id="customerInfoForm" method="post">
<div class="form-check">
<input class="form-check-input" type="radio" name="serviceType" id="residentialService" checked>
<label class="form-check-label">Residential</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="serviceType" id="businessService">
<label class="form-check-label">Business</label>
</div>
<div class="col-md-12 residentialOnly">
<label class="requiredField">Name</label>
<div class="row" id="personName">
<div class="col-md-5">
<input type="text" class="form-control" asp-for="FirstName" placeholder="First" required>
<span class="text-danger" asp-validation-for="FirstName"></span>
</div>
<div class="col-md-2">
<input type="text" class="form-control" asp-for="MiddleName" placeholder="Middle">
<span class="text-danger" asp-validation-for="MiddleName"></span>
</div>
<div class="col-md-5">
<input type="text" class="form-control" asp-for="LastName" placeholder="Last" required>
<span class="text-danger" asp-validation-for="LastName"></span>
</div>
</div>
</div>
<div class="col-md-12 businessOnly">
<label class="requiredField" asp-for="LastName">Business Name</label>
<input type="text" class="form-control" asp-for="LastName" required>
<span class="text-danger" asp-validation-for="LastName"></span>
</div>
<div class="col-md-12">
<label class="requiredField" asp-for="SecurityPassword">Security Password</label>
<input type="text" class="form-control" asp-for="SecurityPassword" required>
<span class="text-danger" asp-validation-for="SecurityPassword"></span>
</div>
<div class="col-md-12">
<label class="requiredField" asp-for="TaxId">Tax Id</label>
<input type="text" class="form-control" asp-for="TaxId" required>
<span class="text-danger" asp-validation-for="TaxId"></span>
</div>
<div class="col-md-12">
<label class="requiredField" asp-for="DateOfBirth">Date of Birth</label>
<input type="date" class="form-control" asp-for="DateOfBirth" required>
<span class="text-danger" asp-validation-for="DateOfBirth"></span>
</div>
<div class="col-md-12">
<label class="requiredField" asp-for="Email">Email</label>
<input type="email" class="form-control" asp-for="Email" required>
<span class="text-danger" asp-validation-for="Email"></span>
</div>
<div class="col-md-12">
<label class="requiredField" asp-for="Phone">Phone</label>
<input type="phone" class="form-control" asp-for="Phone" required>
<span class="text-danger" asp-validation-for="Phone"></span>
</div>
<div class="col-12">
<label class="form-label requiredField" asp-for="Address1Field">Address</label>
<input type="text" class="form-control" asp-for="Address1Field" placeholder="1234 Main St" required>
<span class="text-danger" asp-validation-for="Address1Field"></span>
</div>
<div class="col-12">
<label class="form-label" asp-for="Address2Field">Address 2</label>
<input type="text" class="form-control" asp-for="Address2Field" placeholder="Apartment, studio, or floor">
<span class="text-danger" asp-validation-for="Address2Field"></span>
</div>
<div class="col-md-6">
<label class="form-label requiredField" asp-for="City">City</label>
<input type="text" class="form-control" asp-for="City" required>
<span class="text-danger" asp-validation-for="City"></span>
</div>
<div class="col-md-4">
<label class="form-label requiredField" asp-for="State">State</label>
<div>
<select id="inputCustomerState" class="form-select" style="width: 100%" asp-for="State" required>
</select>
</div>
<span class="text-danger" asp-validation-for="State"></span>
</div>
<div class="col-md-2">
<label class="form-label requiredField" asp-for="Zip">Zip</label>
<input type="text" class="form-control" asp-for="Zip" required>
<span class="text-danger" asp-validation-for="Zip"></span>
</div>
</form>
<script>
newForm = $("#customerInfoForm");
$.validator.unobtrusive.parse(newForm);
</script>
By popular request, my controller methods
Used to render the form:
public IActionResult Create()
{
var newOrderId = HttpContext.Session.Get<Guid>("NewOrderId");
if (newOrderId == Guid.Empty) { newOrderId = NewOrderId(); } // if no order id was found, then create one
ViewBag.NewOrderId = newOrderId;
NewCustomerOrder order = GetNewCustomerOrder(newOrderId);
return View(order);
}
To Submit the form via ajax:
public IActionResult Review(CustomerInfo customerInfo)
{
// get the order from the cache
var newOrderId = HttpContext.Session.Get<Guid>("NewOrderId");
var order = GetNewCustomerOrder(newOrderId);
// update customer info on order
order.CustomerInfo = customerInfo;
// update the order in the cache
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromDays(7));
_cache.Set("NewOrderId" + newOrderId, order, cacheEntryOptions);
return PartialView("_ReviewOrder", order);
}
Here's the javascript/ajax.
$("#customerInfoForm").on("submit", function(e) {
e.preventDefault();
if ($("#customerInfoForm").valid() == false) { // check if the form is valid
return false;
} else { // form is valid
var customerInfo = $("#customerInfoForm").serializeArray();
// hide other tabs
$(".tab-pane").removeClass("active");
$(".tab-pane").removeClass("show");
$(".nav-link").removeClass("active");
// show review tab
$("#nav-tab-reviewOrder").addClass("active");
$("#nav-tab-reviewOrder").addClass("show");
$("#nav-link-reviewOrder").addClass("active");
$("#tabColumn").addClass("w-100");
$("#cartColumn").attr("hidden", true);
$(".nav-link").attr("disabled", true);
$(".nav-link").addClass("disabled");
// get the review tab partial
$.ajax({
type: "POST",
url: '/ServiceOrder/Review',
data: customerInfo,
datatype: "json",
success: function (response) {
$('#reviewOrderPartialView').html(response); // response contains the html from the partial view
// get the cart for review order
$.ajax({
type: "POST",
url: '/Cart/Show',
datatype: "json",
success: function (response) {
$('#cartReviewPartialView').html(response); // response contains the html from the partial view
// disable cart buttons
$(".cs-RemoveFromCartButton").attr("disabled", true);
$(".cs-RemoveFromCartButton").attr("hidden", true);
},
error: function () {
$('#cartReviewPartialView').html('Cart failed to load');
}
});
},
error: function () {
$('#reviewOrderPartialView').html('Review Order failed to load');
}
});
}
})