Conditional validation on model in MVC
Asked Answered
S

4

16

I have a view & model that I use for both the edit and the insert page for a record. One of the business requirements is that a certain field is required on edit but not on new. Originally before this particular feature was added to the docket, i had the model like so:

[Required(ErrorMessage = "*")]
[Range(0.0, (double)decimal.MaxValue)]
[DisplayName("Cost")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public decimal ProposedCost { get; set; }

I would like to either remove the required property if it is an insert form, or add it if an edit form. What is the better approach? All my other validation is done like above. Or can I alter the model state? Thoughts?

EDIT

Something I should clarify is that they are still permitted to insert a cost on new, just not required.

Ssw answered 31/7, 2013 at 12:35 Comment(0)
L
36

If you're on MVC3/.NET4, you can use IValidatableObject which exists specifically for such purposes.

Quoting ScottGu,

...The IValidatableObject interface enables you to perform model-level validation, and enables you to provide validation error messages specific to the state of the overall model....

You model would look like

public class MyViewModel : IValidatableObject
{
    public long? Id { get; set; }
    public decimal? ProposedCost { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
        if (Id != null && ProposedCost == 0) {
            yield return new ValidationResult("ProposedCost must be provided.");
        }
    }
}

and then in the controller,

[HttpPost]
public ActionResult Submit(MyViewModel model)
{
    if (!ModelState.IsValid) {
        //failed - report an error, redirect to action etc
    }
    //succeeded - save to database etc
}

Otherwise, the most clean solution would be to use view models - UpdateViewModel where the property is required, and CreateViewModel where it's not required.

Linchpin answered 31/7, 2013 at 12:52 Comment(5)
I tried this, however was still able to save an edit record without a proposed cost. I placed a breakpoint and was hitting the Validationresult. Thoughts?Ssw
Are you sure you check ModelState.IsValid? I updated the answer to show an example. Also note that ProposedCost needs to be nullable if we allow not to set it.Linchpin
Yeah, I missed that, good catch. The other properties added have a client side check automatically behind the scenes. Anyway can I tap into that? This is of course beyond the scope of my original question.Ssw
tried this and I was able to yield a new Validation result but my, "ModelState.IsValid" was still true... Not sure why.Bickerstaff
it was using yield for some reason it didn't like.. so I just newd up a IEnumerable<ValidationResult> and added errors if found otherwise returned the object empty or not.. That worked using MVC4.Bickerstaff
P
4

There is the MVC Foolproof library: http://foolproof.codeplex.com/

For example you would need to have something like this in your model:

[RequiredIfTrue("Required", ErrorMessage = "*")]
[Range(0.0, (double)decimal.MaxValue)]
[DisplayName("Cost")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public decimal ProposedCost { get; set; }

public bool Required { get; set; }

You would then need to set the Required property based on which form the model is going to.

You will also need a hidden input field on the form to represent the Required property if you wish to perform client side validation.

Hope that helps...

Pacer answered 31/7, 2013 at 12:52 Comment(0)
E
3

You could use the RequiredIf validation attribute from the MVC Foolproof Validation project. I've used it on projects to enable just the functionality you require.

An alternative would be to use the RemoteAttribute and implement the logic yourself in a method.

Emelinaemeline answered 31/7, 2013 at 12:45 Comment(4)
I personally don't like RequiredIf because it's not type safe - you have to write [RequiredIf("SomeProperty", value, ...)] and it means the compiler won't warn you if SomeProperty gets renamed but the old name was stuck in the attributeLinchpin
@Linchpin you can use [RequiredIf(nameof(SomeProperty), value, ...)] which gives you compile time validation and copes withe the renaming of SomePropertyHeadcheese
Very true @JamesCulshaw - one of my favourite C# 6 features, because it helps this exact scenario.Emelinaemeline
Yeah my comment was from 2013 and now we finally have nameof :)Linchpin
C
1

You can try on validation with:

ModelState.Remove("ProposedCost");

Or extending your model like this:

public class NewModeViewModel : EditModeViewModel
{
   public new decimal ProposedCost { get; set; }
}

and passing to the edit view.

Christianson answered 31/7, 2013 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.