如何使用 FluentValidation 验证 Dictionary<string、T 中的项>视图模型属性

How to validate items in Dictionary<string, T> view model property using FluentValidation

提问人:Greg Burghardt 提问时间:10/18/2019 最后编辑:Greg Burghardt 更新时间:11/3/2022 访问量:6055

问:

我正在使用数据结构在视图模型中存储电话号码。用户可以在客户端添加或删除它们,然后再将它们发布回服务器。Dictionary<string, T>


背景故事:我正在使用字典,因为使用 MVC 5 ASP.NET 的数据结构要求表单字段的名称包含从零开始的顺序索引,并且 JavaScript 在屏幕上添加或删除这些字段而不重新排序索引值变得非常痛苦。我发现使用字典非常容易。现在,我正在执行一个概念证明任务,以启用依赖项注入,该任务允许我们在验证期间使用 NHibernate 会话查询数据库,并使用控制器和视图模型使用的相同会话,而不是 FluentValidation 在 MVC 5 中使用的“单一实例”模式。List<T>

使用上述视图模型的属性时,消息在字段旁边显示得刚刚好,但验证器实例是 AppDomain 中的单例,验证器使用的 NHibernate 会话与控制器使用的会话不同。这会导致数据在数据验证期间变得不同步。检查数据库的验证开始返回意外结果,因为 NHibernate 在服务器上缓存了大量数据,并且它实际上有 2 个单独的缓存。[Validator(typeof(T))]


项目设置

  • ASP.NET MVC 5
  • .NET Framework 4.5.1(但我们可以升级)
  • FluentValidation v8.5.0 版
  • FluentValidation.Mvc5 v8.5.0 版本
  • FluentValidation.ValidatorAttribute v8.5.0 版

查看模型

public class PersonForm
{
    public PhoneFieldsCollection Phones { get; set; }
}

public class PhoneFieldsCollection
{
    public Dictionary<string, PhoneNumberFields> Items { get; set; }
}

public class PhoneNumberFields
{
    [Display(Name="Country Code")]
    [DataType(DataType.PhoneNumber)]
    public string CountryCode { get; set; }

    [Display(Name="Phone Number")]
    [DataType(DataType.PhoneNumber)]
    public string PhoneNumber { get; set; }

    [DataType(DataType.PhoneNumber)]
    public string Extension { get; set; }

    [Display(Name="Type")]
    public string TypeCode { get; set; }
}

查看模型验证器

public class PersonFormValidator : AbstractValidator<PersonForm>
{
    private readonly IPersonRepository repository;

    public PersonFormValidator(IPersonRepository repository)
    {
        // Later on in proof of concept I will need to query the database
        this.repository = repository;

        RuleForEach(model => model.Phones)
            .SetValidator(new PhoneNumberFieldsValidator());
    }
}

public class PhoneNumberFieldsValidator : AbstractValidator<PhoneNumberFields>
{
    public PhoneNumberFieldsValidator()
    {
        RuleFor(model => model.PhoneNumber)
            .NotEmpty();
    }
}

用于验证视图模型的控制器代码:

private bool IsModelStateValid(PersonForm model)
{
    // The `repository` field is an IPersonRepository object from the DI container
    var validator = new PersonFormValidator(repository);
    var results = validator.Validate(model);

    if (results.IsValid)
        return true;

    results.AddToModelState(ModelState, "");

    return false;
}

用于呈现页面的 Razor 模板代码

页面级别模板

@model PersonForm

@Html.EditorFor(model => model.Phones)

PhoneFieldCollection 编辑器模板

@model PhoneFieldsCollection

<fieldset class="form-group form-group-phones">
    <legend class="control-label col-md-3 required">
        Phone Numbers:
    </legend>

    <div class="col-md-9">
        @Html.ValidationMessageFor(model => model, "", new { role = "alert", @class = "alert alert-danger", @for = Html.IdFor(model => model) + "-addButton" })

        <ol class="list-unstyled">
            @foreach (var item in Model.Items)
            {
                if (item.Value.IsClientSideTemplate)
                {
                    <script type="text/html">
                        @Html.EditorFor(model => model.Items[item.Key])
                    </script>
                }
                else
                {
                    @Html.EditorFor(model => model.Items[item.Key])
                }
            }
        </ol>

        <hr />

        <p>
            <button type="button" class="btn btn-default" id="@Html.IdFor(model => model)-addButton"
                    data-dynamiclist-action="add"
                    data-dynamiclist="fieldset.form-group-phones ol">
                <span class="glyphicon glyphicon-plus"></span>
                Add another phone number
            </button>
        </p>
    </div>
</fieldset>

PhoneNumberFields 编辑器模板

@model PhoneNumberFields

@Html.EditorFor(model => model.PhoneNumber)
@Html.ValidationMessageFor(model => model.PhoneNumber)

必填字段消息未显示

当我将表单发布回服务器时,电话号码字段为空,我在页面顶部收到一条验证摘要消息,说“电话号码字段为必填项”,这是我所期望的。但是,在编辑器模板中调用不会导致表单字段显示验证消息。ValidationMessageFor(model => model.PhoneNumber)

在调试模式下运行应用程序时,我会得到具有验证消息的字段的名称,但视图模型中的字段名称是(其中 123 是数据库 ID,或在 JavaScript 中生成的时间戳)。Phones[0].PhoneNumberPhones.Items[123].PhoneNumbernew Date().getTime()

所以我知道为什么验证消息没有显示在字段旁边。挑战在于,我该怎么做?

如何使用 FluentValidation 验证字典,以便在使用 **ValidationMessageFor(model => 模型时,表单字段显示错误消息。PhoneNumber)在编辑器模板中?


更新:看起来 2017 年有一个与此相关的 GitHub 问题:支持 IDictionary Validation。这个人找到了一个解决方法,但 FluentValidation 的维护者基本上说支持这是一个巨大的痛苦,需要大量的重构工作。我可能会尝试自己摆弄这个,如果我能得到一些东西,我会发布一个答案。

C# ASP.NET-MVC 验证 FluentValidation

评论


答: 暂无答案