MVC HtmlHelper против FluentValidation 3.1: проблемы с получением ModelMetadata IsRequired - PullRequest
6 голосов
/ 12 октября 2011

Я создал 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,metadata.IsRequired в моем личном LabelHelper будет равно True, и все будет работать как положено.

Однако, если я использую FluentValidation 3.1 и добавлю простое правило, подобное этому:

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

... в моих метаданных LabelHelper.IsRequired будет неправильно установлено в false.(Валидатор работает, хотя: вы не можете отправить пустое поле, и оно должно быть похоже на Email).
Остальные метаданные выглядят корректно (например: metadata.DisplayName = "Email").
Теоретически, FluentValidator шлепает RequiredAttribute на свойство, если используется Rule .NotNull ().

Для ссылок: Моя 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 ]

4 голосов
/ 13 октября 2011

По умолчанию MVC использует атрибуты DataAnnotations для двух отдельных целей - метаданных и проверки.

Когда вы включаете FluentValidation в приложении MVC, FluentValidation подключается к инфраструктуре проверки, но не к метаданным - MVC продолжит использовать атрибуты для метаданных.Если вы хотите использовать FluentValidation для метаданных, а также для проверки, вам нужно написать собственную реализацию MVC ModelMetadataProvider, которая знает, как опрашивать классы валидатора - это не то, что FluentValidation поддерживает из коробки.

3 голосов
/ 20 февраля 2013

У меня есть пользовательский ModelMetadataProvider, который расширяет аннотации DataAnnotations по умолчанию, давая следующее:

  1. заполняет "DisplayName" из строки разделения имени свойства из Case Camel, если ни один не указан через DisplayAttribute.
  2. Если для ModelMetadata.IsRequired установлено значение false, он проверяет наличие свободных правил проверки (типа NotNull или NotEmpty).

Я определенно проверил исходный код, который подготовил Джереми, но я не был готов к полной перестройке, поэтому я смешал и сопоставил, чтобы не потерять поведение по умолчанию. Вы можете найти это здесь

Вот код с некоторыми дополнительными преимуществами, взятый из этого поста.

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