Проверка данных для каждого элемента в списке моей ViewModel

Чтобы выполнить проверку с помощью регулярного выражения, я обычно делаю:

// In my ViewModel
[RegularExpression("MyRegex", ErrorMessageResourceName = "MyErrorMessage")]
public string MyField { get; set; }

И помощник HTML

@Html.TextBoxFor(model => model.MyField)

генерирует разметку, которая выглядит следующим образом:

<input type="text" class="valid" name="MyField" value="" id="MyField" data-val="true" data-val-regex-pattern="MyRegex" data-val-regex="MyErrorMessage"></input>

Проблема в том, что я хочу иметь динамическое количество полей и сейчас использую

// In my ViewModel
[RegularExpression("MyRegex", ErrorMessageResourceName = "MyErrorMessage")]
public IList<string> MyField { get; set; }

Этот раз

@Html.TextBoxFor(model => model.MyField[0])

будет генерировать (без атрибутов html регулярного выражения)

<input id="MyField_0_" type="text" value="" name="MyField[0]"></input>

Как обеспечить создание атрибутов data-val html при привязке элементов списка с атрибутом проверки DataAnnotation в моей модели представления?


person Pierre Arlaud    schedule 28.04.2014    source источник
comment
Я думаю, что ваша HTML-разметка должна иметь data-val-regex = "MyErrorMessage", а не data-val-required = "MyErrorMessage", поскольку у вас нет аннотации [Required] к вашему ресурсу.   -  person Bhushan Shah    schedule 02.05.2014
comment
@AmateurProgrammer Ошибка копирования/вставки, я полагаю. Отредактировано.   -  person Pierre Arlaud    schedule 02.05.2014


Ответы (3)


На самом деле аннотации данных не могут применяться к элементам списка. Что вам нужно сделать, так это создать класс-оболочку и применить аннотацию данных к элементам в классе-оболочке, например:

public IList<MyField> MyFields {get;set;}

public class MyField
{
    [RegularExpression("MyRegex", ErrorMessageResourceName = "MyErrorMessage")]
    public string Value
}

Применение:

@Html.TextBoxFor(model => model.MyFields[0].Value)
person Claies    schedule 02.05.2014
comment
Звучит разумно, и я предполагаю, что процесс проверки не будет учитывать значение? - person Pierre Arlaud; 02.05.2014
comment
он будет рассматривать строку Value как примитив, который может быть проверен, а не как объект, который не может быть проверен. - person Claies; 02.05.2014

Вы используете DataAnnotations для проверки. Насколько я понимаю, вы ищете способ применить проверку DataAnnotation к каждому элементу списка.

Всякий раз, когда вызывается Html.EditorFor, он извлекает ModelMetadata модели, которая была передана ему, а затем извлекает все ModelValidators, связанные с этой моделью. Именно наличие этих ModelValidators приводит к атрибутам data-val-* в HTML.

Когда Html.EditorFor передается в виде списка в качестве модели (или любого перечисляемого, если на то пошло), он сначала извлекает ModelMetadata и связанные валидаторы для свойства — в вашем случае он извлекает ModelMetadata, связанный со свойством «MyField», за которым следует валидаторы — в данном случае «RegularExpression». Затем он перебирает список строк и получает ModelMetadata и Validators для каждой строки. Хотя ModelMetadata создается для каждой строки, для этих строк не указаны средства проверки. По этой причине строка отображается, но атрибуты проверки не добавляются в элемент HTML.

На мой взгляд, то, что вы ищете, может быть достигнуто путем добавления валидатора, указанного в свойстве MyField, ко всем элементам списка во время выполнения.

Это можно сделать с помощью

  1. Написание общего шаблона редактора для всех коллекций
  2. Установка текущего ModelMetadataProvider в DataAnnotationsModelMetadataProvider
  3. Переопределение метода GetValidators для DataAnnotationsModelValidatorProvider.

Шаблон общего редактора для шага 1 приведен ниже.

@model System.Collections.Generic.IEnumerable<object>
@{
    ViewBag.Title = "Collection";
    var modelMetadata = this.ViewData.ModelMetadata;
    var validators = modelMetadata.GetValidators(ViewContext).ToList();
    ViewContext.HttpContext.Items["rootValidators"] = validators;
}

@foreach (var item in Model)
{
    @Html.EditorFor(m => item)
}

Вы можете видеть в приведенном выше коде, что мы получаем все валидаторы, указанные в списке. Эти валидаторы будут добавлены к элементам списка позже. Они были сохранены в HttpContext.Items для использования в нашем пользовательском ModelValidatorProvider.

Шаг 2. В Global.asax введите следующий код:

ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new DAModelValidatorProvider());

ModelMetadataProviders.Current = new CachedDataAnnotationsModelMetadataProvider();

Шаг 3. Напишите свой собственный ModelValidatorProvider, переопределив метод GetValidators, как показано в приведенном ниже коде.

public class DAModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {
        var validators = base.GetValidators(metadata, context, attributes).ToList();

