A set of technologies in .NET for building web applications and web services. Miscellaneous topics that do not fit into specific categories.
Hi @Pablo The Tiger ,
Thanks for reaching out.
In this case, reparsing the form alone is not enough. The newly created telephone inputs are being added with plain JavaScript, so Razor Tag Helpers do not get a chance to generate the validation attributes for them.
For client-side validation to work on those dynamic fields, each generated input needs the same kind of data-val-* attributes that Razor would normally render, and each field also needs a matching validation message <span>. After adding the new HTML to the form, you can then reset and reparse unobtrusive validation.
The snippets below are just for reference, so you may need to adjust the element IDs, property names, and layout classes to match the exact structure in your project.
You can add a small helper like this:
function refreshValidation() {
const form = $('#myForm');
form.removeData('validator');
form.removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse(form);
}
Then, instead of creating the telephone inputs without validation metadata, create them with the required validation attributes included:
function createTelephoneRow(key, phone) {
const telephoneId = phone?.telephoneId ?? 0;
const contactId = phone?.contactId ?? (document.getElementById('Id_Id')?.value || 0);
const description = phone?.description ?? '';
const number = phone?.number ?? '';
const wrapper = document.createElement('div');
wrapper.className = 'input-group mt-3 telephone-row';
wrapper.innerHTML = `
<input type="hidden" name="contact.Telephones.Index" value="${key}" />
<input type="hidden" name="contact.Telephones[${key}].TelephoneId" value="${telephoneId}" />
<input type="hidden" name="contact.Telephones[${key}].ContactId" value="${contactId}" />
<div class="w-100 mb-2">
<input type="text"
class="form-control desc"
name="contact.Telephones[${key}].Description"
value="${description}"
placeholder="Description"
data-val="true"
data-val-required="Description is required" />
<span class="text-danger field-validation-valid"
data-valmsg-for="contact.Telephones[${key}].Description"
data-valmsg-replace="true"></span>
</div>
<div class="w-100 mb-2">
<input type="text"
class="form-control nmbr"
name="contact.Telephones[${key}].Number"
value="${number}"
placeholder="Number"
data-val="true"
data-val-required="Phone number is required" />
<span class="text-danger field-validation-valid"
data-valmsg-for="contact.Telephones[${key}].Number"
data-valmsg-replace="true"></span>
</div>
<button type="button"
class="btn btn-danger"
onclick="this.closest('.telephone-row').remove(); refreshValidation();">
Remove
</button>
`;
return wrapper;
}
Then your addTelephone() function can become:
window.addTelephone = function () {
const key = crypto.randomUUID();
const wrapper = createTelephoneRow(key, null);
document.getElementById('footer').before(wrapper);
refreshValidation();
};
When you load the existing telephone records into the modal, use the same helper and call refreshValidation() after all rows are added:
document.getElementById('telephonesContainer').innerHTML = '';
for (let i = 0; i < data.telephones.length; i++) {
const phone = data.telephones[i];
const wrapper = createTelephoneRow(i, phone);
document.getElementById('telephonesContainer').appendChild(wrapper);
}
refreshValidation();
The important part is that this value:
data-valmsg-for="contact.Telephones[0].Description"
must match the input name exactly:
name="contact.Telephones[0].Description"
The same applies to Number. If those values do not match, validation may be registered, but the error message will not be displayed beside the correct field.
Also keep the validation scripts on the page:
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
Hope this helps! If my answer was helpful, I would greatly appreciate it if you could follow the instructions here so others with the same problem can benefit as well.