TL; DR: Вместо того, чтобы полагаться на помощник тега asp-for
, вы можете установить свой собственный атрибут name
. Это дает вам гибкость, чтобы начать индекс с любого номера, который вы хотите. В идеале вы передадите количество существующих продуктов в GetProduct()
и начнете индексировать его. Кроме того, вам также необходимо добавить в свой атрибут name
префикс Products
, чтобы эти элементы формы были правильно привязаны к вашей коллекции OrderViewModel.Products
при обратной публикации.
<input name="Products[@(startIndex+i)].Quantity" value="@Model[i].Quantity" />
Затем вы можете отфильтровать OrderViewModel.Products
сбор на стороне сервера с использованием LINQ для ограничения результатов выбранными продуктами:
var selectedProducts = c.Products.Where(p => p.IsSelected);
Для более подробного объяснения того, как работает этот подход, а также некоторых вариаций в реализации, прочтите мой полный ответ ниже.
Подробный ответ
Здесь много чего происходит, так что это будет длинный ответ. Я собираюсь начать с предоставления некоторой важной информации о том, как ASP. NET Core MVC соединяет точки между вашей моделью представления, вашим представлением и вашей моделью привязки, так как это поможет лучше понять, как адаптировать это поведение к вашим потребностям. Затем я предложу стратегию решения каждой из ваших проблем.
Примечание: Я не собираюсь писать весь код, так как это приведет к я заново изобрел много кода, который вы уже написали, и дал бы еще более длинный ответ. Вместо этого я собираюсь предоставить контрольный список шагов, необходимых для применения решения к вашему существующему коду.
Фон
Важно отметить, что в то время как ASP. NET Core MVC пытается стандартизировать и упростить рабочий процесс от модели представления к представлению и модели привязки с помощью соглашений (таких как помощник тега asp-for
), каждый из которых не зависит друг от друга.
Итак, когда вы вызываете asp-for
в коллекции, используя, например,
<input asp-for="@Model[i].Quantity" />
Затем он выводит что-то вроде следующего HTML:
<input id="0__Quantity" name="[0].Quantity" value="1" />
И затем, когда вы отправляете это, сервер смотрит в данных вашей формы и использует набор соглашений для сопоставления этих данных с вашей моделью привязки. Таким образом, это может отображаться на:
public async Task<IActionResult> ProductsAsync(List<Product> products) { … }
Когда вы вызываете asp-for
в коллекции, он всегда будет начинать индекс с 0
. И когда он привязывает данные формы к модели привязки, он всегда будет начинать с [0]
и считать.
Но нет причин, по которым вам нужно использовать asp-for
, если вам нужно чтобы изменить это поведение. Вместо этого вы можете самостоятельно установить атрибуты id
и / или name
, если вам нужна гибкость в том, как они создаются.
Примечание: Когда вы это делаете, вы Я хочу убедиться, что вы по-прежнему придерживаетесь одного из соглашений, которые ASP. NET Core MVC уже знакомы с , чтобы обеспечить привязку данных. Хотя, если вы действительно хотите, вы можете вместо создать свои собственные соглашения о привязке .
Проблема 1: Установка индекса
Учитывая приведенный выше фон, если вы хотите настроить начальный индекс, возвращаемый вашим вызовом, на GetProducts()
для вашей второй модели, вам нужно сделать что-то вроде следующего:
Перед позвонив по номеру GetProduct()
, определите, сколько продуктов у вас уже есть, например, подсчитав количество элементов с присвоенным классом card
(т.е. $(".card").length
).
Примечание: Если класс card
не используется исключительно для продуктов, вы можете вместо этого назначить уникальный класс, например product
, каждому элементу tr
в вашем _DisplayOrder
представлении и посчитать это.
Включите это количество в свой вызов GetProduct()
, например, параметр &startingIndex=
:
$('#saveBtn').click(function () {
$.ajax({
url: '/Order/GetProduct?Phoneid=' + $("#CompanyId").val() + "&modelId=" + $('#modelId').val() + "&startingIndex=" + $("#card").length,
type: 'Post',
success: function (data) {
$('#products').append(data);
},
})
})
[HttpPost]
public IActionResult GetProduct(int Phoneid, string[] modelId, int startingIndex = 0) { … }
Передайте это
startingIndex
вашему «частичному» представлению через модель представления; например,
public class ProductListViewModel {
public int StartingIndex { get; set; }
public List<Product> Products { get; set; }
}
Используйте это значение для смещения индекса, записанного на выходе:
<input id="@(Model.StartingIndex+i)__Quantity" name="[@(Model.StartingIndex+i)].Quantity" value="@Model.Products[i].Quantity" />
Это не так аккуратно, как asp-for
, поскольку вам нужно связать много похожей информации, но он предлагает вам гибкость, чтобы гарантировать, что ваши name
значения будут уникальными на странице, независимо от того, сколько раз вы звоните GetProduct()
.
Notes
- Если вы не хотите создавать новую модель представления, вы можете передать
startingIndex
через свой ViewData
словарь вместо этого. Однако я предпочитаю иметь модель представления, которая включает все необходимые мне данные. - При использовании помощника тега
asp-for
он автоматически генерирует атрибут id
, но если вы никогда не ссылаетесь на него через, например, JavaScript, вы можете его опустить. - Браузеры будут отправлять на сервер значения только для элементов формы, которые имеют атрибут
name
. Поэтому, если у вас есть элементы ввода, которые необходимы на стороне клиента, но не необходимы в модели привязки, по какой-то причине вы можете опустить атрибут name
. - Там другие условные обозначения помимо
{Index}__{Property}
, которым вы можете следовать. Но если вы действительно не хотите влезть в сорняки привязки моделей, вам лучше придерживаться одного из существующих соглашений о коллекциях .
Be Будьте осторожны при индексировании!
В соглашениях о привязке моделей для коллекций вы увидите предупреждение:
Форматы данных, в которых используются индексы (.. . [0] ... [1] ...) должны гарантировать, что они пронумерованы последовательно, начиная с нуля. Если в нумерации подстрочных индексов есть пробелы, все элементы после пробела игнорируются. Например, если индексы равны 0 и 2 вместо 0 и 1, второй элемент игнорируется.
Таким образом, при их назначении вам необходимо убедиться, что они являются последовательными без каких-либо пробелы. Если вы используете счетчик (.length
) существующих, например, $(".card")
или $(".product")
элементов на вашей странице, чтобы заполнить значение startingIndex
, то это не должно быть проблемой.
Проблема 2: Отправка этих значений на сервер
Как упоминалось выше, данные любого элемента формы с атрибутом name
будут отправлены на сервер. Поэтому на самом деле не имеет значения, используете ли вы asp-for
, пишете форму вручную, используя HTML, или создаете ее динамически, используя JavaScript. Если есть элемент формы с атрибутом name
, и он находится внутри отправляемого form
, он будет включен в полезные данные.
Отладка данных вашей формы
Вероятно, вы уже знаком с этим, но если нет: если вы используете консоль разработчика своего браузера, вы сможете увидеть эту информацию как часть метаданных страницы при отправке формы. Например, в Google Chrome:
- Go до Инструменты разработчика ( Ctrl + Shift + I )
- Go на вкладку Сеть
- Отправьте форму
- Щелкните имя вашей страницы (обычно первое запись)
- Go на вкладку Заголовки
- Прокрутите вниз до раздела Данные формы (или Параметры строки запроса для запроса
GET
)
Вы должны увидеть что-то вроде:
- [0] .isSelected true
- [0] .Phoneid 4
- [0] .modelId 10
- [0] .Quantity 5
- [0] .price 10,50
- [1] .isSelected true
- […]…
Если вы видите их в Chrome, но не видите эти данные, отраженные в вашем действии контроллера ASP. NET Core MVC, значит, существует разрыв между соглашениями об именах из этих полей и вашей модели привязки - и, таким образом, ASP. NET Core MVC не знает, как сопоставить эти два.
Проблемы привязки
Есть две вероятные проблемы здесь, оба из них могут мешать привязке данных.
Дублирующие индексы
Поскольку вы в настоящее время отправляете повторяющиеся индексы, это может быть вызывая конфликты с данными. Например , если есть два значения для [0].Quantity
, то он будет извлекать их как массив - и может не привязать любое значение, например, к свойству int Quantity
в вашей модели привязки Products
. Я не тестировал это, поэтому я не совсем уверен, как ASP. NET Core MVC справляется с этой ситуацией.
Название коллекции
Когда вы привязываетесь к List<Products>
с помощью помощника тега asp-for
, я полагаю, что он будет использовать соглашение об именах [i].Property
. Это проблема, потому что ваш OrderViewModel
не является коллекцией. Вместо этого они должны быть связаны со свойством Products
в вашей модели привязки. Это можно сделать, добавив к name
префикс Products
. Это будет сделано автоматически, если вы используете asp-for
для привязки к свойству Products
в вашей модели представления - как предложено в ProductListViewModel
выше. Но поскольку вам все равно нужно динамически генерировать name
на основе IndexOffset
, вы можете просто жестко закодировать их как часть своего шаблона:
<input id="Products_@(Model.StartingIndex+i)__Quantity" name="Products[@(Model.StartingIndex+i)].Quantity" value="@Model.Products[i].Quantity" />
Выбранные продукты
Однако проблема все еще существует! Это будет включать все ваших продуктов, даже если они не были выбраны пользователем иным образом. Для решения этой проблемы существует ряд стратегий, от их динамической фильтрации на клиенте до создания настраиваемого связывателя модели, который сначала проверяет атрибут Products_[i]__isSelected
. Однако самый простой - просто разрешить всем им быть привязаны к вашей модели привязки, а затем отфильтровать их перед любой обработкой, например, с помощью LINQ:
var selectedProducts = c.Products.Where(p => p.IsSelected).ToList();
…
repo.SetProducts(selectedProducts);