ASP.NET Core中DevExtreme DataGrid与表单一同提交的问题
问题描述
在ASP.NET Core Razor页面项目中使用DevExtreme DataGrid控件实现添加带手机号的联系人功能:表单头部收集姓名、出生日期等基础信息,DataGrid负责手机号的增删改操作。配置DataGrid的远程数据源URL后,提交表单时出现ViewModel绑定异常,无法获取表单头部的基础数据,同时也无法将DataGrid中的手机号数据与表单一同提交。
解决方案
核心问题分析
当前使用RemoteController作为DataGrid数据源时,DataGrid的增删改操作通过独立AJAX请求触发服务器端Action,这些Action仅修改服务器端内存中的ViewModel数据,但客户端表单并未同步这些变更;同时PartyViewModel中的PhoneNumbers是只读属性(仅get),导致模型绑定无法填充数据,最终提交时ViewModel的基础数据和手机号数据都无法正确获取。
具体修改步骤
- 调整ViewModel的可绑定性
修改PartyViewModel中的PhoneNumbers属性,添加setter以支持模型绑定:
public class PartyViewModel { public Guid Id { get; set; } = Guid.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; [DataType(DataType.Date)] public DateTime? Birthdate { get; set; } public PartyType Type { get; set; } = PartyType.Unknown; // 将只读属性改为可读写,支持模型绑定 public List<PartyPhoneViewModel> PhoneNumbers { get; set; } = new(); }
- 切换DataGrid为客户端数据源模式
移除RemoteController配置,改用客户端数组数据源直接绑定到ViewModel的PhoneNumbers,开启双向绑定确保客户端修改同步到表单字段:
<div class="col-12 mb-3"> @( Html.DevExtreme().DataGrid<PartyPhoneViewModel>() .ShowBorders(true) .ID("grdPartyPhones") .ColumnAutoWidth(true) .RepaintChangesOnly(true) .AllowColumnResizing(true) .TwoWayBindingEnabled(true) .ColumnResizingMode(ColumnResizingMode.NextColumn) // 使用客户端数组数据源,直接绑定ViewModel的PhoneNumbers .DataSource(m => m.Array().Data(Model.ViewModel.PhoneNumbers)) .Columns(column => { column.AddFor(x => x.PhoneNumber); column.AddFor(x => x.Type).Lookup(lookup => lookup .DataSource(Html.GetEnumSelectList<PartyPhoneTypeEnum>() .Select(i => new { Value = int.Parse(i.Value), Text = i.Text })) .ValueExpr("Value") .DisplayExpr("Text")); }) .Editing(edit => edit .RefreshMode(GridEditRefreshMode.Full) .AllowAdding(true) .AllowDeleting(true) .AllowUpdating(true) .UseIcons(true) ) ) </div>
移除不必要的服务器端Grid操作
删除PageModel中所有与Grid远程操作相关的代码(Grid Actions和Grid Row Methods区域),客户端模式下不需要这些独立AJAX接口,DataGrid的修改会直接同步到表单隐藏字段,提交时一并提交。简化PageModel逻辑
修改后的PageModel仅保留核心页面生命周期和提交逻辑:
public class PartyPageModel : NGBasePageModel { [BindProperty] public PartyViewModel ViewModel { get; set; } = new(); public PartyPageModel(IServiceProvider serviceProvider) : base(serviceProvider) { } public override async Task OnGetAsync() { await base.OnGetAsync(); if (Id == Guid.Empty) ViewModel = new(); else { var party = await ServiceProvider .GetRequiredService<IPartyBusiness>() .FetchPartyByIdAsync(Id, true); if (party != null) { ViewModel = party.Adapt<PartyViewModel>(); ViewModel.PhoneNumbers = party.PhoneNumbers .Select(item => item.Adapt<PartyPhoneViewModel>()) .ToList(); } } } public async Task<IActionResult> OnPostParty([FromServices] IPartyBusiness partyBusiness) { if (!ModelState.IsValid) return Page(); var result = await partyBusiness.AddOrUpdateParty(ViewModel); if (!result) return Page(); return RedirectToPage("/"); } }
内容的提问来源于stack exchange,提问作者Ali NGame
相关产品推荐
相关产品推荐

