ASP.NET MVC 3中通过BindAttribute和ModelValidator排除的属性

时间:2010-12-18 23:35:50

标签: c# asp.net-mvc validation binding

晚上好,

我遇到了模型绑定和验证方面的问题但是我不知道它是否是正常的行为:问题是,尽管BindAttribute(他的属性Excluded正确填充),排除的属性被验证但不是在ModelState字典中删除...所以我的观点中出现错误...关于被排除的属性!卫生署!

那么,有没有办法直接在我的模型验证器中获取“非排除属性”列表,以便我可以告诉我的验证服务不验证排除的属性?

以下是验证器提供程序和验证程序本身(只是一个围绕着很好的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();
}

在这里,我为排除的“测试”属性收到错误......嗯!

谢谢!

3 个答案:

答案 0 :(得分:3)

这是预期的行为。这种变化(总是进行全模型验证)是基于客户反馈(以及最少意外的原则)在MVC 2发货周期中进行的。

更多信息:

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

答案 1 :(得分:1)

对于那些想要避免“验证所有内容然后删除不需要的属性”场景的人,我使用嵌套模型元数据提供程序扩展了默认模型绑定器(因为ModelMetadata的“Properties”属性是readonly ...):< / p>

所以,现在,我只能验证“非排除属性”:

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中的一个选项出现。至少,未绑定的属性列表应作为ModelValidatorProvider的GetValidators方法的参数给出!

答案 2 :(得分:0)

我认为通过覆盖DefaultModelBinder的OnModelUpdating方法可以轻松恢复“旧行为”。请指出正确的方向是否不是实现这一目标的好方法:

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的事实有点奇怪或者有些东西我不明白!)