复合ViewModel中子模型单独传递至HTTPPost方法时值为空的问题
我有一个复合ViewModel——ProductViewModel,它包含用于展示信息的ProductDisplayModel和用于接收用户输入、供验证的CreateReviewModel。当尝试仅将CreateReviewModel作为参数传递给HTTP Post方法时,其值为空;但传递整个ProductViewModel时,能正确获取值。希望仅用ModelState.IsValid验证CreateReviewModel,无需传递整个复合ViewModel,请问是否有方法指定HTTP Post方法应接收的模型?
相关代码
复合ViewModel
public class ProductViewModel { public ProductViewModel() { } public ProductViewModel(ProductDisplayModel productDisplayModel) { ProductDisplayModel = productDisplayModel; CreateReviewModel = new CreateReviewModel(); } public ProductDisplayModel ProductDisplayModel { get; set; } public CreateReviewModel CreateReviewModel { get; set; } }
视图代码
@model ProductViewModel <form class="add-review-container" method="post" asp-action="CreatedReview" asp-controller="Review"> <input name="productID" value="@Model.ProductDisplayModel.Product.ProductID" hidden /> <div class="information-container"> <div class="profile-container"> <img class="profile-picture"></img> <div class="username-container"> <p class="username">@User.Identity.Name</p> </div> </div> <div class="rating-container"> <div class="rating"> @for (int i = 5; i >= 1; i--) { <input value="@i" id="star_@i" type="radio" asp-for="CreateReviewModel.Rating"> <label title="@i" for="star_@i"></label> } </div> </div> </div> <textarea class="review-input-box" placeholder="Write a review about the product..." asp-for="CreateReviewModel.Comment"></textarea> <div class="post-review-button-container"> <button class="post-review-button">Post</button> </div> </form>
参数为复合ViewModel时的有效方法
[HttpPost] public async Task<IActionResult> CreatedReview(int productID, ProductViewModel viewModel) // viewModel.CreateReviewModel能正确获取值 { if (ModelState.IsValid) { // 业务逻辑 } }
参数为子模型时的无效方法
[HttpPost] public async Task<IActionResult> CreatedReview(int productID, CreateReviewModel viewModel)// viewModel值为空 { if (ModelState.IsValid) { // 业务逻辑 } }
问题根源在于:视图中asp-for="CreateReviewModel.Rating"生成的表单字段name为CreateReviewModel.Rating,而直接用CreateReviewModel作为Action参数时,模型绑定器默认寻找name为Rating、Comment的字段,无法匹配带前缀的字段,导致值为空。以下是几种可行的解决方法:
方法一:使用[Bind(Prefix)]特性指定前缀
在Action的CreateReviewModel参数上添加[Bind(Prefix = "CreateReviewModel")]特性,告诉模型绑定器将带CreateReviewModel.前缀的字段绑定到该参数:
[HttpPost] public async Task<IActionResult> CreatedReview(int productID, [Bind(Prefix = "CreateReviewModel")] CreateReviewModel reviewModel) { if (ModelState.IsValid) { // 你的业务逻辑代码 } // 后续处理(如返回视图等) return View(); }
这种方法无需修改视图,仅需调整Action参数,就能正确完成模型绑定,同时保留ModelState.IsValid对CreateReviewModel的验证能力。
方法二:手动调用TryUpdateModelAsync指定前缀
在Action内部手动创建CreateReviewModel实例,通过TryUpdateModelAsync指定绑定前缀:
[HttpPost] public async Task<IActionResult> CreatedReview(int productID) { var reviewModel = new CreateReviewModel(); // 指定绑定前缀为"CreateReviewModel" if (await TryUpdateModelAsync(reviewModel, "CreateReviewModel")) { if (ModelState.IsValid) { // 业务逻辑代码 } } // 后续处理 return View(); }
这种方式同样不需要修改视图,适合需要更灵活控制模型绑定流程的场景。
方法三:修改视图字段name(不推荐)
将视图中asp-for的前缀去掉,直接设置字段name为Rating、Comment:
<!-- 替换原asp-for代码 --> <input value="@i" id="star_@i" type="radio" name="Rating"> <!-- ... --> <textarea class="review-input-box" placeholder="Write a review about the product..." name="Comment"></textarea>
但这种方式会丢失asp-for自动生成验证属性、关联模型验证规则的优势,容易引发后续维护问题,因此不推荐使用。
内容的提问来源于stack exchange,提问作者Ivailo

