我为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);
}
}
感谢任何帮助。
答案 0 :(得分:4)
默认情况下,MVC将DataAnnotations属性用于两个不同的目的 - 元数据和验证。
在MVC应用程序中启用FluentValidation时,FluentValidation挂钩到验证基础结构但不挂钩元数据--MVC将继续使用元数据属性。如果你想使用FluentValidation进行元数据和验证,那么你需要编写MVC的ModelMetadataProvider的自定义实现,它知道如何查询验证器类 - 这不是FluentValidation开箱即用的。
答案 1 :(得分:3)
我有一个自定义的ModelMetadataProvider,可以增强默认的DataAnnotations,并提供以下内容:
我肯定检查了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;
}
}