Ненавязчивая проверка не работает с ViewComponents - PullRequest
0 голосов
/ 16 февраля 2020

Я реализую форму с ASP. NET Core v3.1.

У меня есть код для раскрывающегося списка на странице Razor, который выглядит следующим образом:

<div class="form-group">
    <label asp-for="MySelectedItem" class="m-b-none"></label>
    <help-text asp-for="MySelectedItem"></help-text>
    <div class="input-group">
        <select asp-for="MySelectedItem" asp-items="@Model.MyItems" class="form-control"></select>
        <partial name="_ValidationIcon" />
    </div>
    <span asp-validation-for="MySelectedItem" class="validation-message"></span>
</div>

В моем классе модели я включил правило проверки, чтобы гарантировать, что допустимая опция должен быть выбран. Это хорошо выглядит:

enter image description here

Теперь я хочу обобщить это как компонент представления, чтобы мне не приходилось вставлять этот кусок кода в любое время Я хочу выпадающий. Вот моя реализация:

public class DropdownViewComponent : ViewComponent
{
    public IEnumerable<SelectListItem> Items { get; set; }

    public ModelExpression SelectedItem { get; set; }

    public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, IEnumerable<SelectListItem> items)
    {
        Items = items;
        SelectedItem = selectedItem;
        return View(this);
    }
}

/ Dropdown / Default.cs html

@model Web.ViewComponents.DropdownViewComponent

<div class="form-group">
    <label asp-for="SelectedItem" class="m-b-none"></label>
    <help-text asp-for="SelectedItem"></help-text>
    <div class="input-group">
        <select asp-for="SelectedItem" asp-items="@Model.Items" class="form-control"></select>
        <partial name="_ValidationIcon" />
    </div>
    <span asp-validation-for="SelectedItem" class="validation-message"></span>
</div>

Использование:

<vc:dropdown selected-item="MySelectedItem" items="Model.MyItems"></vc:dropdown>

Это Код правильно отображает выпадающий список, но атрибуты проверки отсутствуют в отображаемом HTML. Почему?

enter image description here

Я также не уверен, как получить DisplayName из выражения модели, которое я передал компоненту вида.

Где я ошибся?

Спасибо.

1 Ответ

1 голос
/ 17 февраля 2020

Предположительно, ваши атрибуты проверки находятся в свойстве MyItems вашей родительской модели, которая здесь не указана. Например,

public class ParentModel {
  [Required]
  public IEnumerable<SelectListItem> MyItems { get; set; }
}

Таким образом, проблема заключается в том, что ваше представление больше не привязывается к этой модели или ее свойству MyItems. Вместо этого он привязывается к свойству Items в модели DropdownViewComponent, которая не имеет каких-либо атрибутов проверки. Оба эти свойства могут указывать на одну и ту же IEnumerable<SelectListItem> ссылку на объект , но их метаданные совершенно разные. Таким образом, ASP. NET Ядро правильно отображает атрибуты проверки, связанные со свойством DropdownViewComponent.Items.

Я вижу это как неудачное ограничение компонентов представления - но одно это концептуально трудно понять. Для получения дополнительной информации см. мой ответ на аналогичный вопрос, который я ранее задал, Как связать ModelExpression с ViewComponent в ASP. NET Core .

Тем не менее, учитывая ваши конкретные требования, вы можете обойти эту проблему, передав модель, которая содержит целевое свойство для вашего компонента представления, вместо передачи значения из само свойство target, а затем ретранслировать , что через модель представления вашего компонента представления:

public class DropdownViewComponent : ViewComponent
{
    public ParentModel ParentModel { get; set; }

    public ModelExpression SelectedItem { get; set; }

    public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, ParentModel model)
    {
        ParentModel = model;
        SelectedItem = selectedItem;
        return View(this);
    }
}

Примечание: Обычно я создаю отдельный, легкий модель представления для вашего компонента представления, вместо того, чтобы передавать объект компонента представления вниз к его представлению. Но я поддерживаю эту структуру для согласованности с вашим исходным кодом.

После этого вы сможете привязаться к исходному свойству в представлении компонента представления, тем самым поддерживая все исходные атрибуты проверки:

<select asp-for="SelectedItem" asp-items="@Model.ParentModel.MyItems"></select>

В первом приближении это не очень выгодно для вас - и может даже победить всю вашу причину преследования компонента представления в первую очередь - поскольку заставляет вас работать против одной модели. Если несколько представлений с разными моделями хотят использовать этот компонент представления, это создает некоторые проблемы. Однако вы можете смягчить их, введя уровень абстракции.

Существует несколько подходов к этому, таких как создание интерфейса, но я рекомендую разработать специализированный класс списка, который вы используете для моделирования IEnumerable<SelectListItem>:

public class DropdownList: List<SelectListItem> {
  public virtual IEnumerable<SelectListItem> Items { get; } = this;
}

И затем отработка этого в вашем DropdownViewComponent:

public class DropdownViewComponent : ViewComponent
{
    public DropdownList DropdownList { get; set; }

    public ModelExpression SelectedItem { get; set; }

    public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, DropdownList dropdownList)
    {
        DropdownList = dropdownList;
        SelectedItem = selectedItem;
        return View(this);
    }
}

И, наконец, реализация этого следующим образом в представлении компонента вашего представления:

<select asp-for="SelectedItem" asp-items="@Model.DropdownList.Items"></select>

Это позволит вам переопределить этот класс для добавления атрибутов по мере необходимости. Например,

public class RequiredDropdownList : DropdownList {
  [Required]
  public override Items { get; } = this;
}

Примечание: Если вам нужно использовать различные коллекции на разных моделях представлений, и для их объединения вы использовали IEnumberable<SelectListItem>, этот подход не сработает. В этом случае создание чего-то вроде IDropdownList интерфейса имеет больше смысла. Несмотря на это, концепция практически идентична.

...