MVC HtmlHelper 与 FluentValidation 3.1:获取 ModelMetadata IsRequired 时遇到问题

发布于 2024-12-09 06:24:30 字数 1902 浏览 1 评论 0原文

我为 Label 创建了一个 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。

供参考: 我的 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);
    }
}

感谢任何帮助。

I created a HtmlHelper for Label that puts a star after the name of that Label if associated field is required:

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
}

If I use DataAnnotations and slap [Required] on the property in my ViewModel, metadata.IsRequired in my private LabelHelper will be equal to True and everything will work as intended.

However, if I use FluentValidation 3.1 and add a simple rule like that:

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

... in my LabelHelper metadata.IsRequired will be incorrectly set to false. (The validator works though: you can't submit empty field and it needs to be an Email like).
The rest of the metadata looks correct (Ex: metadata.DisplayName = "Email").
In theory, FluentValidator slaps RequiredAttribute on property if Rule .NotNull() is used.

For references:
My ViewModel:

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

My Controller:

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

Any help is appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

潇烟暮雨 2024-12-16 06:24:30

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

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

By default, MVC uses the DataAnnotations attributes for two separate purposes - metadata and validation.

When you enable FluentValidation in an MVC application, FluentValidation hooks into the validation infrastructure but not metadata - MVC will continue to use attributes for metadata. If you want to use FluentValidation for metadata as well as validation then you'd need to write a custom implementation of MVC's ModelMetadataProvider that knows how to interrogate the validator classes - this isn't something that FluentValidation supports out of the box.

幸福还没到 2024-12-16 06:24:30

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

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

我确实检查了 Jeremy 准备的源代码,但我还没有准备好进行全面检修,因此我进行了混合和匹配,以免丢失默认行为。您可以在此处找到

这里是具有一些额外优点的代码取自 这篇文章。

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

I have a custom ModelMetadataProvider that enhances the default DataAnnotations one giving the following:

  1. populates "DisplayName" from propertyname splitting string from Camel Case, if none is specified through DisplayAttribute.
  2. If the ModelMetadata.IsRequired is set to false it checks if there are any fluent validator rules present (of type NotNull or NotEmpty).

I definitely checked out the source code that Jeremy has prepared but I was not ready for a total overhaul so I mixed and matched in order not to lose the default behavior. You can find it here

Here is the code with some additional goodness taken from this post.

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文