MVC HtmlHelper与FluentValidation 3.1:获取ModelMetadata IsRequired的麻烦

时间:2011-10-12 06:43:44

标签: asp.net-mvc-3 html-helper fluentvalidation modelmetadata isrequired

我为Label创建了一个HtmlHelper,如果需要关联字段,则会在该Label的名称后面添加一个星号:

public static MvcHtmlString LabelForR<TModel, TValue>(
        this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
    return LabelHelper(
        html,
        ModelMetadata.FromLambdaExpression(expression, html.ViewData),
        ExpressionHelper.GetExpressionText(expression),
        null);
}

private static MvcHtmlString LabelHelper(HtmlHelper helper, ModelMetadata metadata, string htmlFieldName, string text)
{
    ... //check metadata.IsRequired here
    ... // if Required show the star
}

如果我在ViewModel中的属性上使用DataAnnotations和slap [Required],我的私有LabelHelper中的metadata.IsRequired将等于True,一切都将按预期工作。

但是,如果我使用FluentValidation 3.1并添加一个简单的规则:

public class CheckEmailViewModelValidator : AbstractValidator<CheckEmailViewModel>
{
    public CheckEmailViewModelValidator()
    {
        RuleFor(m => m.Email)
            .NotNull()
            .EmailAddress();
    }
}

...在我的LabelHelper metadata.IsRequired将被错误地设置为false。 (验证器有效:你不能提交空字段,它需要像电子邮件一样) 其余的元数据看起来是正确的(例如:metadata.DisplayName =“Email”) 理论上,如果使用Rule .NotNull(),FluentValidator会在属性上使用RequiredAttribute。

供参考: 我的ViewModel:

[Validator(typeof(CheckEmailViewModelValidator))]
public class CheckEmailViewModel
{
    //[Required]
    [Display(Name = "Email")]
    public string Email { get; set; }
}

我的控制器:

public class MemberController : Controller
{
    [HttpGet]
    public ActionResult CheckEmail()
    {
        var model = new CheckEmailViewModel();
        return View(model);
    }
}

感谢任何帮助。

2 个答案:

答案 0 :(得分:4)

默认情况下,MVC将DataAnnotations属性用于两个不同的目的 - 元数据和验证。

在MVC应用程序中启用FluentValidation时,FluentValidation挂钩到验证基础结构但不挂钩元数据--MVC将继续使用元数据属性。如果你想使用FluentValidation进行元数据和验证,那么你需要编写MVC的ModelMetadataProvider的自定义实现,它知道如何查询验证器类 - 这不是FluentValidation开箱即用的。

答案 1 :(得分:3)

我有一个自定义的ModelMetadataProvider,可以增强默认的DataAnnotations,并提供以下内容:

    如果没有通过DisplayAttribute指定,则
  1. 从Camel Case中的属性名拆分字符串填充“DisplayName”。
  2. 如果ModelMetadata.IsRequired设置为false,它会检查是否存在任何流畅的验证器规则(类型为NotNull或NotEmpty)。
  3. 我肯定检查了Jeremy准备的源代码,但我还没准备好进行全面检修,所以我混合并匹配,以免失去默认行为。你可以找到它here

    以下是从this帖子中获取一些其他优点的代码。

    public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
    {
        readonly IValidatorFactory factory;
        public CustomModelMetadataProvider(IValidatorFactory factory) 
            : base() {
            this.factory = factory;
        }
    
        // Uppercase followed by lowercase but not on existing word boundary (eg. the start) 
        Regex _camelCaseRegex = new Regex(@"\B\p{Lu}\p{Ll}", RegexOptions.Compiled);
        // Creates a nice DisplayName from the model’s property name if one hasn't been specified 
    
        protected override ModelMetadata GetMetadataForProperty(
            Func<object> modelAccessor, 
            Type containerType,
            PropertyDescriptor propertyDescriptor) {
    
            ModelMetadata metadata = base.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor);
            metadata.IsRequired = metadata.IsRequired || IsNotEmpty(containerType, propertyDescriptor.Name);
            if (metadata.DisplayName == null)
                metadata.DisplayName = displayNameFromCamelCase(metadata.GetDisplayName());
    
            if (string.IsNullOrWhiteSpace(metadata.DisplayFormatString) && 
                (propertyDescriptor.PropertyType == typeof(DateTime) || propertyDescriptor.PropertyType == typeof(DateTime?))) {
                metadata.DisplayFormatString = "{0:d}";
            }
    
            return metadata;
        }
    
        string displayNameFromCamelCase(string name) {
            name = _camelCaseRegex.Replace(name, " $0");
            if (name.EndsWith(" Id"))
                name = name.Substring(0, name.Length - 3);
            return name;
        }
    
        bool IsNotEmpty(Type type, string name) {
            bool notEmpty = false;
            var validator = factory.GetValidator(type);
    
            if (validator == null)
                return false;
    
            IEnumerable<IPropertyValidator> validators = validator.CreateDescriptor().GetValidatorsForMember(name);
    
            notEmpty = validators.OfType<INotNullValidator>().Cast<IPropertyValidator>()
                                 .Concat(validators.OfType<INotEmptyValidator>().Cast<IPropertyValidator>()).Count() > 0;
            return notEmpty;
        }
    }