ASP.NET Core Razor中asp-for绑定集合元素引发索引越界异常及验证问题
Asp.Net Core Razor页面集合字段索引越界与验证失效问题
模型类代码
public class EmployeeInfo { public int EmployeeInfoId { get; set; } public string LastName { get; set; } = null!; public string EmailAddress { get; set; } = null!; public virtual List<EmergencyInfo> EmergencyInfos { get; set; } = new List<EmergencyInfo>(); } public partial class EmergencyInfo { public int EmergencyInfoId { get; set; } public int EmployeeInfoId { get; set; } [DisplayName("Last Name")] [Required] public string? LastName { get; set; } = null!; }
Razor页面代码
<div class="form-group"> @if (Model != null && Model.EmergencyInfos != null && Model.EmergencyInfos.ToList().Count > 0) { var count = Model.EmergencyInfos.Count; @for (var i = 0; i < count; i++) { <div class="form-group row"> <div class="col"> <label class="control-label">LastName</label> <input asp-for="EmergencyInfos[i].LastName" value="@(string.IsNullOrEmpty(Model.EmergencyInfos.ToList()[i].LastName)? "": @Model.EmergencyInfos.ToList()[i].LastName)" class="form-control" />/> <span asp-validation-for="EmergencyInfos[i].LastName" class="text-danger"></span> </div> </div> } } else { <div class="form-group row"> <div class="col"> <label asp-for="EmergencyInfos[0].LastName" class="control-label">LastName</label> <input asp-for="EmergencyInfos[0].LastName" class="form-control" /> <span asp-validation-for="EmergencyInfos[0].LastName" class="text-danger"></span> </div> </div> } </div>
问题现象
使用上述Razor视图时,触发索引越界异常,错误截图如下:
异常信息:
An unhandled exception occurred while processing the request. ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') lambda_method172(Closure , EmployeeInfo ) Stack Query Cookies Headers Routing ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') lambda_method172(Closure , EmployeeInfo ) Microsoft.AspNetCore.Mvc.ViewFeatures.ExpressionMetadataProvider+<>c__DisplayClass0_0<TModel, TResult>.<FromLambdaExpression>g__modelAccessor|0(object container) Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExplorer.get_Model() Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExplorer.get_ModelType() Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.GetInputTypeHints(ModelExplorer modelExplorer)+MoveNext() Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.GetInputType(ModelExplorer modelExplorer, out string inputTypeHint) Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.Process(TagHelperContext context, TagHelperOutput output) Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output) Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync(TagHelperExecutionContext executionContext) AspNetCoreGeneratedDocument.Views_Employee_Create.<ExecuteAsync>b__21_0() in Create.cshtml + <input asp-for="EmergencyInfos[0].LastName" class="form-control" /> 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) AspNetCoreGeneratedDocument.Views_Employee_Create.ExecuteAsync() in Create.cshtml + ViewData["Title"] = "Create"; Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, string contentType, Nullable<int> statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result) Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultAsync>g__Logged|22_0(ResourceInvoker invoker, IActionResult result) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker) Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
若将asp-for="EmergencyInfos[0].LastName"修改为name="EmergencyInfos[0].LastName",索引越界问题消失,但EmergencyInfo.LastName上标记的[Required]验证规则失效,无法触发“Last Name field is required”的提示。
解决方案
问题根源
当EmergencyInfos集合为空时,直接使用EmergencyInfos[0].LastName会触发索引越界——因为集合中没有对应索引的元素。而asp-for标签助手需要访问模型实例来获取元数据(如验证规则),因此必须保证集合中有对应索引的元素存在。
方案1:后端初始化模型(推荐)
在页面的Get请求处理方法中,给EmployeeInfo.EmergencyInfos添加一个空的EmergencyInfo实例,确保集合至少有一个元素:
public IActionResult OnGet() { var model = new EmployeeInfo(); // 添加空实例,避免视图中索引越界 model.EmergencyInfos.Add(new EmergencyInfo()); return Page(model); }
此时可以简化Razor视图,不需要else分支,直接遍历集合即可:
<div class="form-group"> @if (Model != null && Model.EmergencyInfos != null) { for (var i = 0; i < Model.EmergencyInfos.Count; i++) { <div class="form-group row"> <div class="col"> <label asp-for="EmergencyInfos[i].LastName" class="control-label"></label> <input asp-for="EmergencyInfos[i].LastName" class="form-control" /> <span asp-validation-for="EmergencyInfos[i].LastName" class="text-danger"></span> </div> </div> } } </div>
方案2:视图内动态添加空元素
若不想在后端修改,也可以在视图中先检查集合状态,为空则添加空实例:
@{ if (Model != null && Model.EmergencyInfos != null && Model.EmergencyInfos.Count == 0) { Model.EmergencyInfos.Add(new EmergencyInfo()); } } <div class="form-group"> @if (Model != null && Model.EmergencyInfos != null) { for (var i = 0; i < Model.EmergencyInfos.Count; i++) { <div class="form-group row"> <div class="col"> <label asp-for="EmergencyInfos[i].LastName" class="control-label"></label> <input asp-for="EmergencyInfos[i].LastName" class="form-control" /> <span asp-validation-for="EmergencyInfos[i].LastName" class="text-danger"></span> </div> </div> } } </div>
额外优化点
原视图中多次调用Model.EmergencyInfos.ToList()是多余的,因为EmergencyInfos本身就是List<EmergencyInfo>类型,直接使用Model.EmergencyInfos[i]即可,无需重复转换。
内容的提问来源于stack exchange,提问作者Anjali
相关产品推荐
相关产品推荐

