Я использую структуру данных Dictionary<string, T>
для хранения телефонных номеров в модели представления. Пользователь может добавить или удалить их на стороне клиента, прежде чем отправлять их обратно на сервер.
История вопроса: Я использую словарь, потому что использую структуру данных List<T>
сASP.NET MVC 5 требует, чтобы имена полей формы содержали последовательные индексы, начинающиеся с нуля, и для JavaScript становится реальной болью добавлять или удалять эти поля на экране без повторного упорядочения значений индекса. Я нашел использование словаря сделало это очень легко. Сейчас я выполняю задачу проверки концепции, чтобы включить внедрение зависимостей, которое позволяет нам использовать наш сеанс NHibernate для запроса базы данных во время проверок, и использовать тот же сеанс, который контроллеры и модели представления используют вместо шаблона «singleton», который 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; }
}
Просмотр валидаторов моделей
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)
в шаблоне редактора не приводит к появлению сообщения проверки в поле формы.
При запуске приложения в режиме отладки я получаю Phones[0].PhoneNumber
для имени поля, котороеимеет сообщение проверки, но имя поля в модели представления равно Phones.Items[123].PhoneNumber
(где 123 - это идентификатор базы данных или отметка времени, сгенерированная new Date().getTime()
в JavaScript).
Итак, я знаю почему рядом с полем не появляется сообщение проверки. Задача состоит в том, как я могу это сделать?
Как проверить словарь с помощью FluentValidation, чтобы сообщения об ошибках появлялись в полях формы при использовании **ValidationMessageFor(model => model.PhoneNumber)
в шаблоне редактора?
Обновление: Похоже, существует проблема GitHub с 2017 года, связанная с: Поддержка проверки IDictionary . Человек нашел обходной путь, но сопровождающий для FluentValidation в основном сказал, что поддержка - это чудовищная боль и потребует серьезной работы по рефакторингу. Я мог бы попытаться возиться с этим и опубликовать ответ, если смогу заставить что-то работать.