2

There is a complex view model that needs to be validated on the client. Simplified:

public class Survey
{
  public List<Question> Questions { get; set; }
}

public class Question
{
  public List<Answer> Answers { get; set; }
}

public class Answer
{
  public string FieldName { get; set; }

  [Required("Please use a number")]
  public int Number { get; set; }
}

Currently, the question is being validated correctly on the client. But we need to validate and display a contextual message using FieldName like:

The field 'Number of children' is required.

I implemented a CustomRequiredAttribute class (: RequiredAttribute, IClientValidatable) and decorated the Number property with it:

[CustomRequired("{0} is mandatory")]
public int Number { get; set; }

but, in the GetClientValidationRules method, the metadata.Model is null.

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
              ModelMetadata metadata,
              ControllerContext context)
{
  // metadata.Model is null here!
  ModelMetadata answerFieldNameValue = ModelMetadataProviders.Current
                                        .GetMetadataForProperties(
                                              metadata.Model,
                                              metadata.ContainerType)
                        .FirstOrDefault(p => p.PropertyName == "FieldName");

  // and, therefore, answerFieldNameValue.Model is null too!
}

If I go back to the first line of this method and from the context.Controller.ViewData.Model I get any Answer and assign it to metadata.Model, then the answerFieldNameValue.Model will contain the proper string which is the field name of the answer.

How to make this metadata.Model have a proper value when it gets here? Or any other way to solve this problem?

Fabio Milheiro
  • 8,100
  • 17
  • 57
  • 96
  • Have a look at this topic http://stackoverflow.com/a/18965540/1236044 which performs some conditional validation based on model state (which is related to your main concern). The model is extracted from context.Controller.ViewData.Model. The fieldname value should then be `(String)model.GetType().GetProperty("FieldName").GetValue(model, null);` – jbl Dec 05 '13 at 14:52
  • Yes, I can understand what @joelmdev did, but it doesn't fit the purpose of this question, because the questions are in the database and there should be an answer as context which is the object in which the property is. – Fabio Milheiro Dec 05 '13 at 15:04
  • can you post the full model and full CustomRequiredAttribute classes' code? – joelmdev Dec 05 '13 at 15:22
  • It is somewhat big, I could send by skype though if you need. – Fabio Milheiro Dec 05 '13 at 15:47
  • Hi @Bomboca I am having the same problem. Would it be possible to share your solution for this problem please? Thank you very much – duongthaiha Sep 29 '15 at 00:36
  • @duongthaiha, don't have it with me anymore. Sorry – Fabio Milheiro Sep 29 '15 at 09:15

1 Answers1

1

Got it. The code below is my solution which was inspired by BranTheMan who found his way out of a similar problem.

public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    private object lastQuestionLabel;

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        ModelMetadata modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

        object model = null;

        if (modelAccessor != null)
        {
            model = modelAccessor();
        }

        if (typeof(SurveyQuestionVM).IsAssignableFrom(containerType) && propertyName == "TreeViewLabel")
        {
            lastQuestionLabel = model;
        }

        if (typeof(SurveyAnswerVM).IsAssignableFrom(containerType))
        {
            modelMetadata.AdditionalValues.Add("QuestionLabel", lastQuestionLabel);
        }

        return modelMetadata;
    }
}
Fabio Milheiro
  • 8,100
  • 17
  • 57
  • 96