0

I'm having difficulty writing a custom remote validator that will evaluate distinct pairs of fields from a list? The list has 2 fields that need to be validated: Description and Amount. This is my list.

@for (int i = 0; i < Model.MyList.Count(); i++)
{
    @Html.TextBoxFor(m => m.MyList[i].Description)
    @Html.TextBoxFor(m => m.MyList[i].Amount)
    @Html.ValidationMessageFor(m => m.MyList[i].Description)
    @Html.ValidationMessageFor(m => m.MyList[i].Amount)
}

The validation rules are as follows:

  1. Amount must be zero or greater. It cannot be blank.
  2. Description is required only when an amount is greater than zero.
  3. When description is entered, the amount must be greater than zero.

I've seen (and implemented) solutions like these, but they don't address this type of scenario of evaluating multiple distinct pairs of fields that are in a list.

This is in my model. And the validation rules for Amount work. But the custom remote validator (ValidateDescriptionAmountPair) for Description fails.

[Range(0, int.MaxValue, ErrorMessage = "Amount must be 0 or greater.")]
[Required(ErrorMessage = "Amount is required.", AllowEmptyStrings = false)]
public decimal Amount { get; set; }

[Remote("ValidateDescriptionAmountPair", "MyController", AdditionalFields = "Amount")]
public string Description { get; set; }

This is my custom validator.

[HttpGet]
public virtual JsonResult ValidateDescriptionAmountPair(string Description, string Amount)
{
    bool isOkay = true;
    string reasonItsInvalid = string.Empty;

    // TODO: Add code to evaluate business rules, and change
    //       isOkay and reasonItsInvalid accordingly.

    if (isOkay)
    {
        return Json(true, JsonRequestBehavior.AllowGet);
    }
    else
    {
        return Json(reasonItsInvalid, JsonRequestBehavior.AllowGet);
    }

}

I put a breakpoint in ValidateDescriptionAmountPair. This method gets called, but the Description and Amount values arrive as NULL rather than with expected values.

These are some markup examples for fields rendered in the view.

<input data-val="true" data-val-remote="'Description' is invalid." data-val-remote-additionalfields="*.Description,*.Amount" data-val-remote-url="/MyController/ValidateDescriptionAmountPair" id="MyList_0__Description" name="MyList[0].Description" type="text" value="Paid for this" aria-invalid="false">
<input class="form-control text-right" value="5000.00" data-val="true" data-val-number="The field Amount must be a number." data-val-range="Amount must be 0 or greater." data-val-range-max="2147483647" data-val-range-min="0" data-val-required="Amount is required." id="MyList_0__Amount" name="MyList[0].Amount" placeholder="Dollar Amount" style="width:100px;" type="text" aria-required="true" aria-invalid="false" aria-describedby="MyList_0__Amount-error">

<input data-val="true" data-val-remote="'Description' is invalid." data-val-remote-additionalfields="*.Description,*.Amount" data-val-remote-url="/MyController/ValidateDescriptionAmountPair" id="MyList_1__Description" name="MyList[1].Description" type="text" value="test" aria-invalid="false" aria-describedby="MyList_1__Description-error">
<input class="form-control text-right" value="10000.00" data-val="true" data-val-number="The field Amount must be a number." data-val-range="Amount must be 0 or greater." data-val-range-max="2147483647" data-val-range-min="0" data-val-required="Amount is required." id="MyList_1__Amount" name="MyList[1].Amount" placeholder="Dollar Amount" style="width:100px;" type="text" aria-required="true" aria-invalid="false" aria-describedby="MyList_1__Amount-error">

<input data-val="true" data-val-remote="'Description' is invalid." data-val-remote-additionalfields="*.Description,*.Amount" data-val-remote-url="/MyController/ValidateDescriptionAmountPair" id="MyList_2__Description" name="MyList[2].Description" type="text" value="Paid for that" aria-invalid="false">
<input class="form-control text-right" value="20000.00" data-val="true" data-val-number="The field Amount must be a number." data-val-range="Amount must be 0 or greater." data-val-range-max="2147483647" data-val-range-min="0" data-val-required="Amount is required." id="MyList_2__Amount" name="MyList[2].Amount" placeholder="Dollar Amount" style="width:100px;" type="text" aria-required="true" aria-invalid="false" aria-describedby="MyList_2__Amount-error">

What I think may be happening is that the names applied to list entries don't exactly match the "Description" and "Amount" parameter names in ValidateDescriptionAmountPair(), because they have indexes applied to the names. In other words, the field names are MyList[1].Description and MyList[1].Amount, but the ValidateDescriptionAmountPair() method is looking for Description and Amount. That's just a hunch which could be totally off base. If indeed that's why it fails, then what is the solution?

At any rate, I need to be able to implement the business rules noted above, and I would prefer to do this using an MVC data annotation or custom remote validation approach rather than hand-rolling some client-side jQuery to do this. Thanks for your help.

Community
  • 1
  • 1
Ken Palmer
  • 2,355
  • 5
  • 37
  • 57
  • 2
    Unfortunately remote validation wont work because if the collection and I have previously reported this as a bug on CodePlex - [refer this answer](http://stackoverflow.com/questions/27513472/remote-validation-for-list-of-models/27517407#27517407). However you may be able to do all this without a `[RemoteAttribute]` by using [foolproof](http://foolproof.codeplex.com/) validation attributes –  May 14 '15 at 20:15
  • Thanks Stephen. That this is a known limitation helped me out. I ended up rolling my own for the client-side validation, and implementing some back-end blocks as well. It's more code than I care to post, and makes me appreciate the built-in controls Microsoft did provide a little more. – Ken Palmer May 15 '15 at 12:10
  • Did you have a look at the [Codeplex work item](https://aspnetwebstack.codeplex.com/workitem/2213)? The suggestion by mgoyot looks good although I have only done limited testing) –  May 15 '15 at 12:18
  • That's an interesting approach. Though I'd rather avoid altering the jquery.validate.unobtrusive.js file. At least I've got a workaround that's operational. Your guidance helped me get there. – Ken Palmer May 18 '15 at 15:20

1 Answers1

0

I could not achieve this in remote validation. But you achieve this using controller and @Html.ValidationSummary()

Add @Html.ValidationSummary() in view

@Html.ValidationSummary();
@for (int i = 0; i < Model.MyList.Count(); i++)
{
    @Html.TextBoxFor(m => m.MyList[i].Description)
    @Html.TextBoxFor(m => m.MyList[i].Amount)
    @Html.ValidationMessageFor(m => m.MyList[i].Description)
    @Html.ValidationMessageFor(m => m.MyList[i].Amount)
}

In Controller check the logic for distinct value and add the error message to the summary

    [HttpPost]
    public ActionResult MultipleValidation(List<MyList> model)
    {
        bool isOkay = true;
        string reasonItsInvalid = string.Empty;

        // TODO: Add code to evaluate business rules, and change
        // isOkay and reasonItsInvalid accordingly.

        if (!isOkay)
        {
          // Message to display in validation summary
          ModelState.AddModelError("", "Duplicate Record");
        }

        return View("View", model);
    }
Golda
  • 3,823
  • 10
  • 34
  • 67
  • Thanks Golda, You're right, and I've done this kind of checking before. My ultimate solution was to combine something like this on the back end, with client-side jQuery/JavaScript to mimic the behavior Microsoft provides with the remote validators. – Ken Palmer May 15 '15 at 12:12