[英]Add item into List from View Loaded Dynamically and pass it to Controller in asp.net core
I am working on Mobile Store Management System's Order page.我正在处理移动商店管理系统的订单页面。 I want to allow users to select a company through a select list, and then select multiple models of that company from another select list which is loaded dynamically through AJAX.
I want to allow users to select a company through a select list, and then select multiple models of that company from another select list which is loaded dynamically through AJAX.
The code for the cascading models is working, but I am unable to send the selected models to the server because it is adding them in the DOM through JavaScript.级联模型的代码正在运行,但我无法将所选模型发送到服务器,因为它通过 JavaScript 将它们添加到 DOM 中。
The following is the code for the cascading selection:以下是级联选择的代码:
<div class="form-group row">
<label class="control-label col-6">Company Name</label>
<div class="col-12">
<select id="CompanyId" class="custom-select mr-sm-2"
asp-items="@(new SelectList(
@ViewBag.Companies,"Phoneid","Com_name"))">
<option value="">Please Select</option>
</select>
</div>
<span class="text-danger"></span>
</div>
<div class="form-group row">
<label class="control-label col-6"></label>
<div class="col-12">
<select id="modelId" multiple class="custom-select mr-sm-2"
asp-items="@(new SelectList(string.Empty,"modelId","model_name","--Select--"))">
<option value="">Please Select</option>
</select>
</div>
<span class="text-danger"></span>
</div>
<div>
<input type="button" id="saveBtn" value="Save" />
</div>
Cascading Code:级联代码:
$("#CompanyId").change(async function()
{
await $.getJSON("/Order/GetAllModels",{ PhoneId: $("#CompanyId").val()},
function(data)
{
$("#modelId").empty();
$.each(data, function (index, row) {
$("#modelId").append("<option value='" + row.modelId + "'>" +
row.model_name + '</option>')
});
});
}
Once the Save button is clicked, I am displaying the product for the currently selected models using a partial view:单击“保存”按钮后,我将使用局部视图显示当前所选模型的产品:
$('#saveBtn').click(function () {
$.ajax({
url: '/Order/GetProduct?Phoneid=' + $("#CompanyId").val() + "&modelId=" + $('#modelId').val(),
type: 'Post',
success: function (data) {
$('#products').append(data);
},
})
})
When the user selects the first company and their two models, and then clicks the Save button, the partial view loads with indexes i=0,i=1
.当用户选择第一家公司及其两个模型,然后单击“保存”按钮时,部分视图会加载索引
i=0,i=1
。 Then, the user selects another company and selects their models.然后,用户选择另一家公司并选择他们的模型。 Again, the partial view renders with same indexes.
同样,局部视图使用相同的索引呈现。 How can I make the indexes unique?
如何使索引唯一? This partial view is rendered when the user clicks the Save button, which renders only the current company's selected models.
当用户单击“保存”按钮时会呈现此局部视图,该按钮仅呈现当前公司的选定模型。
@model List<Mobile_Store_MS.ViewModel.Orders.Products>
<table class="table">
<tbody>
@for (int i = 0; i < Model.Count; i++)
{
<tr class="card d-flex">
<td>
<input asp-for="@Model[i].isSelected" />
</td>
<td>
<input hidden asp-for="@Model[i].Phoneid" /> <input hidden asp-for="@Model[i].modelId" />
@Html.DisplayFor(modelItem => Model[i].com_Name) @Html.DisplayFor(modelItem => Model[i].model_name)
</td>
<td>
<input asp-for="@Model[i].Quantity" />
</td>
<td>
<input class="disabled" readonly asp-for="@Model[i].price" />
</td>
</tr>
}
</tbody>
</table>
How can I send all of the items rendered through the partial view to the server?如何将通过局部视图呈现的所有项目发送到服务器? I just want to send these selected products along with the quantity and price for each model to the server.
我只想将这些选定的产品连同每个 model 的数量和价格一起发送到服务器。 This means binding these items in the product list of the
OrderViewModel
.这意味着将这些项目绑定到
OrderViewModel
的产品列表中。
You can find my OrderViewModel
and Products
model in the following diagram:您可以在下图中找到我的
OrderViewModel
和Products
model:
Can you tell me how to bind Razor items into a list to post to the controller?你能告诉我如何将 Razor 项目绑定到列表中以发布到 controller 吗? I would be very grateful if you give me some suggestions.
如果您能给我一些建议,我将不胜感激。
TL;DR: Instead of relying on the asp-for
tag helper, you can set your own name
attribute. TL;DR:您可以设置自己的
name
属性,而不是依赖asp-for
标签助手。 This gives you the flexibility to start the index at whatever number you want.这使您可以灵活地以您想要的任何数字开始索引。 Ideally, you will pass the number of existing products to
GetProduct()
and start indexing off of that.理想情况下,您会将现有产品的数量传递给
GetProduct()
并开始对其进行索引。 In addition, you also need to prefix your name
attribute with Products
, thus ensuring those form elements are properly bound to your OrderViewModel.Products
collection on post back.此外,您还需要在
name
属性前加上Products
,从而确保这些表单元素在回发时正确绑定到您的OrderViewModel.Products
集合。
<input name="Products[@(startIndex+i)].Quantity" value="@Model[i].Quantity" />
You can then filter the OrderViewModel.Products
collection on the server-side using LINQ to limit the results to selected products:然后,您可以使用 LINQ 在服务器端过滤
OrderViewModel.Products
集合,以将结果限制为选定的产品:
var selectedProducts = c.Products.Where(p => p.IsSelected);
For a more in-depth explanation of how this approach works, as well as some of the variations in the implementation, read my full answer below.要更深入地解释这种方法的工作原理以及实现中的一些变化,请阅读下面的完整答案。
There's a lot going on here, so this is going to be a lengthy answer.这里发生了很多事情,所以这将是一个冗长的答案。 I'm going to start by providing some critical background on how ASP.NET Core MVC connects the dots between your view model, your view, and your binding model, as that will better understand how to adapt this behavior to your needs.
我将首先提供一些关于 ASP.NET Core MVC 如何连接您的视图 model、您的视图和您的绑定 model 之间的点的关键背景,因为这将更好地理解如何使这种行为适应您的需求。 Then I'm going to provide a strategy for solving each of your problems.
然后我将提供一个策略来解决你的每一个问题。
Note: I'm not going to write all of the code, as that would result in me reinventing a lot of code you've already written—and would make for an even longer answer.
注意:我不会编写所有代码,因为这会导致我重新发明你已经编写的许多代码——并且会产生更长的答案。 Instead, I'm going to provide a checklist of steps needed to apply the solution to your existing code.
相反,我将提供将解决方案应用于现有代码所需的步骤清单。
It's important to note that while ASP.NET Core MVC attempts to standardize and simplify the workflow from view model to view to binding model through conventions (such as the asp-for
tag helper) these are each independent of one another.需要注意的是,虽然 ASP.NET Core MVC 试图标准化和简化从视图 model 到视图绑定
asp-for
的工作流程(例如,这些都是彼此独立的标签,例如)
So when you call asp-for
on a collection using eg,因此,当您使用例如在集合上调用
asp-for
时,
<input asp-for="@Model[i].Quantity" />
It then outputs the something like the following HTML:然后它输出类似于以下 HTML 的内容:
<input id="0__Quantity" name="[0].Quantity" value="1" />
And then, when you submit that, the server looks at your form data, and uses a set of conventions to map that data back to your binding model.然后,当您提交时,服务器查看您的表单数据,并使用一组约定map 将该数据返回到您的绑定 model。 So this might map to:
所以这可能 map 到:
public async Task<IActionResult> ProductsAsync(List<Product> products) { … }
When you call asp-for
on a collection, it will always start the index at 0
.当您在集合上调用
asp-for
时,它将始终从0
开始索引。 And when it bind the form data to a binding model, it will always start at [0]
and count up.并且当它将表单数据绑定到绑定 model 时,它将始终从
[0]
开始并向上计数。
But there's no reason you need to use asp-for
if you need to change this behavior.但是,如果您需要更改此行为,则没有理由需要使用
asp-for
。 You can instead set the id
and/or name
attributes yourself if you need flexibility over how they are generated.如果您需要灵活地生成它们,您可以自己设置
id
和/或name
属性。
Note: When you do this, you'll want to make sure you're still sticking to one of the conventions that ASP.NET Core MVC is already familiar with to ensure data binding occurs.
注意:当你这样做时,你需要确保你仍然坚持ASP.NET Core MVC 已经熟悉的约定之一,以确保发生数据绑定。 Though, if you really want to, you can instead create your own binding conventions .
不过,如果您真的愿意,您可以改为创建自己的绑定约定。
Given the above background, if you want to customize the starting index returned from your call to GetProducts()
for your second model, you'll want to do something like the following:鉴于上述背景,如果您想为第二个 model 自定义调用
GetProducts()
返回的起始索引,您需要执行以下操作:
Before calling GetProduct()
, determine how many products you already have by eg counting the number of elements with the card
class assigned (ie, $(".card").length
).在调用
GetProduct()
之前,通过例如计算分配有card
class 的元素数量(即$(".card").length
)来确定您已经拥有多少产品。
Note: If the
card
class is not exclusively used for products, you can instead assign a unique class likeproduct
to eachtr
element in your_DisplayOrder
view and count that.注意:如果
card
class 并非专门用于产品,您可以改为将唯一的 class 类似product
分配给您的_DisplayOrder
视图中的每个tr
元素并计算它。
Include this count in your call to GetProduct()
as eg, a &startingIndex=
parameter:在您对
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
to your "partial" view via a view model;startingIndex
索引中继到您的“部分”视图; eg,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" />
That's not as tidy as asp-for
since you're needing to wire up a lot of similar information, but it offers you the flexibility to ensure that your name
values are unique on the page, no matter how many times you call GetProduct()
.这不像
asp-for
那样整洁,因为您需要连接很多类似的信息,但它为您提供了确保您的name
值在页面上唯一的灵活性,无论您调用多少次GetProduct()
.
startingIndex
via your ViewData
dictionary instead.ViewData
字典传递startingIndex
。 I prefer having a view model that includes all of the data I need, though.asp-for
tag helper, it automatically generates the id
attribute, but if you're not ever referencing it via eg JavaScript you can omit it.asp-for
标签助手时,它会自动生成id
属性,但如果您从未通过例如 JavaScript 引用它,则可以省略它。name
attribute.name
属性的表单元素的值提交给服务器。 So if you have input elements that are needed on the client-side but aren't needed in the binding model, for some reason, you can omit the name
attribute.name
属性。{Index}__{Property}
that you can follow.{Index}__{Property}
之外,您还可以遵循其他约定。 But unless you really want to get into the weeds of model binding, you're best off sticking to one of the existing collection conventions . In the Model Binding conventions for collections , you'll notice a warning:在 collections 的Model 绑定约定中,您会注意到一个警告:
Data formats that use subscript numbers (... [0]... [1]...) must ensure that they are numbered sequentially starting at zero.
使用下标数字 (... [0]... [1]...) 的数据格式必须确保它们从零开始按顺序编号。 If there are any gaps in subscript numbering, all items after the gap are ignored.
如果下标编号有任何间隙,则忽略该间隙之后的所有项目。 For example, if the subscripts are 0 and 2 instead of 0 and 1, the second item is ignored.
例如,如果下标是 0 和 2 而不是 0 和 1,则忽略第二项。
As such, when assigning these, you need to make sure that they're sequential without any gaps.因此,在分配这些时,您需要确保它们是连续的,没有任何间隙。 If you're using the count (
.length
) of existing eg $(".card")
or $(".product")
elements on your page to seed the startingIndex
value, however, then that shouldn't be a problem.但是,如果您使用页面上现有的例如
$(".card")
或$(".product")
元素的计数 ( .length
) 来设置startingIndex
值,那么这应该不是问题。
As mentioned above, any form element with a name
attribute will have its data submitted to the server.如上所述,任何具有
name
属性的表单元素都会将其数据提交给服务器。 So it doesn't really matter if you're using asp-for
, writing out your form manually using HTML, or constructing it dynamically using JavaScript.因此,如果您使用
asp-for
,使用 HTML 手动写出您的表单,或者使用 JavaScript 动态构建它,这并不重要。 If there's a form element with a name
attribute, and it's within the form
being submitted, it will get included in the payload.如果有一个带有
name
属性的表单元素,并且它在提交的form
中,它将被包含在有效负载中。
You're likely already familiar with this, but if not: If you use your browser's developer console, you'll be able to see this information as part of the page metadata when you submit your form.您可能已经对此很熟悉,但如果不熟悉:如果您使用浏览器的开发者控制台,您将能够在提交表单时将此信息作为页面元数据的一部分查看。 For instance, in Google Chrome:
例如,在谷歌浏览器中:
GET
request)GET
请求的查询字符串参数) You should see something like:您应该会看到如下内容:
If you're seeing these in Chrome, but not seeing these data reflected in your ASP.NET Core MVC controller action, then there's a disconnect between the naming conventions of these fields and your binding model—and, thus, ASP.NET Core MVC doesn't know how to map the two.如果您在 Chrome 中看到这些,但在您的 ASP.NET Core MVC controller 操作中没有看到这些数据,那么这些字段的命名约定与您的绑定模型之间存在脱节 - 因此,Z9E0DA8438E1E38A1C30 Core MVC'tCE38A1CZ30 不知道如何map这两个。
There are two likely issues here, both of which might be interfering with your data binding.这里有两个可能的问题,它们都可能会干扰您的数据绑定。
Duplicate Indexes重复索引
Since you are currently submitting duplicate indexes, that could be causing collisions with the data.由于您当前正在提交重复的索引,这可能会导致与数据发生冲突。 Eg, if there are two values for
[0].Quantity
, then it will retrieve those as an array—and may fail to bind either value to eg the int Quantity
property on your Products
binding model.例如,如果
[0].Quantity
有两个值,那么它将以数组的形式检索这些值,并且可能无法将任一值绑定到例如绑定 model 的Products
上的int Quantity
属性。 I haven't tested this, though, so I'm not entirely sure how ASP.NET Core MVC deals with this situation.不过,我还没有对此进行测试,所以我不完全确定 ASP.NET Core MVC 是如何处理这种情况的。
Collection Name集合名称
When you bind to a List<Products>
with the asp-for
tag helper, I believe it will use the [i].Property
naming convention.当您使用
asp-for
标签助手绑定到List<Products>
时,我相信它将使用[i].Property
命名约定。 That's a problem because your OrderViewModel
is not a collection.这是一个问题,因为您的
OrderViewModel
不是一个集合。 Instead, these needs to be associated with the Products
property on your binding model.相反,这些需要与绑定 model 上的
Products
属性相关联。 That can be done by prefixing the name
with Products
.这可以通过在
name
前加上Products
来完成。 This will be done automatically if you use asp-for
to bind to a Products
property on your view model—as proposed on the ProductListViewModel
above.如果您使用
asp-for
绑定到视图模型上的Products
属性,这将自动完成 - 正如上面ProductListViewModel
中所建议的那样。 But since you need to dynamically generate the name
's based on the IndexOffset
anyway, you can just hard-code these as part of your template:但是由于无论如何您都需要根据
IndexOffset
动态生成name
,因此您可以将这些硬编码为模板的一部分:
<input id="Products_@(Model.StartingIndex+i)__Quantity" name="Products[@(Model.StartingIndex+i)].Quantity" value="@Model.Products[i].Quantity" />
There's still a problem, though!不过还是有问题! This is going to include all of your products—even if they weren't otherwise selected by the user.
这将包括您的所有产品——即使它们不是由用户选择的。 There are a number of strategies for dealing with this, from dynamically filtering them on the client, to creating a custom model binder that first validates the
Products_[i]__isSelected
attribute.有很多策略可以解决这个问题,从在客户端动态过滤它们,到创建一个自定义 model 绑定器,首先验证
Products_[i]__isSelected
属性。 The easiest, though, is to simply allow all of them to be bound to your binding model, and then filter them prior to any processing using eg LINQ:不过,最简单的方法是简单地将它们全部绑定到您的绑定 model,然后在任何处理之前使用例如 LINQ 过滤它们:
var selectedProducts = c.Products.Where(p => p.IsSelected).ToList();
…
repo.SetProducts(selectedProducts);
For the 1st question, you can try different things.对于第一个问题,您可以尝试不同的事情。 When you do the ajax call, you get a list of models.
当您调用 ajax 时,您会得到一个模型列表。 For each of these models, add the selected company ID as a property.
对于这些模型中的每一个,将选定的公司 ID 添加为属性。 So you don't have to worry about the index being something unique.
所以你不必担心索引是独一无二的。
As for the 2nd question, should be a relatively easy thing to do.至于第二个问题,应该是比较容易做的事情。 However, more information is needed.
但是,需要更多信息。 1. When the save button is hit, are you doing a full postback?
1.当保存按钮被点击时,你是在做一个完整的回发吗? or it is also an AJAX call?
或者它也是一个 AJAX 电话? 2. Why do you not want to opt for a AJAX call to do the update as well?
2. 为什么您不想同时选择 AJAX 调用来进行更新? So you can based upon the response, redirect the user to a results page, etc.
因此,您可以根据响应,将用户重定向到结果页面等。
If you can create a small sample in a new project, and upload to github, and post the information here.如果你可以在新项目中创建一个小样本,并上传到github,并在此处发布信息。 I should be able to take a look and understand better.
我应该可以看一下并更好地理解。 I will definitely be able to help.
我一定能提供帮助。
Also try reading this thread, it might help也尝试阅读这个线程,它可能会有所帮助
how to persist partial view model data during postback in asp.net mvc 如何在 asp.net mvc 回发期间保留部分视图 model 数据
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.