如何在ASP.NET Identity的Account控制器Login方法中添加额外参数?
这个问题我之前也碰到过,核心原因是未登录用户提交POST表单时,被[Authorize]拦截并重定向到Login页面,而重定向是GET请求,原POST表单里的newRate参数没法被自动带到跳转URL里,导致登录成功后调用RateRestaurant时缺少必要参数。下面给你几个实用的解决方案:
方案一:扩展Login方法接收参数,用TempData暂存
首先修改Login的GET方法,让它能接收newRate和restaurantId参数,然后把这些参数存到TempData里(TempData在跨请求时能临时保存数据):
// GET: /Account/Login [AllowAnonymous] public ActionResult Login(string returnUrl, int? newRate, int? restaurantId) { ViewBag.ReturnUrl = returnUrl; // 把评分参数暂存到TempData,登录成功后取用 TempData["NewRate"] = newRate; TempData["RestaurantId"] = restaurantId; return View(); }
接着在Login的POST方法里,登录成功后检查TempData里的参数,如果存在就直接跳转到带参数的RateRestaurant:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } // 执行登录逻辑 var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); switch (result) { case SignInStatus.Success: // 检查是否有暂存的评分参数 if (TempData.TryGetValue("NewRate", out var newRateObj) && TempData.TryGetValue("RestaurantId", out var restaurantIdObj) && int.TryParse(newRateObj.ToString(), out int newRate) && int.TryParse(restaurantIdObj.ToString(), out int restaurantId)) { // 跳转到评分方法,带上完整参数 return RedirectToAction("RateRestaurant", "Restaurants", new { newRate, restaurantId }); } // 没有参数的话,正常跳转到原returnUrl return RedirectToLocal(returnUrl); case SignInStatus.LockedOut: return View("Lockout"); case SignInStatus.RequiresVerification: return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); default: ModelState.AddModelError("", "Invalid login attempt."); return View(model); } }
方案二:自定义AuthorizeAttribute保存POST参数
因为原表单是POST提交,默认的[Authorize]只会把请求的路径作为returnUrl,不会携带POST参数。我们可以自定义一个AuthorizeAttribute,在拦截时把POST参数存到Session里:
public class CustomAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.HttpMethod == HttpMethod.Post.Method) { // 保存POST表单数据到Session var formData = filterContext.HttpContext.Request.Form; filterContext.HttpContext.Session["PendingPostData"] = formData; // 保存目标Action和Controller名称 filterContext.HttpContext.Session["TargetAction"] = filterContext.ActionDescriptor.ActionName; filterContext.HttpContext.Session["TargetController"] = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; } base.HandleUnauthorizedRequest(filterContext); } }
然后把RateRestaurant方法上的[Authorize]替换成[CustomAuthorize]:
[CustomAuthorize] public ActionResult RateRestaurant(int newRate, int restaurantId) { // 原方法逻辑不变 var stringUserId = User.Identity.GetUserId(); var userId = new Guid(stringUserId); var rate = new Rate() { Rating = newRate, UserId = userId, RestaurantId = restaurantId }; _context.Ratings.Add(rate); _context.SaveChanges(); return RedirectToAction("Details", "Restaurants", new { id = restaurantId }); }
最后在Login的POST方法里,登录成功后检查Session里的暂存数据,如果存在就重新提交到目标Action:
case SignInStatus.Success: // 检查是否有暂存的POST请求数据 if (Session["PendingPostData"] is NameValueCollection postData && Session["TargetAction"] is string targetAction && Session["TargetController"] is string targetController) { // 解析参数 if (int.TryParse(postData["newRate"], out int newRate) && int.TryParse(postData["restaurantId"], out int restaurantId)) { // 清除Session里的临时数据 Session.Remove("PendingPostData"); Session.Remove("TargetAction"); Session.Remove("TargetController"); // 跳转到评分方法 return RedirectToAction(targetAction, targetController, new { newRate, restaurantId }); } } return RedirectToLocal(returnUrl);
方案三:将表单改为GET提交(不推荐)
如果业务允许,你可以把表单提交方式改成GET,这样提交时的URL会自动带上newRate和restaurantId参数,重定向到Login时returnUrl会包含这些参数,登录成功后直接跳转就能正常调用RateRestaurant:
using (Html.BeginForm("RateRestaurant", "Restaurants", new { restaurantId = Model.Restaurant.Id }, FormMethod.Get)) { <div> @Html.LabelFor(m => m.NewRate) @Html.DropDownListFor(m => m.NewRate, new SelectList(Model.RatesForDropDownList)) <button type="submit">Rate</button> </div> }
不过这种方式不推荐,因为评分属于用户操作,用POST更符合REST规范,也能避免参数在URL里暴露的问题。
内容的提问来源于stack exchange,提问作者bobeq

