BrunoZell/JsonModelBinder

Data Model Validation

Closed this issue · 1 comments

Looks like the Model's don't participate in the Data Validation system, when we custom Model Bind.

I added some code so that Data Model Validation would work:

   public class JsonModelBinder : IModelBinder
    {
        private readonly MvcJsonOptions _options;

        public JsonModelBinder(IOptions<MvcJsonOptions> options) =>
            _options = options.Value;

        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            // Test if a value is received
            var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (valueProviderResult != ValueProviderResult.None)
            {
                bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);

                // Deserialize from string
                string serialized = valueProviderResult.FirstValue;

                // Use custom json options defined in startup if available
                object deserialized = _options?.SerializerSettings == null ?
                    JsonConvert.DeserializeObject(serialized, bindingContext.ModelType) :
                    JsonConvert.DeserializeObject(serialized, bindingContext.ModelType, _options.SerializerSettings);

                // DataAnnotation Validation. Validate Properties and Fields.
                var validationResultProps = from prop in TypeDescriptor.GetProperties(deserialized).Cast<PropertyDescriptor>()
                                       from attribute in prop.Attributes.OfType<ValidationAttribute>()
                                       where !attribute.IsValid(prop.GetValue(deserialized))
                                       select new { Propertie = prop.Name, ErrorMessage = attribute.FormatErrorMessage(string.Empty) };

                var validationResultFields = from field in TypeDescriptor.GetReflectionType(deserialized).GetFields().Cast<FieldInfo>()
                                       from attribute in field.GetCustomAttributes<ValidationAttribute>()
                                       where !attribute.IsValid(field.GetValue(deserialized))
                                       select new { Propertie = field.Name, ErrorMessage = attribute.FormatErrorMessage(string.Empty) };

                var errors = validationResultFields.Concat(validationResultFields);

                // Add the ValidationResult's to the ModelState
                foreach (var validationResultItem in errors)
                    bindingContext.ModelState.AddModelError(validationResultItem.Propertie, validationResultItem.ErrorMessage);

                // Set successful binding result
                bindingContext.Result = ModelBindingResult.Success(deserialized);
#if NET451
                return Task.FromResult(0);
#else
                return Task.CompletedTask;
#endif
            }
#if NET451
            return Task.FromResult(0);
#else
            return Task.CompletedTask;
#endif
        }
    }

I hacked this together from various internet posts. I wonder if there isn't an easier way to just ask the deserialzied object to validate itself?

For now, I iterate the Properties and Fields, and call the ValidationAttribute.IsValid against the data.

Included in release 2.0.0