0

I have this validator class:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using DocumentFormat.OpenXml.Linq;
using FluentValidation;
using FluentValidation.AspNetCore;
using FluentValidation.Results;
using Microsoft.Ajax.Utilities;
using OpenXmlPowerTools;
using ValidatorAttribute = ServiceStack.FluentValidation.Attributes.ValidatorAttribute;

namespace Research.Models
{
    public class DocTrailListValidator : AbstractValidator<DocTrailList>
    {
        public DocTrailListValidator()
        {
            RuleFor(x => x.ProjectID).NotNull().WithMessage("A required field");
            RuleFor(x => x.DocumentType).NotNull().WithMessage("A required field");
            RuleFor(x => x.IsThisCurrent).NotNull().WithMessage("A required field");
            RuleFor(x => x).Must(x => !IsDuplicate(x)).WithMessage("Duplicate entry found.");
        }

        private bool IsDuplicate(DocTrailList c)
        {
            // Example list of existing values for validation
            var existingValues = new List<DocTrailList>();
            

                return existingValues.Any(x => x.ProjectID == c.ProjectID &&
                                           x.DocumentType == c.DocumentType &&
                                           x.IsThisCurrent == c.IsThisCurrent);
        }
    }
}

The first three rules validation errors are assigned to their respective fields in the Create view and I can see them firing. However, for the existingValues variable, how/where do I place the Validation Message in order for it to fire on the Create page? The following snippet of the Create view code shows the IsThisCurrent field (for which the Validation Message works) and bmy 'effort' at getting the variable in the list to fire. I tried it with the variable name also but that failed. Any help gratefully received.

<div class="col">
    <div class="form-group"> 
        <label asp-for="IsThisCurrent" class="control-label"></label>
        @Html.DropDownList("IsThisCurrent", new SelectList(ViewBag.current,"YesNo","YesNo"),string.Empty)
        <div class="col-md-10">
            @Html.ValidationMessageFor(model => model.IsThisCurrent, null, new { @class = "text-danger" })
        </div>
    </div>
</div>
<div class="col">
    <div class="form-group">
        List<DocTrailList>
        @* @Html.ValidationMessageFor(model => model.ProjectID, null, new { @class = "text-danger" }) *@
        @Html.ValidationMessageFor(model => model.ProjectID, null, new { @class = "text-danger" })
</div>

UPDATE: This is the controller for the Create view.

   // GET: DocTrailLists/Create
public IActionResult Create()
{
    ViewData["ProjectID"] = new SelectList(_context.PIF, "ProjectID", "ProjectID");

    var dtname = _context.DocType.ToList();
    ViewBag.dtname = dtname;

    var current = _context.YesNoList.ToList();
    ViewBag.current = current;

    return View();
}

// POST: DocTrailLists/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("DetailID,ProjectID,DocumentType,Version,Dated,DateReceived,IsThisCurrent")] DocTrailList docTrailList)
{
    DocTrailListValidator doctraillistsvalidator = new DocTrailListValidator();
    ValidationResult result = doctraillistsvalidator.Validate(docTrailList);

    if (result.IsValid)
    {
        if (ModelState.IsValid)
        {
            _context.Add(docTrailList);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
    }
    else
    {
        foreach (var failure in result.Errors)
        {
            ModelState.AddModelError("", failure.ErrorMessage);
        }
    }
    ViewData["ProjectID"] = new SelectList(_context.PIF, "ProjectID", "ProjectID", docTrailList.ProjectID);
    return View(docTrailList);
}
5
  • From ModelState.AddModelError("", failure.ErrorMessage);, I doubt this will reset all the validation property name. You should provide the property name: ModelState.AddModelError(failure.PropertyName, failure.ErrorMessage);. Other than that, my concern will be does your 4th validation failed and it appears in the result.Errors. Commented Sep 8 at 8:45
  • Hi, I still can't get this to work. I put a breakpoint on the Create view ProjectID's ValidationMessage. The message I get is: "The breakpoint will not currently be hit. No executable code of the debugger's target code type is associated with this line. Possible causes include: conditional compilation, compiler optimisations, or the target architecure of this line is not supported by the current debugger code type. Commented Sep 8 at 10:01
  • You may read this: stackoverflow.com/questions/50306571/…. Make sure you are in debug mode. If it still happen, you may try to rebuild the solution. Commented Sep 8 at 11:37
  • My thought is that you need to check whether your 4th validation is failed and the error appears in the result.Errors. If yes, means that could be model state or view issue that not able to bind that error message. If no, means that your 4th validation is passed, then you should check on its implementation why the validation passes. Commented Sep 9 at 7:34
  • Can I know any update on this question, whether it has been resolved? Commented Sep 18 at 7:19

1 Answer 1

0

Approach 1: Validation Summary

Either you can adopt a @Html.ValidationSummary() with first argument: true or <div asp-validation-summary="ModelOnly"> element to show all the error messages that are in the model-level only.

<!-- Fields elements -->

<div class="row">
  <div class="col">
    @Html.ValidationSummary(true, "", new { @class = "text-danger" }) 
  </div>
</div>

<!-- Or asp-validation-summary attribute -->
<div asp-validation-summary="ModelOnly" class="text-danger"></div>

Approach 2: Associate the error to a property

Or you should associate the error to a property. By changing the lambda (input) parameters as below, you can access the parent (DocTrailList) object instance.

RuleFor(x => x.ProjectID)
    .Must((root, projectID, context) => !IsDuplicate(root))
    .WithMessage("Duplicate entry found.");

Demo @ .NET Fiddle

Sign up to request clarification or add additional context in comments.

4 Comments

Hi Yong Shun, thank you for all of this information. I've tried every scenario and I don't have any validation message firing for that 4th validation. Some further context: both DocumentType and IsThisCurrent are string data types. There are other fields in the model but they are not a part of this duplication request. It should fire, if you have, for example: Project ID 1, DocumentType "Protocol", IsThisCurrent "Yes" duplicated. Thank you for any help.
Noted, can I know the IsDuplicate function is triggered? If yes, secondly, I wonder how your controller action trigger the validation? If based on your previous question, that if you add validation error in the model state within if (!ModelState.IsValid) will not work, because your 4th validation doesn't exist as part of validation of ModelState unless you implement the IValidatableObject interface with the 4th validation to the DocTrailList class. I would say if (!ModelState.IsValid) is not needed since you are using FluentValidation and can be replaced with `result.I
Perhaps try to share your controller action with how you use ModelState.IsValid and fluent validator in the question. Thanks.
Thanks. I've just updated, adding in the controller class. I really appreciate your help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.