MVC HtmlHelper vs FluentValidation 3.1: Problems Related to Getting ModelMetadata IsRequired

I created an HtmlHelper for the label, which puts a star after the name of this label, if the corresponding 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] for the property in my ViewModel, the metadata.IsRequired in my personal LabelHelper will be True, and everything will work as intended.

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

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

... the LabelHelper.IsRequired metadata will be incorrectly set to false. (The validator works though: you cannot send an empty field, and it should look like an email).
The rest of the metadata looks correct (for example: metadata.DisplayName = "Email").
In theory, the FluentValidator uses the RequiredAttribute on property if the .NotNull () rule is used.

For reference: 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.

+6
source share
2 answers

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

When you enable FluentValidation in an MVC application, FluentValidation intercepts the validation infrastructure, but not the metadata — MVC will continue to use attributes for the metadata. If you want to use FluentValidation for metadata, as well as for validation, you need to write your own implementation of MVC ModelMetadataProvider that knows how to poll validator classes - this is not what FluentValidation supports out of the box.

+4
source

I have a custom ModelMetadataProvider that improves DataAnnotations by default by providing the following:

  • fills the "DisplayName" from the property name split string from the Camel Case, if none of them are specified through DisplayAttribute.
  • If ModelMetadata.IsRequired is set to false, it checks to see if there are any current validation rules (such as NotNull or NotEmpty).

I definitely checked the source code prepared by Jeremy, but I was not ready for a major overhaul, so I mixed and matched so as not to lose the default behavior. You can find here

Here is the code with some additional kindness 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; } } 
+3
source

Source: https://habr.com/ru/post/899106/


All Articles