ASP.NET MVC 5表单提交传递路由参数至控制器方法的问题
问题:ASP.NET MVC5表单提交时传递租户ID路由参数失败
背景
基于.NET 4.8构建ASP.NET MVC5多租户应用,配置SSO时需要在整个登录流程URL中保留当前租户ID,通过表单提交(需保留防伪令牌)向控制器POST方法传递该路由参数,但目前无法正常运行。
现有表单代码
@using (Html.BeginForm("ExternalLogin/" + Request.Url.Segments[Request.Url.Segments.Length - 1], "AccountSSO", new { ReturnUrl = "" })) { @Html.AntiForgeryToken() <div id="socialLoginList"> <p> <button type="submit" class="btn btn-default" name="provider" value="OpenIdConect" title="SSO Login">SSO Login</button> </p> </div> }
注:租户ID作为路由参数,通过Request.Url.Segments获取并拼接在Action名称后
控制器Action代码
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult ExternalLogin(int tenantId) { // 请求重定向到外部登录提供商 var res = new ChallengeResult("OpenIdConnect", Url.Action($"ExternalLoginCallback/{tenantId}", "AccountSSO")); return res; }
报错信息
The parameters dictionary contains a null entry for parameter 'tenantId' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult ExternalLogin(Int32)' in 'MyProject.Controllers.AccountSSOController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter. Parameter name: parameters
路由配置(Global.asax最后三条)
// default route var defaults = new RouteValueDictionary(); routes.Add("Default", new Route("api/{controller}/{action}", defaults, new RouteDefaultHandler())); // default route with param var defaultsWithParam = new RouteValueDictionary(); routes.Add("DefaultWithParam", new Route("api/{controller}/{action}/{param}", defaultsWithParam, new RouteDefaultHandler())); //catch-all route var notFoundDefaults = new RouteValueDictionary(); notFoundDefaults.Add("controller", "DefaultPage"); notFoundDefaults.Add("action", "Show"); routes.Add("DefaultPage", new Route("{*url}", notFoundDefaults, new MvcRouteHandler()));
解决方案
- 方法1:修正路由定义,匹配tenantId参数
当前带参数的路由使用{param},但控制器Action参数名为tenantId,名称不匹配导致无法绑定。修改路由配置,将参数名改为tenantId:
// 修改带参数的路由 var defaultsWithParam = new RouteValueDictionary(); // 把{param}改为{tenantId},确保与Action参数名一致 routes.Add("DefaultWithParam", new Route("api/{controller}/{action}/{tenantId}", defaultsWithParam, new RouteDefaultHandler()));
然后调整表单的BeginForm调用,使用路由值而非硬拼接URL:
@{ var tenantId = int.Parse(Request.Url.Segments[Request.Url.Segments.Length - 1]); } @using (Html.BeginForm("ExternalLogin", "AccountSSO", new { tenantId = tenantId, ReturnUrl = "" }, FormMethod.Post)) { @Html.AntiForgeryToken() <div id="socialLoginList"> <p> <button type="submit" class="btn btn-default" name="provider" value="OpenIdConnect" title="SSO Login">SSO Login</button> </p> </div> }
- 方法2:通过隐藏表单字段传递参数
如果不想修改路由,可以在表单中添加隐藏字段,将tenantId作为POST表单数据传递:
@{ var tenantId = Request.Url.Segments[Request.Url.Segments.Length - 1]; } @using (Html.BeginForm("ExternalLogin", "AccountSSO", new { ReturnUrl = "" })) { @Html.AntiForgeryToken() <!-- 添加隐藏字段传递tenantId --> <input type="hidden" name="tenantId" value="@tenantId" /> <div id="socialLoginList"> <p> <button type="submit" class="btn btn-default" name="provider" value="OpenIdConnect" title="SSO Login">SSO Login</button> </p> </div> }
这种方式不需要修改路由,MVC会自动将表单中的tenantId字段绑定到Action的同名参数上。
- 方法3:从Request URL中手动提取参数
如果上述方法都不适用,可以在Action中直接从请求URL的Segments中提取租户ID:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult ExternalLogin() { var tenantId = int.Parse(Request.Url.Segments[Request.Url.Segments.Length - 1]); var res = new ChallengeResult("OpenIdConnect", Url.Action($"ExternalLoginCallback/{tenantId}", "AccountSSO")); return res; }
这种方式不需要依赖路由绑定,直接手动获取参数,但要注意添加异常处理(比如解析失败的情况)。
内容的提问来源于stack exchange,提问作者dzenesiz
相关产品推荐
相关产品推荐

