Search code examples
javascriptc#jqueryasp.net-corerazor-pages

How to implement client side validation for a conditional required property - Razor ASP.NET core


I have a model that is shared between two pages (Security Log, Lost and Found). I am looking to require the property, 'Narrative', only if the property, 'AppType' = "Security Log".

I have the server side validation working fine, but when I go to save the form, the drop-down values on my form lose their values because I am using ViewBag. This is what I currently have.

SecurityLog.cs

public string AppType { get; set; }
[RequiredIf(nameof(AppType), "Security Log", ErrorMessage = "Narrative is required")]
public string Narrative { get; set; }

public class RequiredIfAttribute : ValidationAttribute
    {
        public string PropertyName { get; set; }
        public object Value { get; set; }

        public RequiredIfAttribute(string propertyName, object value, string errorMessage = "")
        {
            PropertyName = propertyName;
            ErrorMessage = errorMessage;
            Value = value;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var instance = validationContext.ObjectInstance;
            var type = instance.GetType();
            var proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
            if (proprtyvalue.ToString() == Value.ToString() && value == null)
            {
                return new ValidationResult(ErrorMessage);
            }
            return ValidationResult.Success;
        }
    }

Does anyone know how I can implement client side validation (javascript/jquery/etc..) on my Security Log create page? I am finding that the rest of my project's required fields validate client side because the drop-down values are retained. When I have all required fields except for Narrative filled-out and click save, then I lose the dd values so I know this is being handled server side.

Sample of create page before clicking save
enter image description here

Sample of create page after clicking save

enter image description here

Create page logic

<div class="form-group">
    <label asp-for="SecurityLog.EntityID" class="control-label"></label>
    <select id="entity" asp-for="SecurityLog.EntityID" class="form-control" asp-items="ViewBag.EntityID">
         <option value="">--Select Entity--</option>
    </select>
    <span asp-validation-for="SecurityLog.EntityID" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="SecurityLog.LocationID" class="control-label"></label>
    <select id="location" asp-for="SecurityLog.LocationID" class="form-control"
            asp-items="ViewBag.LocationID">
        <option value="">--Select Location--</option>
    </select>
    <span asp-validation-for="SecurityLog.LocationID" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="SecurityLog.ShiftRangeID" class="control-label"></label>
    <select asp-for="SecurityLog.ShiftRangeID" class="form-control" asp-items="ViewBag.ShiftRangeID">
        <option value="">--Select Shift--</option>
    </select>
    <span asp-validation-for="SecurityLog.ShiftRangeID" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="SecurityLog.EventStart" class="control-label"></label>
    <input asp-for="SecurityLog.EventStart" class="form-control" id="datepicker2" type="text" autocomplete="off" />
    <span asp-validation-for="SecurityLog.EventStart" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="SecurityLog.EventEnd" class="control-label"></label>
    <input asp-for="SecurityLog.EventEnd" class="form-control" id="datepicker3" type="text" autocomplete="off" />
    <span asp-validation-for="SecurityLog.EventEnd" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="SecurityLog.ContactName" class="control-label"></label>
    <input asp-for="SecurityLog.ContactName" class="form-control" autocomplete="off" />
    <span asp-validation-for="SecurityLog.ContactName" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="SecurityLog.EventTypeID" class="control-label"></label>
    <select id="selectEventType" asp-for="SecurityLog.EventTypeID" class="form-control" asp-items="ViewBag.EventTypeID">
        <option value="">--Select Event Type--</option>
    </select>
    <span asp-validation-for="SecurityLog.EventTypeID" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="SecurityLog.Narrative" class="control-label"></label>
    <textarea asp-for="SecurityLog.Narrative" class="form-control" style="height:200px;"></textarea>
    <span asp-validation-for="SecurityLog.Narrative" class="text-danger"></span>
</div>    
<div class="form-group">
    <input type="submit" value="Create" class="btn btn-primary" id="btnCreateLog" onclick="return lockedOrNot()" />
</div>

Create.cshtml.cs

[BindProperty]
public SecurityLog SecurityLog { get; set; }
[BindProperty(SupportsGet = true)]
public int entity { get; set; }    

SelectList FilteredLocation;

public JsonResult OnGetLocations()
{

    FilteredLocation = new SelectList(_context.Location.Where(c => c.EntityID == entity).Where(c =>c.Active == "Y").OrderBy(c =>c.Name), "ID", "Name");
    
    return new JsonResult(FilteredLocation);
}


public IActionResult OnGetAsync()
{  
    ViewData["EntityID"] = new SelectList(_context.Entity.Where(a => a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");                          
    ViewData["ShiftRangeID"] = new SelectList(_context.ShiftRange.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
    ViewData["LocationID"] = new SelectList(_context.Location.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
    ViewData["EventTypeID"] = new SelectList(_context.EventType.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID","Name");
                
    return Page();
}

public async Task<IActionResult> OnPostAsync()
{
        
    if (!ModelState.IsValid)
    {
         ViewData["EntityID"] = new SelectList(_context.Entity.Where(a => a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
         ViewData["ShiftRangeID"] = new SelectList(_context.ShiftRange.Where(a => a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
         ViewData["LocationID"] = new SelectList(_context.Location.Where(a => a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");//Not workiong                
         ViewData["EventTypeID"] = new SelectList(_context.EventType.Where(a => a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");  
        //return Page(SecurityLog); // Return also the entity //Errors out "No overload for message 'Page ' takes 1 argument
        return Page();
    }
        

    _context.SecurityLog.Add(SecurityLog);    

    await _context.SaveChangesAsync();

    Message = "Entry added successfully!";

    //return RedirectToPage("Index");
    return Page();

}

Solution

  • You must return the List and the entity when validation fail.

    public IActionResult OnGetAsync()
    {       
        ViewData["EntityID"] = new SelectList(_context.Entity.Where(a => a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");                     
        ViewData["ShiftRangeID"] = new SelectList(_context.ShiftRange.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
        ViewData["LocationID"] = new SelectList(_context.Location.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
        ViewData["EventTypeID"] = new SelectList(_context.EventType.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID","Name");
                    
        return Page();
    }
    
    public async Task<IActionResult> OnPostAsync()
    {
            
        if (!ModelState.IsValid)
        {
            // Where and When is invoked ? Is necessary for locations ?
            // FilteredLocation = new SelectList(_context.Location.Where(c => c.EntityID == entity).Where(c =>c.Active == "Y").OrderBy(c =>c.Name), "ID", "Name");
        
            // return new JsonResult(FilteredLocation);
            // Return the ViewData
            ViewData["EntityID"] = new SelectList(_context.Entity.Where(a => a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
            ViewData["ShiftRangeID"] = new SelectList(_context.ShiftRange.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
            ViewData["LocationID"] = new SelectList(_context.Location.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID", "Name");
            ViewData["EventTypeID"] = new SelectList(_context.EventType.Where(a=>a.Active == "Y").OrderBy(a => a.Name), "ID","Name");
            // Return also the entity
            // Somewhere you have to set SecurityLog
            return Page(); 
        }
            
        // From where came the SecurityLog ?
        _context.SecurityLog.Add(SecurityLog);    
    
        await _context.SaveChangesAsync();
    
        Message = "Entry added successfully!";
    
        // If you return always the same page, you must return also here the ViewData or move from it the top
        //return RedirectToPage("Index");
        return Page();
    
    }