Muh*_*ami 3 c# razorengine asp.net-core-mvc asp.net-core
我正在处理移动商店管理系统的订单页面。我想让用户通过选择列表选择一个公司,然后从另一个通过 AJAX 动态加载的选择列表中选择该公司的多个模型。
级联模型的代码正在运行,但我无法将所选模型发送到服务器,因为它通过 JavaScript 将它们添加到 DOM 中。
以下是级联选择的代码:
<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>
Run Code Online (Sandbox Code Playgroud)
级联代码:
$("#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>')
});
});
}
Run Code Online (Sandbox Code Playgroud)
一旦Save按钮被点击,我展示了使用局部视图当前选定的型号的产品:
$('#saveBtn').click(function () {
$.ajax({
url: '/Order/GetProduct?Phoneid=' + $("#CompanyId").val() + "&modelId=" + $('#modelId').val(),
type: 'Post',
success: function (data) {
$('#products').append(data);
},
})
})
Run Code Online (Sandbox Code Playgroud)
当用户选择第一个公司及其两个模型,然后单击Save按钮时,局部视图将加载索引i=0,i=1。然后,用户选择另一家公司并选择他们的型号。同样,局部视图使用相同的索引呈现。如何使索引唯一?当用户单击Save按钮时会呈现此局部视图,该按钮仅呈现当前公司的选定模型。
@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>
Run Code Online (Sandbox Code Playgroud)
如何将通过局部视图呈现的所有项目发送到服务器?我只想将这些选定的产品以及每个型号的数量和价格发送到服务器。这意味着将这些项目绑定到OrderViewModel.
您可以在下图中找到我的OrderViewModel和Products模型:
你能告诉我如何将 Razor 项目绑定到列表中以发布到控制器吗?如果您能给我一些建议,我将不胜感激。
TL;DR:asp-for您可以设置自己的name属性,而不是依赖标签助手。这使您可以灵活地以任何您想要的数字开始索引。理想情况下,您将传递现有产品的数量GetProduct()并开始对其进行索引。此外,您还需要为您的name属性加上前缀Products,从而确保这些表单元素在回发时正确绑定到您的OrderViewModel.Products集合。
<input name="Products[@(startIndex+i)].Quantity" value="@Model[i].Quantity" />
Run Code Online (Sandbox Code Playgroud)
然后,您可以OrderViewModel.Products使用 LINQ 在服务器端过滤集合以将结果限制为选定的产品:
var selectedProducts = c.Products.Where(p => p.IsSelected);
Run Code Online (Sandbox Code Playgroud)
要更深入地解释这种方法的工作原理,以及实现中的一些变化,请阅读下面我的完整答案。
这里发生了很多事情,所以这将是一个冗长的答案。我将首先提供一些关于 ASP.NET Core MVC 如何连接视图模型、视图和绑定模型之间的点的重要背景,因为这将更好地理解如何使这种行为适应您的需求。然后我将提供解决您的每个问题的策略。
注意:我不会编写所有代码,因为这会导致我重新编写您已经编写的大量代码——并且会产生更长的答案。相反,我将提供将解决方案应用于现有代码所需的步骤清单。
需要注意的是,虽然 ASP.NET Core MVC 试图通过约定(例如asp-for标签助手)标准化和简化从视图模型到视图再到绑定模型的工作流,但这些工作流彼此独立。
因此,当您asp-for使用例如调用集合时,
<input asp-for="@Model[i].Quantity" />
Run Code Online (Sandbox Code Playgroud)
然后输出类似于以下 HTML 的内容:
<input id="0__Quantity" name="[0].Quantity" value="1" />
Run Code Online (Sandbox Code Playgroud)
然后,当您提交该数据时,服务器会查看您的表单数据,并使用一组约定将该数据映射回您的绑定模型。所以这可能映射到:
public async Task<IActionResult> ProductsAsync(List<Product> products) { … }
Run Code Online (Sandbox Code Playgroud)
当你调用asp-for一个集合时,它总是从 开始索引0。并且当它将表单数据绑定到绑定模型时,它总是会开始[0]并计数。
但是,如果您需要更改此行为,则没有理由需要使用asp-for。如果您需要灵活地生成id和/或name属性,您可以改为自己设置它们。
注意:当你这样做时,你需要确保你仍然坚持ASP.NET Core MVC 已经熟悉的约定之一,以确保发生数据绑定。不过,如果您真的想要,您也可以创建自己的绑定约定。
鉴于上述背景,如果您想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);
},
})
})
Run Code Online (Sandbox Code Playgroud)
[HttpPost]
public IActionResult GetProduct(int Phoneid, string[] modelId, int startingIndex = 0) { … }
Run Code Online (Sandbox Code Playgroud)
startingIndex通过视图模型将其中继到您的“部分”视图;例如,public class ProductListViewModel {
public int StartingIndex { get; set; }
public List<Product> Products { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
<input id="@(Model.StartingIndex+i)__Quantity" name="[@(Model.StartingIndex+i)].Quantity" value="@Model.Products[i].Quantity" />
Run Code Online (Sandbox Code Playgroud)
这不像asp-for您需要连接大量类似信息那样整洁,但它为您提供了灵活性,以确保您的name值在页面上是唯一的,无论您调用多少次GetProduct()。
startingIndex通过你的ViewData字典来中继。不过,我更喜欢有一个包含我需要的所有数据的视图模型。asp-for标签助手时,它会自动生成id属性,但如果您从未通过例如 JavaScript 引用它,则可以省略它。name属性的表单元素的值。所以,如果你有需要的客户端,但输入的元素并不在绑定模型所需要,出于某种原因,你可以省略的name属性。{Index}__{Property}您可以遵循之外,还有其他约定。但是,除非您真的想深入研究模型绑定,否则最好坚持使用现有的集合约定之一。使用下标数字 (... [0] ... [1] ...) 的数据格式必须确保它们从零开始按顺序编号。如果下标编号有任何间隙,则间隙后的所有项目都将被忽略。例如,如果下标是 0 和 2 而不是 0 和 1,则忽略第二项。
因此,在分配这些时,您需要确保它们是连续的,没有任何间隙。但是,如果您使用页面上.length现有 eg$(".card")或$(".product")元素的计数 ( )来为startingIndex值设置种子,那么这应该不是问题。
如上所述,任何具有name属性的表单元素都会将其数据提交给服务器。因此,无论您是使用asp-for、使用 HTML 手动编写表单还是使用 JavaScript 动态构建它都无关紧要。如果有一个带有name属性的表单元素,并且它在form被提交的范围内,它将被包含在有效负载中。
您可能已经熟悉这一点,但如果不熟悉:如果您使用浏览器的开发者控制台,您将能够在提交表单时看到此信息作为页面元数据的一部分。例如,在谷歌浏览器中:
GET请求)你应该看到类似的东西:
如果您在 Chrome 中看到这些,但没有看到这些数据反映在您的 ASP.NET Core MVC 控制器操作中,那么这些字段的命名约定与您的绑定模型之间存在脱节,因此,ASP.NET Core MVC不知道如何映射两者。
这里有两个可能的问题,这两个问题都可能会干扰您的数据绑定。
重复索引
由于您当前正在提交重复的索引,这可能会导致与数据发生冲突。例如,如果 有两个值[0].Quantity,那么它将以数组的形式检索它们——并且可能无法将任一值绑定到例如绑定模型int Quantity上的属性Products。不过,我还没有对此进行测试,所以我不完全确定 ASP.NET Core MVC 如何处理这种情况。
馆藏名称
当您List<Products>使用asp-for标签助手绑定到 a时,我相信它会使用[i].Property命名约定。这是一个问题,因为你OrderViewModel不是一个集合。相反,这些需要与Products绑定模型上的属性相关联。这可以通过前缀namewith来完成Products。如果您使用asp-for绑定到Products视图模型上的属性,这将自动完成- 正如ProductListViewModel上面所建议的那样。但是由于您需要name根据IndexOffset无论如何动态生成's ,您可以将它们硬编码为模板的一部分:
<input id="Products_@(Model.StartingIndex+i)__Quantity" name="Products[@(Model.StartingIndex+i)].Quantity" value="@Model.Products[i].Quantity" />
Run Code Online (Sandbox Code Playgroud)
不过还是有问题!这将包括您的所有产品——即使它们不是由用户选择的。有许多策略可以解决这个问题,从在客户端动态过滤它们,到创建一个首先验证Products_[i]__isSelected属性的自定义模型绑定器。不过,最简单的方法是简单地将它们全部绑定到您的绑定模型,然后在使用例如 LINQ 进行任何处理之前过滤它们:
var selectedProducts = c.Products.Where(p => p.IsSelected).ToList();
…
repo.SetProducts(selectedProducts);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4460 次 |
| 最近记录: |