英文:
How to save Invoice Header and invoice rows on single page submit form with .net core razor pages?
问题
1.Explanation
我想打开“创建”页面,填写发票头数据,填写发票动态行数据,并一次性保存所有内容。
(* 更新的代码)
我成功解决了RowList错误,它不允许我添加多行:
https://stackoverflow.com/questions/75716824/how-to-insert-multiple-rows-with-addrange-in-asp-net-core-razor-pages/75717334#75717334
现在我遇到一个错误
1.1 错误
ArgumentNullException: 值不能为空。(参数 'source')
System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
ArgumentNullException: 值不能为空。(参数 'source')
System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
System.Linq.Enumerable.Count
EPIDENT5.Pages.Magazina.Pages_Magazina_Create.
...
@for (int i = 0; i < Model.RowList.Count(); i++)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.GetChildContentAsync(bool useCachedResult, HtmlEncoder encoder)
Microsoft.AspNetCore.Mvc.TagHelpers.RenderAtEndOfFormTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.
EPIDENT5.Pages.Magazina.Pages_Magazina_Create.ExecuteAsync() in Create.cshtml
ViewData["Title"] = "Create";
我知道我遗漏了一些东西,但不明白问题出在哪里。
2.问题
如何完成这个操作?
3.前端代码:
4.后端代码
// 数据绑定部分
[BindProperty]
public InvHeader InvHeader { get; set; } = default!;
public IList
// Onget 方法,其中获取行数
public IActionResult OnGetAsync()
{
var rr = new List
{
new InvRow() { Name = "Apple", Qty = 5, Price = 100 },
new InvRow() { Name = "Peach", Qty = 3, Price = 500 },
new InvRow() { Name = "Ananas", Qty = 1, Price = 1100 },
};
RowList = rr;
return Page();
}
// Onpost 方法
public async Task
{
if (!ModelState.IsValid)
{
return Page();
}
_context.InvHeaders.Add(InvHeader);
await _context.InvRows.AddRangeAsync(RowList);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
英文:
1.Explaination
I want to open the "Create" page, fill the invoice header data, fill the invoice dynamic rows data and save everything on one single submit.
(* Updated code)
I managed to solve the RowList error, which did not let me add multiple rows:
Now i am getting an error
1.1 The error
ArgumentNullException: Value cannot be null. (Parameter 'source')
System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
ArgumentNullException: Value cannot be null. (Parameter 'source')
System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
System.Linq.Enumerable.Count<TSource>(IEnumerable<TSource> source)
EPIDENT5.Pages.Magazina.Pages_Magazina_Create.<ExecuteAsync>b__28_0() in Create.cshtml
...
@for (int i = 0; i < Model.RowList.Count(); i++)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.GetChildContentAsync(bool useCachedResult, HtmlEncoder encoder)
Microsoft.AspNetCore.Mvc.TagHelpers.RenderAtEndOfFormTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count)
EPIDENT5.Pages.Magazina.Pages_Magazina_Create.ExecuteAsync() in Create.cshtml
ViewData["Title"] = "Create";
I know i am missing something but can not understand where the problem is.
2.Question
How can this be done?
3. The frontend code:
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
// This is the header table
<table class="table table-striped border-0"
style="width: 100%; text-align: left;">
<thead class="border-0">
<tr class="border-0">
<td class="border-0" style="min-width:100%;">
<div class="border border-secondary p-4">
<div class="row ">
<div style="float:left;width:50%;">
<div class="form-group m-1">
<label asp-for="InvHeader.Cli" class="control-label" style="font-size:80%;">Client</label>
<input asp-for="InvHeader.Cli" class="form-control" value="clients name" />
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<input asp-for="InvHeader.InvDate" class="form-control" type="date" value="@(DateTime.UtcNow.Date.ToString("yyyy-MM-dd"))" />
<span asp-validation-for="InvHeader.InvDate" class="text-danger"></span>
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<input asp-for="InvHeader.InvNr" class="form-control" value="33" />
<span asp-validation-for="InvHeader.InvNr" class="text-danger"></span>
</div>
</div>
</div>
</div>
</td>
</tr>
</thead>
</table>
//This is the dynamic rows table
<table id="table1" border="0">
<tr style="font-size:80%;">
<th style="width:50%;">Product</th>
<th style="width:5%;">qty</th>
<th style="width:5%;">price</th>
</tr>
foreach (var item in Model.PL)
{
<tr class="border-bottom">
<td>
<select id="Products" asp-for="@Model.PL[@i].ProdId" class="form-control" type="text" name="data[@i][ProdId]" style="width:100%;" >
@foreach (var item in Model.PL)
{
<option value="@item.ProdId"
qty="@qty"
price="@Convert.ToInt32(item.price)">@item.name, @Convert.ToInt32(item.price)</option>
}
</select>
</td>
<td><input asp-for="@Model.MR[@i].qty" class="form-control" type="text" name="qty[@i]" style="width:100%;" /></td>
<td><input asp-for="@Model.MR[@i].price" class="form-control" type="text" name="price[@i]" style="width:100%;" /></td>
</tr>
</table>
</form>
4.The backend code
//The data binding part
[BindProperty]
public InvHeader InvHeader { get; set; } = default!;
public IList<InvRow> RowList { get; set; }
//The onget method, where row count id red
public IActionResult OnGetAsync()
{
var rr = new List<InvRow>()
{
new InvRow() { Name = "Apple", Qty = 5, Price = 100 },
new InvRow() { Name = "Peach", Qty = 3, Price = 500 },
new InvRow() { Name = "Ananas", Qty = 1, Price = 1100 },
};
RowList = rr;
return Page();
}
//The Onpost method
public async Task<IActionResult> OnPostAsync(IList<InvRow> RowList)
{
if (!ModelState.IsValid)
{
return Page();
}
_context.InvHeaders.Add(InvHeader);
await _context.InvRows.AddRangeAsync(RowList);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
答案1
得分: 1
模型绑定通过名称属性绑定属性。与参数匹配的正确名称属性应该是这样的:addrows[index].propertyName
。
不确定您页面中的 PL
是什么,但似乎只有 qty
和 price
输入与 InvRows
模型相关。您需要更改两个输入的名称,如下所示:
<td><input asp-for="@Model.MR[@i].qty" class="form-control" type="text" name="addrows[@i].qty" style="width:100%;" /></td>
<td><input asp-for="@Model.MR[@i].price" class="form-control" type="text" name="addrows[@i].price" style="width:100%;" /></td>
如果选择元素也与 InvRows
模型相关,只需更改选择的名称,如:addrows[@i].ProdId
。无论如何,名称取决于您的模型是什么样子。
另外,您的页面包含重复的 foreach,具有相同的名称,这是不正确的。假设应该是:
@for(int i = 0; i < Model.PL.Count(); i++)
{
<tr class="border-bottom">
<!-- ... 省略其余内容 ... -->
</tr>
}
整个工作演示您可以参考:
// Model
public class InvHeaders
{
// ... 省略其余内容 ...
}
public class InvRows
{
// ... 省略其余内容 ...
}
public class Product
{
// ... 省略其余内容 ...
}
// Page
@page
@model IndexModel
<form method="post">
<!-- ... 省略其余内容 ... -->
</form>
// PageModel
public class IndexModel : PageModel
{
[BindProperty]
public InvHeaders InvHeader { get; set; } = default!;
[BindProperty]
public InvRows InvRow { get; set; } = default!;
public IList<InvRows> MR { get; set; } = default!;
public List<Product> PL { get; set; }
// ... 省略其余内容 ...
}
只是一个注意,下面的默认 option
元素中没有 qty
或 price
属性,不确定您想要做什么,但我需要提醒您这对模型绑定没有意义:
<option value="@item.ProdId" qty="@qty" price="@Convert.ToInt32(item.Price)">@item.name, @Convert.ToInt32(item.price)</option>
英文:
Model Binding binds the property by name attribute. The correct name attribute which matches the parameter should be like:addrows[index].propertyName
.
Not sure what is your PL
in your page, but it seems only qty
and price
input are related to the InvRows
model. You need change the two inputs name like below:
<td><input asp-for="@Model.MR[@i].qty" class="form-control" type="text" name="addrows[@i].qty" style="width:100%;" /></td>
<td><input asp-for="@Model.MR[@i].price" class="form-control" type="text" name="addrows[@i].price" style="width:100%;" /></td>
If the select element is also related to the InvRows
model, just change the select name like: addrows[@i].ProdId
. Any way, the name depends on how is your model like.
Besides, your page contains duplicated foreach with same name, it is not correct. Assume that is should be:
@for(int i = 0;i<Model.PL.Count();i++)
{
<tr class="border-bottom">
<td>
<select id="Products" asp-for="@Model.PL[@i].ProdId" class="form-control" type="text" name="data[@i][ProdId]" style="width:100%;">
@foreach (var item in Model.PL)
{
<option value="@item.ProdId">@item.name, @Convert.ToInt32(item.price)</option>
}
</select>
</td>
<td><input asp-for="@Model.MR[@i].qty" class="form-control" type="text" name="addrows[@i].qty" style="width:100%;" /></td>
<td><input asp-for="@Model.MR[@i].price" class="form-control" type="text" name="addrows[@i].price" style="width:100%;" /></td>
</tr>
}
A whole working demo you could follow:
Model
public class InvHeaders
{
public int CliId { get; set; }
public DateTime InvDate { get; set; }
public string InvNr { get; set; }
}
public class InvRows
{
public int qty { get; set; }
public int price { get; set; }
}
public class Product
{
public int ProdId { get; set; }
public string name { get; set; }
public int price { get; set; }
}
Page
@page
@model IndexModel
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<table class="table table-striped border-0"
style="width: 100%; text-align: left;">
<thead class="border-0">
<tr class="border-0">
<td class="border-0" style="min-width:100%;">
<div class="border border-secondary p-4">
<div class="row ">
<div style="float:left;width:25%;">
<div class="form-group m-1">
<label asp-for="InvHeader.CliId" class="control-label" style="font-size:80%;">Client</label>
<select asp-for="InvHeader.CliId" class="form-control font-weight-bold text-primary" asp-items="ViewBag.FurId"></select>
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<input asp-for="InvHeader.InvDate" class="form-control" type="date" value="@(DateTime.UtcNow.Date.ToString("yyyy-MM-dd"))" />
<span asp-validation-for="InvHeader.InvDate" class="text-danger"></span>
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<input asp-for="InvHeader.InvNr" class="form-control" value="" />
<span asp-validation-for="InvHeader.InvNr" class="text-danger"></span>
</div>
</div>
</div>
</div>
</td>
</tr>
</thead>
</table>
<table id="table1" border="0">
<tr style="font-size:80%;">
<th style="width:50%;">Product</th>
<th style="width:5%;">qty</th>
<th style="width:5%;">price</th>
</tr>
@for(int i = 0;i<Model.PL.Count();i++)
{
<tr class="border-bottom">
<td>
<select id="Products" asp-for="@Model.PL[@i].ProdId" class="form-control" type="text" name="data[@i][ProdId]" style="width:100%;">
@foreach (var item in Model.PL)
{
<option value="@item.ProdId">@item.name, @Convert.ToInt32(item.price)</option>
}
</select>
</td>
<td><input asp-for="@Model.MR[@i].qty" class="form-control" type="text" name="addrows[@i].qty" style="width:100%;" /></td>
<td><input asp-for="@Model.MR[@i].price" class="form-control" type="text" name="addrows[@i].price" style="width:100%;" /></td>
</tr>
}
</table>
<input type="submit" value="POST" />
</form>
PageModel
public class IndexModel : PageModel
{
[BindProperty]
public InvHeaders InvHeader { get; set; } = default!;
[BindProperty]
public InvRows InvRow { get; set; } = default!;
public IList<InvRows> MR { get; set; } = default!;
public List<Product> PL { get; set; }
public void OnGet()
{
//hard-coded the value is just for easy testing
PL = new List<Product>()
{
new Product(){ProdId=1,name="aa",price=12},
new Product(){ProdId=2,name="bb",price=16},
new Product(){ProdId=3,name="cc",price=21}
};
}
public void OnPost(List<InvRows> addrows)
{
//do your stuff....
}
}
Just a note, there is no qty
or price
attribute in default option
element below, not sure what do you want to do but I need remind you that it makes no sense for model binding.
<option value="@item.ProdId" qty="@qty" price="@Convert.ToInt32(item. Price)">
答案2
得分: 0
这是对我有效的部分:
1. 这是前端代码
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<table class="table table-striped border-0" style="width: 100%; text-align: left;">
<thead class="border-0">
<tr class="border-0">
<td class="border-0" style="min-width:100%;">
<div class="border border-secondary p-4">
<div class="row ">
<div style="float:left;width:50%;">
<div class="form-group m-1">
<label asp-for="InvHeader.Cli" class="control-label" style="font-size:80%;">客户</label>
<input asp-for="InvHeader.Cli" class="form-control" value="客户名称" />
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<label asp-for="InvHeader.Cli" class="control-label" style="font-size:80%;">日期</label>
<input asp-for="InvHeader.InvDate" class="form-control" type="date" value="@(DateTime.UtcNow.Date.ToString("yyyy-MM-dd"))" />
<span asp-validation-for="InvHeader.InvDate" class="text-danger"></span>
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<label asp-for="InvHeader.Cli" class="control-label" style="font-size:80%;">编号</label>
<input asp-for="InvHeader.InvNr" class="form-control" value="33" />
<span asp-validation-for="InvHeader.InvNr" class="text-danger"></span>
</div>
</div>
</div>
</div>
</td>
</tr>
</thead>
</table>
<table id="table1" border="0">
<tr style="font-size:80%;">
<th style="width:50%;">名称</th>
<th style="width:5%;">数量</th>
<th style="width:5%;">价格</th>
</tr>
@for (int i = 0; i < Model.RowList.Count(); i++)
{
<tr class="border-bottom">
<td>
<div class="form-group">
<input asp-for="@Model.RowList[@i].Name" class="form-control" name="RowList[@i].Name" />
<span asp-validation-for="@Model.RowList[@i].Name" class="text-danger"></span>
</div>
</td>
<td>
<div class="form-group">
<input asp-for="@Model.RowList[@i].Qty" class="form-control" name="RowList[@i].Qty" value="1" />
<span asp-validation-for="@Model.RowList[@i].Qty" class="text-danger"></span>
</div>
</td>
<td>
<div class="form-group">
<input asp-for="@Model.RowList[@i].Price" class="form-control" name="RowList[@i].Price" value="33" />
<span asp-validation-for="@Model.RowList[@i].Price" class="text-danger"></span>
</div>
</td>
</tr>
}
</table>
<div class="form-group">
<input type="submit" value="创建" class="btn btn-primary" />
<a asp-page="Index">返回列表</a>
</div>
</form>
2. 这是后端代码
[BindProperty]
public InvHeader InvHeader { get; set; } = default!;
public IList<InvRow> RowList { get; set; }
public IActionResult OnGetAsync()
{
var rr = new List<InvRow>()
{
new InvRow() { Name = "苹果", Qty = 5, Price = 100 },
new InvRow() { Name = "桃子", Qty = 3, Price = 500 },
new InvRow() { Name = "菠萝", Qty = 1, Price = 1100 },
};
RowList = rr;
return Page();
}
public async Task<IActionResult> OnPostAsync(IList<InvRow> RowList)
{
if (!ModelState.IsValid)
{
return Page();
}
_context.InvHeaders.Add(InvHeader);
await _context.InvRows.AddRangeAsync(RowList);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
英文:
This what worked for me:
1. This is the frontend code
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<table class="table table-striped border-0"
style="width: 100%; text-align: left;">
<thead class="border-0">
<tr class="border-0">
<td class="border-0" style="min-width:100%;">
<div class="border border-secondary p-4">
<div class="row ">
<div style="float:left;width:50%;">
<div class="form-group m-1">
<label asp-for="InvHeader.Cli" class="control-label" style="font-size:80%;">Client</label>
<input asp-for="InvHeader.Cli" class="form-control" value="clients name" />
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<label asp-for="InvHeader.Cli" class="control-label" style="font-size:80%;">Date</label>
<input asp-for="InvHeader.InvDate" class="form-control" type="date" value="@(DateTime.UtcNow.Date.ToString("yyyy-MM-dd"))" />
<span asp-validation-for="InvHeader.InvDate" class="text-danger"></span>
</div>
</div>
<div style="float:left;width:25%;">
<div class="form-group m-1">
<label asp-for="InvHeader.Cli" class="control-label" style="font-size:80%;">Nr</label>
<input asp-for="InvHeader.InvNr" class="form-control" value="33" />
<span asp-validation-for="InvHeader.InvNr" class="text-danger"></span>
</div>
</div>
</div>
</div>
</td>
</tr>
</thead>
</table>
<table id="table1" border="0">
<tr style="font-size:80%;">
<th style="width:50%;">Name</th>
<th style="width:5%;">qty</th>
<th style="width:5%;">price</th>
</tr>
@for (int i = 0; i < Model.RowList.Count(); i++)
{
<tr class="border-bottom">
<td>
<div class="form-group">
<input asp-for="@Model.RowList[@i].Name" class="form-control" name="RowList[@i].Name" />
<span asp-validation-for="@Model.RowList[@i].Name" class="text-danger"></span>
</div>
</td>
<td>
<div class="form-group">
<input asp-for="@Model.RowList[@i].Qty" class="form-control" name="RowList[@i].Qty" value="1" />
<span asp-validation-for="@Model.RowList[@i].Qty" class="text-danger"></span>
</div>
</td>
<td>
<div class="form-group">
<input asp-for="@Model.RowList[@i].Price" class="form-control" name="RowList[@i].Price" value="33" />
<span asp-validation-for="@Model.RowList[@i].Price" class="text-danger"></span>
</div>
</td>
</tr>
}
</table>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
<a asp-page="Index">Back to List</a>
</div>
</form>
2. This is the backend code
[BindProperty]
public InvHeader InvHeader { get; set; } = default!;
public IList<InvRow> RowList { get; set; }
public IActionResult OnGetAsync()
{
var rr = new List<InvRow>()
{
new InvRow() { Name = "Apple", Qty = 5, Price = 100 },
new InvRow() { Name = "Peach", Qty = 3, Price = 500 },
new InvRow() { Name = "Ananas", Qty = 1, Price = 1100 },
};
RowList = rr;
return Page();
}
// To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync(IList<InvRow> RowList)
{
if (!ModelState.IsValid)
{
return Page();
}
_context.InvHeaders.Add(InvHeader);
await _context.InvRows.AddRangeAsync(RowList);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论