ASP.NET MVC脚手架生成的Controller无法创建数据库数据
我在ASP.NET MVC Web应用里添加了新的脚手架项,但控制器的Create方法无法向数据库插入数据,提交后直接退回创建页面,且无任何错误提示。
控制器代码(FacilitiesController的Create方法)
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("Id,Name,Status,Capacity,OpenedTime,ClosedTime,ServiceID")] Facility facility) { if (ModelState.IsValid) { _context.Add(facility); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } ViewData["ServiceID"] = new SelectList(_context.Service, "Id", "Id", facility.ServiceID); return View(facility); }
模型定义
Service与Facility为一对多关系:
public enum PricingType { Item, Session, Hour, Day, Week, Month } public partial class Service { public Service() { this.Facilities = new HashSet<Facility>(); } public int Id { get; set; } public string Name { get; set; } public double Price { get; set; } public PricingType PricingType { get; set; } public ICollection<Facility> Facilities { get; set; } } public enum FacilityStatus { Available, Reserved, Occupied, Cleaning, Maintenance, Unknown } public class Facility { public int Id { get; set; } public string? Name { get; set; } public FacilityStatus? Status { get; set; } public int? Capacity { get; set; } [DisplayName("Opened Time")] [Range(0.0, 24.0)] public double? OpenedTime { get; set; } [DisplayName("Closed Time")] [Range(0.0, 24.0)] public double? ClosedTime { get; set; } public int ServiceID { get; set; } public Service Service { get; set; } }
迁移代码(add-migration AddedFacility生成)
protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Service", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), Name = table.Column<string>(type: "nvarchar(max)", nullable: false), Price = table.Column<double>(type: "float", nullable: false), PricingType = table.Column<int>(type: "int", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Service", x => x.Id); }); migrationBuilder.CreateTable( name: "Facility", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), Name = table.Column<string>(type: "nvarchar(max)", nullable: true), Status = table.Column<int>(type: "int", nullable: true), Capacity = table.Column<int>(type: "int", nullable: true), OpenedTime = table.Column<double>(type: "float", nullable: true), ClosedTime = table.Column<double>(type: "float", nullable: true), ServiceID = table.Column<int>(type: "int", nullable: false) }, constraints: table => { table.PrimaryKey("PK_Facility", x => x.Id); table.ForeignKey( name: "FK_Facility_Service_ServiceID", column: x => x.ServiceID, principalTable: "Service", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_Facility_ServiceID", table: "Facility", column: "ServiceID"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "Facility"); migrationBuilder.DropTable( name: "Service"); }
控制台日志输出
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (43ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [s].[Id], [s].[Name], [s].[Price], [s].[PricingType]
FROM [Service] AS [s]
The thread 0x1720 has exited with code 0 (0x0).
Microsoft.EntityFrameworkCore.Infrastructure: Information: Entity Framework Core 6.0.9 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.9' with options: None
Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [s].[Id], [s].[Name], [s].[Price], [s].[PricingType]
FROM [Service] AS [s]
The thread 0x3680 has exited with code 0 (0x0).
The thread 0x33c has exited with code 0 (0x0).
The thread 0x4734 has exited with code 0 (0x0).
The thread 0x4b94 has exited with code 0 (0x0).
The thread 0x3220 has exited with code 0 (0x0).
视图代码(Views/Facilities/Create.cshtml)
@model HRMS.Models.Facility @{ ViewData["Title"] = "Create"; } <h1>Create</h1> <h4>Facility</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Create"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Name" class="control-label"></label> <input asp-for="Name" class="form-control" /> <span asp-validation-for="Name" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Status" class="control-label"></label> <select asp-for="Status" class="form-control" asp-items="Html.GetEnumSelectList<FacilityStatus>()"></select> <span asp-validation-for="Status" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Capacity" class="control-label"></label> <input asp-for="Capacity" class="form-control" /> <span asp-validation-for="Capacity" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="OpenedTime" class="control-label"></label> <input asp-for="OpenedTime" class="form-control" /> <span asp-validation-for="OpenedTime" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ClosedTime" class="control-label"></label> <input asp-for="ClosedTime" class="form-control" /> <span asp-validation-for="ClosedTime" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ServiceID" class="control-label"></label> <select asp-for="ServiceID" class ="form-control" asp-items="ViewBag.ServiceID"></select> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
问题分析与修复方案
核心原因
提交后退回页面说明ModelState.IsValid为false,但视图未正确显示验证错误,同时存在几个关键问题:
防伪造令牌不匹配
控制器方法标记了[ValidateAntiForgeryToken],但视图表单未生成对应的令牌字段,导致验证失败。需在表单内添加:@Html.AntiForgeryToken()验证摘要范围限制
当前视图使用ModelOnly模式的验证摘要,仅显示模型级错误,改为All模式可显示所有字段级错误:<div asp-validation-summary="All" class="text-danger"></div>Service下拉框显示不合理
控制器中ViewData["ServiceID"]的SelectList用Id作为显示文本,用户无法识别服务名称,改为Name更直观:ViewData["ServiceID"] = new SelectList(_context.Service, "Id", "Name", facility.ServiceID);调试定位具体错误
在控制器Create方法内添加调试代码,查看ModelState的具体错误项:// 在if (ModelState.IsValid)前添加 var validationErrors = ModelState.Values.SelectMany(v => v.Errors); // 断点查看validationErrors的内容,定位未通过验证的字段
修复步骤
- 在Create视图的
<form>标签内添加防伪造令牌 - 修改验证摘要为
All模式 - 调整Service下拉框的显示文本为服务名称
- 通过调试查看
ModelState错误,针对性修复字段验证问题
内容的提问来源于stack exchange,提问作者soe thein

