【问题标题】:MVC HtmlHelper vs FluentValidation 3.1: Troubles getting ModelMetadata IsRequiredMVC HtmlHelper vs FluentValidation 3.1:获取 ModelMetadata IsRequired 的麻烦
【发布时间】:2011-12-05 21:06:51
【问题描述】:

我为标签创建了一个 HtmlHelper,如果需要关联字段,则在该标签的名称后放置一个星号:

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
}

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

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

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

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

参考: 我的视图模型:

[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);
    }
}

感谢任何帮助。

【问题讨论】:

  • 仅限 Jeremy 的评论。我没有写“知道如何询问验证器类的 MVC 的 ModelMetadataProvider 的自定义实现”,主要是因为我不知道如何去做,研究它可能需要很多时间。如果你能提供一个这样的例子,那绝对有帮助!
  • 我也不知道。我扩展了 LabelFor 并向其添加类。这不是最好的解决方案,但我没有时间研究如何实现 ModelMetadataProvider。但是如果我这样做,我会在这里写解决方案。

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


【解决方案1】:

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

当您在 MVC 应用程序中启用 FluentValidation 时,FluentValidation 会挂钩到验证基础架构而不是元数据 - MVC 将继续使用元数据的属性。如果您想将 FluentValidation 用于元数据和验证,那么您需要编写 MVC 的 ModelMetadataProvider 的自定义实现,该实现知道如何询问验证器类 - 这不是 FluentValidation 开箱即用的支持。

【讨论】:

    【解决方案2】:

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

    1. 如果没有通过 DisplayAttribute 指定,则从来自 Camel Case 的属性名拆分字符串填充“DisplayName”。
    2. 如果 ModelMetadata.IsRequired 设置为 false,它会检查是否存在任何流畅的验证器规则(类型为 NotNull 或 NotEmpty)。

    我确实检查了 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;
        }
    }
    

    【讨论】:

    • 您传递给CustomModelMetaDataProvider 的构造函数的IValidatorFactory 是什么?
    • @skeletank 这是一个流畅的验证抽象。我记得它用于从您选择的 DI 容器中解析验证器实例。我不知道自第 3 版以来这是否有所改变,因为这是一篇已有 3 年历史的帖子
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-14
    • 1970-01-01
    • 2011-06-09
    相关资源
    最近更新 更多