5

Question

I have created a server-side property level validation attribute. But instead of applying it to an individual field I've applied it to a List. This allows me to validate the model as a whole.

I now need to know how to convert this to work using the unobtrusive client-side validation built into MVC 3.

My current code is below to illustrate my issue...

Scenario

The basic scenario was the ability total up all the Quantity values for every row in a List grouped by the GroupNo field. If the sum of any of the groups was more than 10 then an error should be displayed.

I was kindly given an answer in a previous post to make this work server-side using a validation attribute against a List...

The model:

public class ItemDetails
{
    public int SerialNo { get; set; }
    public string Description { get; set; }
    public int GroupNo { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

public class MyViewModel
{
    [EnsureMaxGroupItems(10, ErrorMessage = "You cannot have more than 10 items in each group")]
    public IList<ItemDetails> Items { get; set; }
}

and the validation attribute itself:

[AttributeUsage(AttributeTargets.Property)]
public class EnsureMaxGroupItemsAttribute : ValidationAttribute
{
    public int MaxItems { get; private set; }

    public EnsureMaxGroupItemsAttribute(int maxItems)
    {
        MaxItems = maxItems;
    }

    public override bool IsValid(object value)
    {
        var items = value as IEnumerable<ItemDetails>;
        if (items == null)
        {
            return true;
        }

        return items
            .GroupBy(x => x.GroupNo)
            .Select(g => g.Sum(x => x.Quantity))
            .All(quantity => quantity <= MaxItems);
    }
}

and finally your controller actions will work with the view model:

public ActionResult ListItems()
{
    var model = new MyViewModel
    {
        Items = ItemsRepository.GetItems()
    };
    return View(model);
}

[HttpPost]
public ActionResult ListItems(MyViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    ...
}

and next the corresponding strongly typed view:

@model MyViewModel
@Html.ValidationSummary()
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Items)
    <button type="submit">Go go go</button>
}

and the last bit is the corresponding editor template that will automatically be rendered for each element of the Items collection so that you don't even need to write for loops (~/Views/Shared/EditorTemplates/ItemDetails.cshtml):

@model ItemDetails
@Html.HiddenFor(x => x.SerialNo)
@Html.LabelFor(x => x.Description)
@Html.HiddenFor(x => x.GroupNo)
@Html.LabelFor(x => x.Price)
@Html.TextBoxFor(x => x.Quantity)

Client-side unobtrusive validation possible?

I would like it all to validate using unobtrusive MVC validation. But I cannot figure out how to unobtrusively validate the EnsureMaxGroupItemsAttribute attribute against the list as a whole.

I've implemented IClientValidatable in this way:

    Public Function GetClientValidationRules(metadata As System.Web.Mvc.ModelMetadata, context As System.Web.Mvc.ControllerContext) As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.ModelClientValidationRule) Implements System.Web.Mvc.IClientValidatable.GetClientValidationRules

        Dim result = New List(Of ModelClientValidationRule)

        Dim rule = New ModelClientValidationRule() With { _
            .ErrorMessage = "You cannot have more than 10 items in each group", _
            .ValidationType = "itemscheck"}

        result.Add(rule)

        Return result

    End Function

Note: the mix of VB and C# is only because the previous question I asked was answered in C#. The project is in VB but I don't mind an answer in C#.

I've created the adaptor in my JS file:

jQuery.validator.unobtrusive.adapters.addBool("itemscheck"); 

... and ...

jQuery.validator.addMethod("itemscheck", function (value, element, params) {
    // The check has been omitted for the sake of saving space.  
    // However this method never gets called
    return false;
});

Is there a way to hook this up to work unobtrusively?

cw_dev
  • 465
  • 1
  • 12
  • 27
  • After reading http://stackoverflow.com/questions/4748703/ivalidatableobject-in-mvc3-client-side-validation/4826678#4826678 I don't think this is possible. Although this talks about IValidatableObject and not ValidationAttribute I can only think because I am assigning this to a List the effect is the same. I think my only solution is to remove unobtrusive validation completely and just validate with jQuery. – cw_dev Jul 16 '12 at 10:20

1 Answers1

4

This is not possible because your custom attribute is placed in the collection property and there are no HTML5 data-* attributes emitted at all. It is not a supported scenario by the unobtrusive client validation framework. You could write directly a custom jquery validate rule to handle this scenario if you need client validation for it.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Thanks, I figured this was probably not going to be possible. I've not really tried to do any validation without using unobtrusive before. As it stands I have my own function in a js file that submits the form (I don't have a standard submit button). Is it just a case of putting my validation code into this function and I don't submit if invalid. Doing it this way there would be no built in validation summary on the page, so I was thinking of show/hide appropriately. Or are you implying there is a more automatic way to do this and if so can it work with a validation summary? – cw_dev Jul 16 '12 at 13:11
  • I would plug directly into the jQuery validate plugin. – Darin Dimitrov Jul 16 '12 at 13:15
  • Thanks, I think I am slowly figuring it out, adding jQuery.validate.addMethod to my js. A few questions:- 1) how would I hook up a form level validation check as in my original question above? Do I have to hook it up to a control even though it applies to multiple controls on different rows? 2) With unobtrusive validation I am used to a validation summary - to do the same with the plugin do I have to customise ShowErrors and build up my own equivelent? 3) How would server-side errors work in combination with this? Would I still have an Html.ValidationSummary to serve them? – cw_dev Jul 16 '12 at 14:13
  • You could hook up the validation for each quantity field. This way when its value changes the validation will kick in. As far as showing the error message is concerned you could override the showError method of the jquery validate plugin which allows you to display the error message in the span placeholder generated by the ValidationSummary helper. – Darin Dimitrov Jul 16 '12 at 14:49