ASP.NET Core中GET方法调用传递ViewModel的Action时遭遇模型类型不匹配及执行流程异常问题
嘿,我来帮你理清楚这个问题的来龙去脉,你现在遇到的其实是两个核心问题:一个是对ASP.NET Core中Action执行流程的误解,另一个是视图模型类型不匹配的问题,咱们一个个拆解说。
首先纠正你一个关键误解:你觉得调用return await this.Info2(vm);是“重定向”到第二个Action,但这完全不是重定向——这只是在当前的HTTP请求上下文里,直接调用了Info2这个方法,相当于把Info2的逻辑当成了Info方法的一部分,并没有让浏览器发起新的请求。而且你说“执行完Info2后回到Info方法的最后一行”,这其实是你的错觉:当你执行return await Info2(vm);时,这个return语句已经让Info方法直接返回了Info2的执行结果,不会再执行后面的代码,你看到的错误是视图渲染阶段出的问题,和流程回到那里没关系。
接下来看模型类型不匹配的错误:错误提示说ViewDataDictionary需要SomeViewModel,但传入的是MyModelProxy。这说明最终渲染的视图期望接收的是SomeViewModel类型,但你传入的是MyModel实例。为什么会这样?
看你的代码,Info2方法返回View(mdl),其中mdl是MyModel类型。如果Info2对应的视图(默认是Info2.cshtml)的强类型声明是@model MyModel,那应该没问题,但错误说明当前渲染的视图其实是期望SomeViewModel的——这大概率是因为你直接在Info方法里返回Info2的结果时,ASP.NET Core的视图查找逻辑没有正确定位到Info2对应的视图,反而用了Info方法的视图(它的强类型是SomeViewModel),导致传入的MyModel和视图期望的类型不匹配,就报错了。
那怎么解决这个问题?根据你的需求(要么返回当前视图,要么跳转到另一个Action并传递ViewModel),我给你两个靠谱的方案:
方案一:如果Info2是GET方法(不需要POST)
这种情况我们可以用RedirectToAction做真正的重定向,配合TempData传递复杂的ViewModel(因为重定向是GET请求,没法直接传复杂对象):
首先修改Info方法,把ViewModel存入TempData再重定向:
public async Task<IActionResult> Info(int someId, string something) { SomeViewmodel vm = new SomeViewmodel() { Id = someId, Name = something }; if (somecondition) { // 把ViewModel序列化成JSON存入TempData,TempData默认存Session里 TempData["SomeViewModel"] = JsonSerializer.Serialize(vm); // 真正重定向到Info2 return RedirectToAction(nameof(Info2)); } else { return View(vm); } }
然后修改Info2方法,从TempData取出ViewModel再处理:
public async Task<IActionResult> Info2() { if (TempData.TryGetValue("SomeViewModel", out var vmJson)) { var vm = JsonSerializer.Deserialize<SomeViewmodel>(vmJson.ToString()); // 这里处理你的业务逻辑,生成MyModel MyModel mdl = new MyModel() { // 这里根据vm的属性赋值 Id = vm.Id, Name = vm.Name }; // 确认Info2对应的视图(Info2.cshtml)的强类型是@model MyModel return View(mdl); } // 如果TempData里没有数据,跳回Info方法 return RedirectToAction(nameof(Info)); }
方案二:如果Info2必须是POST方法
因为浏览器没法直接从GET请求重定向到POST请求,所以我们可以在Info的视图里加个自动提交的表单,当条件满足时自动提交到Info2的POST接口:
首先修改Info方法,把判断条件传给视图:
public async Task<IActionResult> Info(int someId, string something) { SomeViewmodel vm = new SomeViewmodel() { Id = someId, Name = something, NeedGoToInfo2 = somecondition // 给ViewModel加个布尔属性,标记是否需要跳转到Info2 }; return View(vm); }
然后修改Info对应的视图(Info.cshtml):
@model SomeViewmodel @if (Model.NeedGoToInfo2) { <!-- 生成隐藏表单,把ViewModel的所有属性用隐藏字段传递 --> <form id="autoSubmitForm" asp-action="Info2" method="post"> <input type="hidden" asp-for="Id" /> <input type="hidden" asp-for="Name" /> <!-- 把ViewModel的其他属性都用类似的隐藏字段传递 --> </form> <script> // 页面加载完成后自动提交表单 document.addEventListener('DOMContentLoaded', function() { document.getElementById('autoSubmitForm').submit(); }); </script> } else { <!-- 这里放你正常的视图内容 --> <h1>正常显示的内容</h1> }
最后给Info2加上[HttpPost]特性,确保它只接收POST请求:
[HttpPost] public async Task<IActionResult> Info2(SomeViewmodel vm) { MyModel mdl = new MyModel() { // 处理业务逻辑赋值 }; // 确认Info2的视图是强类型@model MyModel return View(mdl); }
最后再给你提个醒:以后别在一个Action里直接调用另一个Action然后return它的结果了,这种写法很容易踩坑,因为ASP.NET Core的视图上下文、路由信息都是和当前请求绑定的,直接调用方法会打破这些上下文的一致性,就像你这次遇到的模型类型不匹配问题就是这么来的。
内容来源于stack exchange

