Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC 3: Validating model when information external to the model is required

What's a good way to validate a model when information external to the model is required in order for the validation to take place? For example, consider the following model:

public class Rating {
    public string Comment { get; set; }
    public int RatingLevel { get; set; }
}

The system administrator can then set the RatingLevels for which a comment is required. These settings are available through a settings service.

So, in order to fully validate the model I need information external to it, in this case the settings service.

I've considered the following so far:

  1. Inject the service into the model. The DefaultModelBinder uses System.Activator to create the object so it doesn't go through the normal dependency resolver and I can't inject the service into the model without creating a new model binder (besides which, that doesn't feel like the correct way to go about it).
  2. Inject the service into an annotation. I'm not yet sure this is possible but will investigate further soon. It still feels clumsy.
  3. Use a custom model binder. Apparently I can implement OnPropertyValidating to do custom property validation. This seems the most preferable so far though I'm not yet sure how to do it.

Which method, above or not, is best suited to this type of validation problem?

like image 734
Kaleb Pederson Avatar asked Oct 12 '11 17:10

Kaleb Pederson


People also ask

How is validation implemented in ASP.NET MVC?

In ASP.NET MVC model validations are done using Data Annotation, its inherited System. ComponentModel. DataAnnotations assembly. In ASP.NET MVC 4 provides a different way to build custom validation.

How do I know if a model is valid or not?

The state of the submitted Model is checked using ModelState. IsValid property and if the Model is valid then the value of the Name property is set in a ViewBag object. Note: ModelState.

Which of the following are alternatives to perform model validation instead of using built in validation attributes?

Alternatives to built-in attributes If you need validation not provided by built-in attributes, you can: Create custom attributes. Implement IValidatableObject.


1 Answers

Option 1 doesn't fit. The only way it would work would be to pull in the dependency via the service locator anti-pattern.

Option 2 doesn't work. Although I couldn't see how this was possible because of the C# attribute requirements, it is possible. See the following for references:

  • Resolving IoC Container Services for Validation Attributes in ASP.NET MVC
  • NInjectDataAnnotationsModelValidatorProvider

Option 3: I didn't know about this earlier, but what appears to be a very powerful way to write validators is to use the ModelValidator class and a corresponding ModelValidatorProvider.

First, you create your custom ModelValidatorProvider:

public class CustomModelValidatorProvider : ModelValidatorProvider
{
    public CustomModelValidatorProvider(/* Your dependencies */) {}

    public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
    {
        if (metadata.ModelType == typeof(YourModel))
        {
            yield return new YourModelValidator(...);
        }
    }
}

ASP.NET MVC's IDependencyResolver will attempt to resolve the above provider, so as long as it's registered with your IoC container you won't need to do anything else. And then the ModelValidator:

public class EntryRatingViewModelValidatorMvcAdapter : ModelValidator
{
    public EntryRatingViewModelValidatorMvcAdapter(
            ModelMetadata argMetadata,
            ControllerContext argContext)
                : base(argMetadata, argContext)
    {
        _validator = validator;
    }


    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        if (/* error condition */)
        {
            yield return new ModelValidationResult
              {
                MemberName = "Model.Member",
                Message = "Rating is required."
              };
        }
    }
}

As the provider is retrieved through the IDependencyResolver and the provider has full control over the returned ModelValidators I was easily able to inject the dependencies and perform necessary validation.

like image 178
Kaleb Pederson Avatar answered Sep 25 '22 19:09

Kaleb Pederson