如何在 FluentValidation 中创建警告消息

How to create warning messages in FluentValidation

我想根据枚举类型为验证消息添加样式。 FluentValidation 提供了使用 WithState 方法为消息添加自定义状态的可能性。根据使用的枚举,它会在 HTML 中为该消息添加 class,因此稍后我可以为其添加样式。

模型验证器class:

public class SampleModelValidator : AbstractValidator<SampleModelValidator>
{
    public SampleModelValidator()
    {
        RuleFor(o => o.Age)).NotEmpty()
                // Using custom state here
                .WithState(o => MsgTypeEnum.WARNING)
                .WithMessage("Warning: This field is optional, but better fill it!");
    }
}

控制器动作方法:

[HttpPost]
public ActionResult Submit(SampleModel model)
{
    ValidationResult results = this.validator.Validate(model);
    int warningCount = results.Errors
                .Where(o => o.CustomState?.ToString() == MsgTypeEnum.WARNING.ToString())
                .Count();
    ...
}

我注意到 ASP.NET MVC 默认使用 unobtrusive.js 并在每个错误消息中添加 class .field-validation-error。所以我想需要以某种方式覆盖该逻辑。

如何根据提供的枚举类型向验证消息添加样式?

我想出了如何实现它。首先需要创建一个 html 助手,它将构建 html 标签并向它们添加必要的 类。这个助手允许为一个 field/property 显示多个不同类型的消息。

很棒的文章,解释了如何去做,可能是唯一的一篇!
http://www.pearson-and-steel.co.uk/

Html 辅助方法

public static MvcHtmlString ValidationMessageFluent<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, int? itemIndex = null)
{
    List<ValidationFailure> validationFailures = html.ViewData["ValidationFailures"] as List<ValidationFailure>;
    string exprMemberName = ((MemberExpression)expression.Body).Member.Name;
    var priorityFailures = validationFailures.Where(f => f.PropertyName.EndsWith(exprMemberName));

    if (priorityFailures.Count() == 0)
    {
        return null;
    }

    // Property name in 'validationFailures' may also be stored like this 'SomeRecords[0].PropertyName'
    string propertyName = itemIndex.HasValue ? $"[{itemIndex}].{exprMemberName}" : exprMemberName;

    // There can be multiple messages for one property
    List<TagBuilder> tags = new List<TagBuilder>();
    foreach (var validationFailure in priorityFailures.ToList())
    {
        if (validationFailure.PropertyName.EndsWith(propertyName))
        {
            TagBuilder span = new TagBuilder("span");
            string customState = validationFailure.CustomState?.ToString();

            // Handling the message type and adding class attribute
            if (string.IsNullOrEmpty(customState))
            {
                span.AddCssClass(string.Format("field-validation-error"));
            }
            else if (customState == MsgTypeEnum.WARNING.ToString())
            {
                span.AddCssClass(string.Format("field-validation-warning"));
            }

            // Adds message itself to the html element
            span.SetInnerText(validationFailure.ErrorMessage);
            tags.Add(span);
        }
    }

    StringBuilder strB = new StringBuilder();
    // Join all html tags togeather
    foreach(var t in tags)
    {
        strB.Append(t.ToString());
    }

    return MvcHtmlString.Create(strB.ToString());
}

此外,内部控制器操作方法需要获取验证器错误并将它们存储在会话中。

控制器动作方式

// In controller you also need to set the ViewData. I don't really like to use itself
// but did not found other solution
[HttpPost]
public ActionResult Submit(SampleModel model)
{
   IList<ValidationFailure> errors = this.validator.Validate(model).Errors;
   ViewData["ValidationFailures"] = errors as List<ValidationFailure>;

    ...
}

并且只需在视图中使用 html 助手。

查看

// In html view (using razor syntax)
@for (int i = 0; i < Model.SomeRecords.Count(); i++)
{
    @Html.ValidationMessageFluent(o => Model.SomeRecords[i].PropertyName, i)

    ...
}

只有在遍历列表时才需要最后一个索引参数。