5

I have this ViewModel (simplified):

public class ResponseViewModel {

    public QuestionViewModel Question { get; set; }
    public string Answer { get; set; }
}

public class QuestionViewModel {

    public string Text { get; set; }
    public string Description { get; set; }
    public bool IsRequired { get; set; }
}

QuestionViewModel is mapped from my DAL entity Question which is a straightforward mapping from:

public class Question {

    public int Id { get; set; }
    public string Text { get; set; }
    public string Description { get; set; }
    public bool IsRequired { get; set; }
}

I want to be able to make Answer Required if Question.IsRequired is true. However after the postback Only The property Answer is filled (of course).

What is the best way to go here? I would like to be able to create a validation attribute but don't know how to achieve this.

UPDATE:

I tried to make it work by using ModelBinding but until now no succes. What I have done:

public class EntityModelBinder : DefaultModelBinder
  protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        // IF I DO IT HERE I AM TOO EARLY
    }
  protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        base.OnModelUpdated(controllerContext, bindingContext);
        // IF I DO IT HERE I AM TOO LATE. VALIDATION ALREADY TOOK PLACE
    }
}
Henk Mollema
  • 44,194
  • 12
  • 93
  • 104
amaters
  • 2,266
  • 2
  • 24
  • 44

2 Answers2

6

Perhaps a RequiredÌf attribute is what you need. There are several questions on StackOverflow regarding this. One of them can be found here: RequiredIf Conditional Validation Attribute.

Darin also points to a blogpost on MSDN containing an implementation of the RequiredIf attribute.

With this attribute your view model would become something like:

public class ResponseViewModel {

    public QuestionViewModel Question { get; set; }

    [RequiredIf("Question.IsRequired", true, "This question is required.")]
    public string Answer { get; set; }
}

I'm not sure if the implementations I gave support properties on complex types (Question.IsRequired for example) but with some modification it should be possible.

Community
  • 1
  • 1
Henk Mollema
  • 44,194
  • 12
  • 93
  • 104
  • The requiredIf attribute is a nice addition. However, I don't see how it will help hydrating the model because the Question is not part of the viewdata (i don't want to include hiddenfields). Or am I missing something here? – amaters Oct 25 '13 at 06:58
  • 1
    @amaters if you only want server-side validation, you should not need the value on the client. Check the second answer of [this question](http://stackoverflow.com/questions/7390902/requiredif-conditional-validation-attribute). – Henk Mollema Oct 25 '13 at 07:20
  • client side validation would be nice, for now it's enough if I can find a good place where I can hydrate the model and make the validation work. Using the IValidatableObject is an option altough I then have to find a way to pass my unitofwork. – amaters Oct 25 '13 at 08:26
  • @amaters the answer also provides a way to get the validation working on the client, but you need to use hidden fields I guess. – Henk Mollema Oct 25 '13 at 08:50
5

You can use IValidatableObject to perform validation at class level (in this case, ResponseViewModel), in order to check the answer's validity according to Question.IsRequired :

public class ResponseViewModel : IValidatableObject
{
    public QuestionViewModel Question { get; set; }
    public string Answer { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Question.IsRequired && string.IsNullOrEmpty(Answer))
        {
            yield return new ValidationResult("An answer is required.");
        }
    }
}

However, Question.IsRequired must have a valid value during validation process. You can do this by putting it in your view as an hidden input :

@Html.HiddenFor(m => m.Question.IsRequired)

The default model binder will get the right value and perform validation correctly.

Réda Mattar
  • 4,361
  • 1
  • 18
  • 19
  • I don't want to put everything in hidden fields because if I do so the result can be manipulated by the user. – amaters Oct 21 '13 at 07:51
  • But you're probably still passing some information in any way, because when the answer is submitted, you need to know what was the original question. How do you do it ? Do you keep Question.Id saved somewhere in order to get it back from your database ? In that case, you can either do the same for Question.IsRequired, or read it when you get the question back. Anyway, getting rid of hidden fields is not an easy task, check this page for more suggestions : http://stackoverflow.com/questions/4504600/alternatives-to-hidden-fields-in-mvc2 – Réda Mattar Oct 22 '13 at 15:10