Исключенные свойства через BindAttribute и ModelValidator в ASP.NET MVC 3 - PullRequest
2 голосов
/ 19 декабря 2010

Добрый вечер,

У меня проблемы с привязкой и проверкой модели, но я не знаю, нормальное ли это поведение: проблема в том, что, несмотря на BindAttribute (его свойство исключено, правильно заполнено), исключенные свойства проверяются, но не удаляются в словаре ModelState ... поэтому я получаю ошибки в своих представлениях ... относительно исключенного свойства!Doh!

Итак, есть ли способ получить список "неисключенных свойств" непосредственно в моем валидаторе модели, чтобы я мог запретить моей службе валидации проверять исключенные свойства?

Вотпровайдер валидатора и сам валидатор (просто внутренняя оболочка вокруг большого FluentValidator)

internal sealed class ValidationProvider : ModelValidatorProvider {
    private readonly IValidationFactory _validationFactory;

    public ValidationProvider(IValidationFactory validationFactory) {
        _validationFactory = validationFactory;
    }

    public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) {
        if (metadata.ModelType != null) {
            IValidationService validationService;
            if (_validationFactory.TryCreateServiceFor(metadata.ModelType, out validationService)) {
                yield return new ValidationAdapter(metadata, context, validationService);
            }
        }
    }

    private sealed class ValidationAdapter : ModelValidator {
        private readonly IValidationService _validationService;

        internal ValidationAdapter(ModelMetadata metadata,
            ControllerContext controllerContext,
            IValidationService validationService)
            : base(metadata, controllerContext) {
            _validationService = validationService;
        }

        public override IEnumerable<ModelValidationResult> Validate(object container) {
            if (Metadata.Model != null) {
                IEnumerable<ValidationFault> validationFaults;
                if (!_validationService.TryValidate(Metadata.Model, out validationFaults)) {
                    return validationFaults.Select(fault => new ModelValidationResult {
                        MemberName = fault.PropertyInfo.Name,
                        Message = fault.FaultedRule.Message
                    });
                }
            }

            return Enumerable.Empty<ModelValidationResult>();
        }
    }
}

А вот действие:

public class MyModel {
    public string Test { get; set; }
    public string Name { get; set; }
}

[HttpPost]
public ActionResult Test([Bind(Exclude = "Test")] MyModel model) {
    if (ModelState.IsValid) {
        ...
    }

    return View();
}

Здесь я получаю ошибки дляисключено свойство "Test" ... Ха!

Спасибо!

Ответы [ 3 ]

3 голосов
/ 22 декабря 2010

Это ожидаемое поведение. Это изменение (всегда выполняемое при проверке всей модели) было сделано в конце цикла корабля MVC 2 на основе отзывов клиентов (и принципа наименьшего удивления).

Дополнительная информация:

http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html

1 голос
/ 22 декабря 2010

Для тех, кто хочет избежать сценария «все проверить, затем удалить нежелательные свойства», я расширил связыватель модели по умолчанию с помощью поставщика метаданных вложенной модели (поскольку свойство «Свойства» ModelMetadata доступно только для чтения ...):

Итак, теперь я могу проверять только «неисключенные свойства»:

public class OldWayValidationBinder : DefaultModelBinder {
    private readonly ModelMetadataProvider _metadataProvider;

    public ValidationBinder(ModelMetadataProvider metadataProvider) {
        _metadataProvider = metadataProvider;
    }

    protected ModelMetadata CreateModelMetadata(ModelBindingContext bindingContext) {
        var metadataProvider = new ModelMetadataProviderAdapter(
            _metadataProvider, bindingContext.PropertyFilter);

        return new ModelMetadata(metadataProvider,
            bindingContext.ModelMetadata.ContainerType,
            () => bindingContext.ModelMetadata.Model,
            bindingContext.ModelMetadata.ModelType,
            bindingContext.ModelMetadata.PropertyName);
    }

    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
        base.OnModelUpdated(controllerContext, new ModelBindingContext(bindingContext) {
            ModelMetadata = CreateModelMetadata(bindingContext)
        });
    }

    private sealed class ModelMetadataProviderAdapter : ModelMetadataProvider {
        private readonly ModelMetadataProvider _innerMetadataProvider;
        private readonly Predicate<string> _propertyFilter;

        internal ModelMetadataProviderAdapter(
            ModelMetadataProvider innerMetadataProvider,
            Predicate<string> propertyFilter) {
            _innerMetadataProvider = innerMetadataProvider;
            _propertyFilter = propertyFilter;
        }

        public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType) {
            return _innerMetadataProvider.GetMetadataForProperties(container, containerType)
                .Where(metadata => _propertyFilter(metadata.PropertyName));
        }

        public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName) {
            return _innerMetadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName);
        }

        public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType) {
            return _innerMetadataProvider.GetMetadataForType(modelAccessor, modelType);
        }
    }
}

internal sealed class ValidationProvider : ModelValidatorProvider {
    private readonly IValidationFactory _validationFactory;

    public ValidationProvider(IValidationFactory validationFactory) {
        _validationFactory = validationFactory;
    }

    public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) {
        if (metadata.ModelType != null) {
            IValidationService validationService;
            if (_validationFactory.TryCreateServiceFor(metadata.ModelType, out validationService)) {
                yield return new ModelValidatorAdapter(metadata, context, validationService);
            }
        }
    }

    private sealed class ModelValidatorAdapter : ModelValidator {
        private readonly IValidationService _validationService;

        internal ValidationAdapter(ModelMetadata metadata,
            ControllerContext controllerContext,
            IValidationService validationService)
            : base(metadata, controllerContext) {
            _validationService = validationService;
        }

        public override IEnumerable<ModelValidationResult> Validate(object container) {
            if (Metadata.Model != null) {
                IEnumerable<ValidationFault> validationFaults;
                var validatableProperties = Metadata.Properties.Select(metadata => Metadata.ModelType.GetProperty(metadata.PropertyName));
                if (!_validationService.TryValidate(Metadata.Model, validatableProperties, out validationFaults)) {
                    return validationFaults.Select(fault => new ModelValidationResult {
                        MemberName = fault.PropertyInfo.Name,
                        Message = fault.FaultedRule.Message
                    });
                }
            }

            return Enumerable.Empty<ModelValidationResult>();
        }
    }
}

Тем не менее, я считаю, что этот сценарий должен присутствовать в качестве опции в MVC.По крайней мере, список несвязанных свойств должен быть задан как параметр метода GetValidators ModelValidatorProvider!

0 голосов
/ 22 декабря 2010

Я думаю, что «старое поведение» можно легко восстановить, переопределив метод OnModelUpdating в DefaultModelBinder. Пожалуйста, укажите мне правильное направление, если это не хороший способ достичь этого:

internal sealed class OldWayModelBinder : DefaultModelBinder {
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
        foreach (var validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {
            string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);
            if (bindingContext.PropertyFilter(subPropertyName)) {
                if (bindingContext.ModelState.IsValidField(subPropertyName)) {
                    bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);
                }
            }
        }
    }
}

(однако тот факт, что метод IsValidField возвращает true, если заданное свойство является ошибочным, немного странно или есть что-то, чего я не понимаю!)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...