Я хотел бы связать выражение модели (например, свойство) с компонентом представления — так же, как с помощью вспомогательного HTML (например, @Html.EditorFor()
) или вспомогательного тега (например, <partial for />
) — и повторно использовать эту модель в представлении с помощью вложенных помощников HTML и/или тегов. Я могу определить ModelExpression
как параметр компонента представления и извлечь из него множество полезных метаданных. Помимо этого, я начинаю сталкиваться с блокпостами:
- Как передать и привязать к базовой исходной модели, например. помощник тега
asp-for
? - Как убедиться, что метаданные свойств (например, атрибуты проверки) из
ViewData.ModelMetadata
учитываются? - Как мне собрать полное
HtmlFieldPrefix
для атрибута поляname
?
Я предоставил (упрощенный) сценарий с кодом и результатами ниже, но код раскрывает больше неизвестных, чем ответов. Известно, что большая часть кода неверна, но я включаю его, чтобы у нас была конкретная основа для оценки и обсуждения альтернатив.
Сценарий
Значения списка <select>
должны быть заполнены через репозиторий данных. Предположим, что нецелесообразно или нежелательно заполнять возможные значения как часть, например. исходная модель представления (см. «Альтернативные параметры» ниже).
Образец кода
/Components/SelectListViewComponent.cs
using system;
using Microsoft.AspNetCore.Mvc.Rendering;
public class SelectViewComponent
{
private readonly IRepository _repository;
public SelectViewComponent(IRepository repository)
{
_repository = repository?? throw new ArgumentNullException(nameof(repository));
}
public IViewComponentResult Invoke(ModelExpression aspFor)
{
var sourceList = _repository.Get($"{aspFor.Metadata.Name}Model");
var model = new SelectViewModel()
{
Options = new SelectList(sourceList, "Id", "Name")
};
ViewData.TemplateInfo.HtmlFieldPrefix = ViewData.TemplateInfo.GetFullHtmlFieldName(modelMetadata.Name);
return View(model);
}
}
Примечания
- Использование
ModelExpression
не только позволяет мне вызывать компонент представления с выражением модели, но также дает мне много полезных метаданных через отражение, таких как параметры проверки. - Имя параметра
for
недопустимо в C#, так как это зарезервированное ключевое слово. Таким образом, вместо этого я используюaspFor
, который будет отображаться в вспомогательном формате тега какasp-for
. Это немного хак, но дает знакомый интерфейс для разработчиков. - Очевидно, что код и логика
_repository
будут значительно меняться в зависимости от реализации. В моем собственном случае я фактически извлекаю аргументы из некоторых пользовательских атрибутов. GetFullHtmlFieldName()
не создает полное имя поля HTML; он всегда возвращает любое значение, которое я ему передаю, которое является просто именем выражения модели. Подробнее об этом в разделе «Проблемы» ниже.
/Models/SelectViewModel.cs
using Microsoft.AspNetCore.Mvc.Rendering;
public class SelectViewModel {
public SelectList Options { get; set; }
}
Примечания
- Технически в этом случае я мог просто вернуть
SelectList
непосредственно в представление, так как оно будет обрабатывать текущее значение. Однако если вы привяжете свою модель к вспомогательному тегуasp-for
вашего<select>
, то он автоматически активируетmultiple
, что является поведением по умолчанию при привязке к модели коллекции.
/Views/Shared/Select/Default.cshtml
@model SelectViewModel
<select asp-for=@Model asp-items="Model.Options">
<option value="">Select one…</option>
</select>
Примечания
- Технически значение
@Model
возвращаетSelectViewModel
. Если бы это был<input />
, это было бы очевидно. Эта проблема скрыта из-за того, чтоSelectList
определяет правильное значение, предположительно изViewData.ModelMetadata
. - Вместо этого я мог бы установить
aspFor.Model
, например. свойствоUnderlyingModel
наSelectViewModel
. Это приведет к тому, что имя поля HTML будет{HtmlFieldPrefix}.UnderlyingModel
, и все равно не удастся получить какие-либо метаданные (например, атрибуты проверки) из исходного свойства.
Вариации
Если я не устанавливаю HtmlFieldPrefix
и помещаю компонент представления в контекст, например. a <partial for />
или @Html.EditorFor()
, тогда имена полей будут правильными, так как HtmlFieldPrefix
определяется в родительском контексте. Однако, если я размещу его непосредственно в представлении верхнего уровня, я получу следующую ошибку из-за того, что HtmlFieldPrefix
не определено:
ArgumentException: имя поля HTML не может быть нулевым или пустым. Вместо этого используйте методы Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor или Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper``1.EditorFor с непустым значением аргумента htmlFieldName. (Параметр «выражение»)
Проблемы
HtmlFieldPrefix
не заполняется должным образом полным значением. Например, если имя свойства модели —Country
, оно всегда будет возвращатьCountry
, даже если фактический путь к модели, скажем,ShippingAddress.Country
илиAddresses[2].Country
.- Функция Ненавязчивая проверка jQuery не срабатывает. Например, если свойство, к которому оно привязано, помечено как
[Required]
, то оно здесь не помечается. Предположительно, это связано с тем, что оно привязано кSelectViewModel
, а не к родительскому свойству. - Исходная модель никак не передается в представление компонента представления;
SelectList
может вывести исходное значение изViewData
, но оно потеряно для просмотра. Я мог бы передатьaspFor.Model
через модель представления, но у него не будет доступа к исходным метаданным (таким как атрибуты проверки).
Альтернативные варианты
Некоторые другие варианты, которые я рассмотрел и отклонил для своих вариантов использования.
- Вспомогательные функции тегов. Этого легко добиться с помощью вспомогательных функций тегов. Внедрение зависимостей, таких как репозиторий, в помощник тега менее элегантно, поскольку нет способа создать экземпляр помощника тега через корень композиции, как это можно сделать, например.
IViewComponentActivator
. - Контроллеры. В этом упрощенном примере также можно определить исходную коллекцию в модели представления верхнего уровня рядом с фактическим свойством (например,
Country
для значения,CountryList
для параметров). Это может быть непрактично или элегантно в более сложных примерах. - AJAX: значения можно получить с помощью вызова JavaScript к веб-службе, привязав выходные данные JSON к элементу
<select>
на клиенте. Я использую этот подход в других приложениях, но здесь он нежелателен, так как я не хочу раскрывать весь спектр потенциальной логики запросов в общедоступном интерфейсе. - Явные значения. Я мог бы явно передать модель parent вместе с
ModelExpression
, чтобы воссоздать родительский контекст в компоненте представления. Это немного глупо, поэтому я хотел бы сначала обыграть подходModelExpression
.
Прошлое исследование
Этот вопрос задавали (и отвечали) раньше:
Однако в обоих случаях принятый ответ (один от OP) не полностью исследует вопрос и вместо этого решает, что помощник тега больше подходит для их сценариев. Помощники по тегам великолепны и имеют свое предназначение; Однако я хотел бы полностью изучить исходные вопросы для сценариев, в которых компоненты представления более уместны (например, в зависимости от внешней службы).
Я гоняюсь за кроликом в норе? Или есть варианты, которые может решить более глубокое понимание выражения модели сообществом?