Domanda

Environment:
I am using MVC4, Razor 2 and FluentValidation.MVC4 (3.4.6.0).

Scenario:
I have a complex view model for a particular page which also has a child view model on it as follows:

public class ProfileViewModel
{
    public string FirstName {get; set;}
    public PhoneNumberViewModel Primary {get; set;}
    // ... other stuff ... //
}

public class PhoneNumberViewModel
{
    public string AreaCode { get; set; }
    public string Exchange { get; set; }
    public string Suffix { get; set; }
    public string Extension { get; set; }
}

This profile can be edited and POSTed back for updating. I have created Fluent Validators for both as follows:

public class ProfileViewModelValidator : AbstractValidator<ProfileViewModel>
{
    public ProfileViewModelValidator()
    {
        RuleFor(m => m.FirstName).NotEmpty().WithMessage("Please enter a First Name,");
        RuleFor(m => m.Primary).SetValidator(new PhoneNumberViewModelValidator()).WithMessage("Hello StackOverflow!");
        // ... other validation ... //
    }
}

public class PhoneNumberViewModelValidator : AbstractValidator<PhoneNumberViewModel>
{
    public PhoneNumberViewModelValidator()
    {
        RuleFor(m => m.AreaCode).NotEmpty();
    }     
}

And then, of course, I have views to display everything.

Profile View Snippet:

...
@Html.TextBoxFor(m => m.FirstName)
@Html.EditorFor(m => m.PrimaryPhoneNumber)
...

Phone Number Editor Template Snippet:

...
@Html.ValidationLabelFor(m => m, "Primary Phone:")
@Html.TextBoxFor(m => m.AreaCode)
@Html.TextBoxFor(m => m.Exchange)
@Html.TextBoxFor(m => m.Suffix)
@Html.TextBoxFor(m => m.Extension) 
@Html.ValidationMessageFor(m => m)
...

If it's relevant, I have things set up so that it automatically wires up validators with the various objects. I don't actually even need the .SetValidator() line above... everything is validated anyway because of wire up.

Objective:
If I don't enter a first name, I get the error message provided above in the area created by ValidationMessageFor. However, if any of the elements of the child PhoneNumberViewModel fail validation, I get nothing. The Text Boxes highlight red, which is fantastic, but I do not get the message I supplied in the .WithMessage(), indicating that my child property is invalid.

Currently I am achieving it by extra work in my controller... that looks for errors on the child objects and then adds errors to the parent object. This approach smells really, really bad. It's putting validation-related concerns in the controller, and I just don't want them there. Not to mention it also ends up having a lot of indexing by strings to dig into the ModelState, and it's just... gross.

Is there a simple way to define a validation rule for the ProfileViewModelValidator that will add errors for the ProfileViewModel if the child fails to validate? And/or should it be working, but am I doing something wrong? I've searched and searched, but I cannot find a satisfactory solution.

Thanks for your time!

È stato utile?

Soluzione

I have discovered a different solution from the smelly one I included in my question. It is also less than ideal, but I think I prefer it to the solution provided above. Perhaps others will agree, so it may be useful until a cleaner answer comes along.

I added a property to the PhoneNumberViewModel that returns the entire phone number as a formatted string:

public string FullNumber
{
   get { return string.Format("{0}{1}{2}{3}", AreaCode, Exchange, Suffix, Extension); }
}

You can then apply validation rules to this property in addition to your rules for each of the component properties. If you only apply the .WithMessage() to the FullNumber property, you will end up getting both the validation message you expect (and only once) as well as the textbox highlighting for each individual box if there is a failure.

The main downside to this approach is that you end up duplicating your validation rules. You validate each component individually, and then have to validate the combined property also which will basically be a combination of the validation you have already done. You're doubling the code, and doubling the amount of processing. You're also doubling the amount of places something can go wrong.

In my case I am using regular expressions, so it isn't too bad... and I much prefer it to embedding error propagation code in the controller. It's still not ideal however - hopefully a better solution will come along.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top