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

Gre*_*rdt 7 c# validation asp.net-mvc fluentvalidation

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


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

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


项目设置

  • 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; }
}
Run Code Online (Sandbox Code Playgroud)

查看模型验证器

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

页面级模板

@model PersonForm

@Html.EditorFor(model => model.Phones)
Run Code Online (Sandbox Code Playgroud)

PhoneFieldCollection 编辑器模板

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; }
}
Run Code Online (Sandbox Code Playgroud)

PhoneNumberFields 编辑器模板

@model PhoneNumberFields

@Html.EditorFor(model => model.PhoneNumber)
@Html.ValidationMessageFor(model => model.PhoneNumber)
Run Code Online (Sandbox Code Playgroud)

未显示必填字段消息

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

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

所以我知道为什么验证消息没有显示在字段旁边。挑战是,我该如何做到这一点?

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


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