0

We are currently using FluentValidation in our MVC project. We needed to be able to create a dynamic view where users could add or remove items. This is accomplished using partialviews.

                <div id="LocationsContainer">
                    @foreach (var location in Model.Locations)
                    {
                        Html.RenderPartial("_Location", location);
                    }
                    @Html.ValidationMessageFor(m => m.Locations)
                    <br />
                </div>

And within the partial view I just have a few fields.

        ...
        <div class="float-box">
            <div class="label">
                @Html.LabelFor(m => m.PropertyAddress)
            </div>
            @Html.TextBoxFor(m => m.PropertyAddress)
            <br />
            @Html.ValidationMessageFor(m => m.PropertyAddress)
        </div>
        <div class="float-box">
            <div class="label">
                @Html.LabelFor(m => m.ApartmentNo)
            </div>
            @Html.TextBoxFor(m => m.ApartmentNo)
            <br />
            @Html.ValidationMessageFor(m => m.ApartmentNo)
        </div>
        ...

In my validator i set the validator for Model.Locations (which creates the partial views) by calling SetCollectionValidator

RuleFor(vm => vm.Locations).SetCollectionValidator(new ServiceAddressViewModelValidator());

In my controller where I call ModelState.IsValid it seems to be working/validating. I can see that errors are caught inside my partial view according to my validation rules. I just can't get the error messages to display. In my validation result i can see error messages but there not getting applied to the UI. Am i doing something wrong here? I even tried using dataannotations with fluentvalidation for things other than the partial views and I got weird behavior. Doing this approach it appeared when fluentvalidation displayed error messages then the data annotation error messages didn't display and when the data annotation error messages displayed it only worked for the first partial view. If i had more than one it wasn't getting applied to the other views. Any ideas how i can get this to work? (With fluentvalidation or data annotations)?

1 Answers1

1

Properties on a class are only validated if the class is instantiated. If no data for the class is posted at all, the modelbinder will not instantiate it. Your problem here, is that while you may be posting data, your form fields are not named properly, so the the modelbinder doesn't know what to do with them and discards the information. As a result, your collection items are never instantiated and validation is never run. To fix your issue, you need to use for rather than foreach:

@for (var i = 0; i < Model.Locations.Count(); i++)
{
    Html.RenderPartial("_Location", Model.Locations[i]);
}

Or, since all you're doing is rendering a partial, you can actually take a short cut if you use editor templates. First create the view Views\Shared\EditorTemplates\Location.cshtml. In that view, put the contents of your current partial view that your using for locations. Then, all you need in your main view is:

@Html.EditorFor(m => m.Locations)

Razor will see you have an editor template for a Location class (The template name should match the class name. If it's not Location, rename the template to match.) and since you have a collection, it will render that template for each item in the collection.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Model.Locations does get instantiated. That is happening in my controller. And I also instantiate it with a single object in the collection also. I am also using javascript to add additional items on the fly. All this appears to be working I can add/remove items and its working in the view. I do have validation on Model.Locations that is validating it has at least a single address, if not it throws an error message. This is working because its in the parent view. What isn't working is the validation in the partial view (the displaying of error messages). – Nighttrain5150 May 07 '15 at 15:33
  • Do I need to reference i (from the parent view) in my partial view? – Nighttrain5150 May 07 '15 at 15:34
  • 1) That's what I'm talking about. Sure, `Model.Locations` will be instantiated as an empty list. None of the item instances will be instantiated, and *that* is where the validation is. 2) No, once inside the partial, the model will be the actual `Location` instance. However, by indexing your collection, Razor now knows how to construct the proper field name. – Chris Pratt May 07 '15 at 15:41
  • Im confused, in my controller on load, I instantiate Model.Locations as a list and I also instantiate a single class (which has only int or string properties) and add that to Model.Locations. So when the page first runs I can see a single partial view has been added. I can remove items (which leaves me with an empty list) but i have validation on this and its working. When I click an add button its instantiating new objects and adding them to Model.Locations and its reflected in the view. But your saying that Model.Locations or its objects are still not being instantiated? – Nighttrain5150 May 07 '15 at 15:49
  • Once you post, all you have is a bucket of name-value pairs that the modelbinder must sort out and figure out how to fill the action parameters with, which for a post, often requires instantiating a class hierarchy. It's at *this* point that your `Locations` list is empty, because the modelbinder would have failed to match the field names to where they should go. – Chris Pratt May 07 '15 at 15:52
  • Ah, gotcha! Thanks for being patient with me. Im a WPF developer fairly new to ASP.NET MVC. – Nighttrain5150 May 07 '15 at 15:58
  • Yeah, the two-way databinding of WPF totally glosses over what's happening behind the scenes. It's similar to Web Forms in that respect. The biggest problem people have migrating from either to MVC is coming to terms with the concept of having to handle manually some of what happens for your automagically in WPF or Web Forms. – Chris Pratt May 07 '15 at 16:01
  • So i figured out what my problem is. In my partialview i am using Steve Sanderson's BeginCollectionItem to generate guid ids because I need to be able to dynamically add/remove items from the view. The problem with this approach is that FluentValidation generates int ids for validation errors where my html elements will have guid ids. Any idea how i can map the int to guids or guids to ints? – Nighttrain5150 May 08 '15 at 16:08
  • Well, no, they're entirely different types: there's no way to just make a GUID an int. If FluentValidation doesn't work with guids, then I would suggest moving away form BeginCollectionItem and just use standard integer indexes. For the purposes of adding/remove items dynamically, just use a data-binding library like Knockout or even a JS templating library to dynamically fix your indexes. – Chris Pratt May 08 '15 at 16:13