        // get root validators of the collection. this was stored in the editor template - fetching it for use now.
        // fetching the rootvalidators inside this method is a bad idea because we have to call GetValidators method on the 
        // containers ModelMetadata and it will result in a non-terminal recursion
        var rootValidators = context.HttpContext.Items["rootValidators"] as IEnumerable<ModelValidator>;
        if (rootValidators != null)
        {
            foreach (var rootValidator in rootValidators)
            {
                validators.Add(rootValidator);
            }
        }

        return validators;
    }
}

Выполнение вышеуказанных 3 шагов действительно сработало для меня. Однако я использовал Html.EditorFor вместо Html.TextBoxFor. Используя Html.EditorFor, я не дал мне правильных атрибутов идентификатора и имени - я считаю, что это тривиальная проблема в схеме вещей. Я создал для этого решение и загрузил его на https://github.com/swazza85/Stackoverflow. так что вы можете попробовать и посмотреть, соответствует ли это вашим потребностям. То, что я сделал здесь, ни в коем случае не является полным решением, но, надеюсь, оно поможет вам работать без необходимости менять свои модели.

Привет, Сваруп.

person swazza85    schedule 02.05.2014
comment
Здравствуйте, интересные идеи у вас есть. Вместо того, чтобы решать проблему с помощью простого решения, такого как другой ответ, вы пытаетесь углубиться в структуру .NET с загружаемым примером, и я считаю это почетным (большое спасибо!). Я не уверен, что понимаю, почему это не работает с TextBoxFor и почему не работают атрибуты id и name. Не могли бы вы пролить свет на этот вопрос? - person Pierre Arlaud; 05.05.2014
comment
Поскольку я использовал шаблоны редактора, я использовал EditorFor вместо TextBoxFor. Насколько мне известно, TextBoxFor использует построитель тегов для создания элемента ввода типа = текст, тогда как EditorFor просматривает доступные шаблоны редактора и соответствующим образом создает элемент HTML — отсюда и использование EditorFor вместо TextBoxFor. - person swazza85; 05.05.2014
comment
Атрибуты id и name требуют указания строки постфикса для метода Html.Editor. На шаге 1 ответа, если вы замените цикл foreach циклом foreach, приведенным ниже, вы должны правильно отобразить поля имени и идентификатора. @{ вар я = 0; foreach (элемент var в модели) { @Html.Editor(string.Format([{0}], i)); я++; } } Надеюсь это поможет. - person swazza85; 05.05.2014
comment
отличное решение, мне помогло! Я считаю, что это та область, в которой MVC может улучшиться. - person jmzagorski; 26.02.2015
comment
рада, что смогла быть полезной :) - person swazza85; 26.02.2015
comment
@ swazza85, безопасно ли очищать все остальные ValidationProviders только для этого одного пользовательского класса? - person jmzagorski; 26.02.2015

Я использовал ответ @swazza85, но мне пришлось изменить его для моей ситуации. Надеюсь, если кто-то еще использует его решение, они смогут извлечь выгоду из моей модификации. Мне пришлось изменить IEnumerable<object> на IList<object> (или в моем случае IList<decimal?>, потому что IList<object> выдает ошибку.). Затем мне пришлось использовать итератор for, потому что слово item добавлялось к атрибуту имени, а связыватель модели не связывал эти элементы с моей моделью.

@model System.Collections.Generic.IList<decimal?>

@{
    ViewBag.Title = "Collection";
    var modelMetadata = this.ViewData.ModelMetadata;
    var validators = modelMetadata.GetValidators(ViewContext).ToList();
    ViewContext.HttpContext.Items["rootValidators"] = validators;
}

@for (var i = 0; i < Model.Count(); i++)
{
    @Html.EditorFor(model => Model[i], new { htmlAttributes = new { @class = "form-control" } })
    @Html.ValidationMessageFor(model => Model[i], "", new { @class = "text-danger" })
}

Также, если вы не хотите очищать своих провайдеров в файле Global.asax, просто верните валидаторы в операторе if и верните пустой список за его пределами, просто обратите внимание, что этот шаблон редактора должен быть последним в ваших представлениях, иначе возникнут проблемы. с другими свойствами или шаблонами. Вы можете установить ViewContext.HttpContext.Items["rootValidators"] = null в конце шаблона.

  protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {
      var validators = base.GetValidators(metadata, context, attributes).ToList();

      // get root validators of the collection. this was stored in the editor template - fetching it for use now.
      // fetching the rootvalidators inside this method is a bad idea because we have to call GetValidators method on the 
      // containers ModelMetadata and it will result in a non-terminal recursion
      var rootValidators = context.HttpContext.Items["rootValidators"] as IEnumerable<ModelValidator>;

      if (rootValidators != null)
      {
        foreach (var rootValidator in rootValidators)
        {
          validators.Add(rootValidator);
        }
        return validators;
      }

      return new List<ModelValidator>();
    }
person jmzagorski    schedule 26.02.2015