THE PROBLEM
I have a client view model with some required properties and non-required properties. The view contains different sections for updating different view model properties. If i am updating a part of the view with required client details such as FirstName, LastName, DOB, etc then i can wrap the input types into an ajax form and the controller will pick up these properties from the view model and validate accordingly using ModelState.IsValid and validation will be successful. However if I have another section on the same view that needs to update a non required property on the viewmodel (ie Notes) and pass this through from an ajax form post then ModelState validation fails because the other required properties are null as they were never submitted as part of the ajax form. Note that the required fields should always have data populated prior to loading the client details page and therefore should never be null.
THE CODE
ViewModel
public class ClientDetailViewModel
{
public int ID { get; set; }
[Required]
[StringLength(50, MinimumLength = 2)]
public string FirstName { get; set; }
[Required]
[StringLength(50, MinimumLength = 2)]
public string LastName { get; set; }
[Required]
[Display(Name = "Date of Birth")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
public string Gender { get; set; }
public string Notes { get; set; }
}
View
@model MSIC.Models.ClientViewModels.ClientDetailViewModel
@inject MSIC.Services.Custom.IGenderService GenderService;
<!-- tab-pane for updating core client details -->
<div class="tab-pane active" id="tab_1">
<form asp-controller="Client" asp-action="Edit" class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="FirstName" class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="LastName" class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="DOB" class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<input asp-for="DOB" class="form-control" />
<span asp-validation-for="DOB" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Gender" class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<select asp-for="Gender" asp-items="@(new SelectList(GenderService.GetAll(),"Code","Name"))" class="form-control">
</select>
<span asp-validation-for="Gender" class="text-danger" />
</div>
</div>
</form>
</div>
<!-- different section for updating client notes -->
<div id="divNotes" class="center-block">@Model.Notes</div>
<a href="#" class="btn btn-danger btn-block" data-toggle="modal" data-target="#notesModal" role="button"><b>Edit Notes</b></a>
<form asp-controller="Client" asp-action="EditNotes" class="form-horizontal" data-ajax="true" data-ajax-method="POST" data-ajax-update="#divNotes" data-ajax-mode="replace" data-ajax-success="CloseModal('#notesModal')" data-ajax-failure="AjaxOnFailure(xhr, status, error)">
<div class="modal fade" id="notesModal" tabindex="-1" role="dialog" aria-labelledby="notesModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="notesModalLabel">edit Reason</h4>
</div>
<div class="modal-body">
<input type="hidden" asp-for="ID" />
<div class="form-group">
<div class="col-sm-10">
<textarea asp-for="Notes" class="form-control" autofocus></textarea>
<span asp-validation-for="Notes" class="text-danger" />
</div>
</div>
</div>
<div class="modal-footer">
<div class="text-danger pull-left">
<i id="modalErrorIcon" class=""></i>
<span id="modalErrorText"></span>
</div>
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
</form>
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditNotes(ClientDetailViewModel model)
{
//validation fails because required fields in model are null since they were not submitted with this ajax form
if (ModelState.IsValid)
{
//update database with client notes and return notes to screen
return Content(model.Notes);
}
return Content("i haven't coded this yet");
}
THE QUESTIONS
How can I perform ModelState validation inside the controller for only some fields in an efficient way without duplicating code? I searched this issue and am aware of options like ModelState[].Errors.Clear(); but since this will be a big view with many different sections available for update I would like to avoid duplicating those statements in different Action methods for all the small ajax posts I will need to perform. Basically I would like to use ModelState validation so i can just store my validation logic inside the ViewModel and not separate and/or duplicate any validation logic inside the controller too.
The other option I can see would be to include all the required properties as hidden input types in each ajax form i have but this seems horribly unecessary and would surely be a nightmare to maintain. Is there a better way to pass all ViewModel properties for an ajax post and if so would it be expensive to send ViewModel properties that are not used other than for taking advantage of the ModelState.IsValid check?
I am new to asp.net and have started with asp.net core mvc (which i'm enjoying a lot) and have been learning via all the tutorials, SO questions, etc. However is it possible that I am approaching this the wrong way and if so what would be the right way to tackle this issue using asp.net core and Microsoft.jQuery.Unobtrusive.Ajax or some other ajax tool? Note that I have only posted this question because none of the other similar issues posted seem to deal with sharing ViewModel properties on a View but only submitting some of the properties but taking advantage of out-of-the-box validation.
Thanks in advance.