Fabio Milheiro
Fabio Milheiro

Reputation: 8474

Find property value of complex object in GetClientValidationRules

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?

Upvotes: 2

Views: 2177

Answers (1)

Fabio Milheiro
Fabio Milheiro

Reputation: 8474

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;
    }
}

Upvotes: 1

Related Questions