使用 __RequestVerificationToken 对嵌套 ViewModel 进行远程验证

Remote Validation On Nested ViewModels With __RequestVerificationToken

我有一个名为 "LoginIndexViewModel" 的视图模型 class 用于登录剃须刀页面,包含登录、登录和忘记密码表单。它包含几个属性,每个属性都是单独的视图模型。这是 "LoginIndexViewModel" 视图模型:

public class LoginIndexViewModel
{
    public LoginViewModel Login { get; set; }

    public SignUpViewModel SignUp { get; set; }

    public ForgetPasswordViewModel ForgetPassword { get; set; }
}

在 "SignUpViewModel" 上有一个具有远程验证的 属性,我想在操作方法调用之前检查防伪令牌。这是 "SignUpViewModel":

的正文
public class SignUpViewModel
{
    .
    .
    .

    [Display(Name = "Email *")]
    [DataType(DataType.EmailAddress)]
    [Required(ErrorMessage = "The Email Is Required.")]
    [EmailAddress(ErrorMessage = "Invalid Email Address.")]
    [RegularExpression("^[a-zA-Z0-9_\.-]+@([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}$", ErrorMessage = "Invalid Email Address.")]
    [Remote("CheckUsername", "Account", ErrorMessage = "The Email Address Have Already Registered.", HttpMethod = "POST", **AdditionalFields = "__RequestVerificationToken")]
    public string Username { get; set; }

    .
    .
    .
}

我在操作方法 "CheckUsername" 和 @Html.AntiForgeryToken() 上面使用了 [ValidateAntiForgeryToken] 属性,如下所示:

[HttpPost]
[AjaxOnly]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public virtual async Task<JsonResult> CheckUsername([Bind(Prefix = "SignUp.UserName")]string userName)
{
    return Json(await _userManager.CheckUsername(username), JsonRequestBehavior.AllowGet);
}

剃须刀视图:

 @using (Html.BeginForm(MVC.Account.ActionNames.Register, MVC.Account.Name, FormMethod.Post, new { id = "RegisterForm", @class = "m-login__form m-form", role = "form" }))
 {
   @Html.AntiForgeryToken()
   .
   .
   .
   <div class="form-group">
       @Html.TextBoxFor(m => m.SignUp.UserName, new { @class = "form-control", placeholder = "Email *", autocomplete = "off", @Value = "" })
       <span class="helper">@Html.ValidationMessageFor(model => model.SignUp.UserName)</span>
   </div>
   .
   .
   .
 }

问题是远程验证调用抛出异常:'The required anti-forgery form field "__RequestVerificationToken" is not present.'

根据 Mozilla 调试器工具,我发现 CheckUsername 使用 'SignUp.__RequestVerificationToken' 参数而不是 '__RequestVerificationToken' 调用,因此会引发异常。

有谁知道发生了什么以及为什么 __RequestVerificationToken 没有提供?

AdditionalFields 中定义的属性添加前缀是设计使然,由 jquery.validate.unobtrusive.js 添加。相关代码是在adapters.add("remote", ["url", "type", "additionalfields"], function (options) {方法中添加前缀

有多种选择可以解决您的问题

根据现有源代码创建您自己的自定义(例如)[ValidateAntiForgeryTokenWithPrefix] 属性,但修改以从请求中删除任何前缀(不推荐)

进行您自己的 ajax 调用,而不是使用 [Remote] 属性 - 即处理文本框的 .blur() 事件以调用服务器方法,同时传递值和令牌,并在成功回调中更新 @Html.ValidationMessageFor() 生成的占位符,并处理 .keyup() 事件以清除任何消息。这确实具有性能优势,因为在初始验证之后,remote 规则会对每个 keyup() 事件进行 ajax 调用,因此它会导致大量服务器和数据库调用。

然而,最简单的解决方案是基于 LoginViewModelSignUpViewModelForgetPasswordViewModel 创建 3 个部分,然后使用 @Html.Partial() 从主视图调用,例如

_SignUpViewModel.cshtml

@model SignUpViewModel
....
@using (Html.BeginForm(MVC.Account.ActionNames.Register, MVC.Account.Name, FormMethod.Post, new { id = "RegisterForm", @class = "m-login__form m-form", role = "form" }))
{
    @Html.AntiForgeryToken()
    ....
    <div class="form-group">
        @Html.TextBoxFor(m => m.UserName, new { @class = "form-control", placeholder = "Email *", autocomplete = "off" })
        <span class="helper">
            @Html.ValidationMessageFor(model => model.UserName)
        </span>
    </div>
    ....
}

并在主视图中

@model LoginIndexViewModel
....
@Html.Partial("_SignUpViewModel", Model.SignUp)
.... // ditto for Login and ForgetPassword properties

然后您可以从 CheckUsername() 方法中省略 [Bind(Prefix = "SignUp.UserName")]

或者,您可以根据 LoginViewModel 创建主视图,然后使用 @Html.Action() 调用 [ChildActionOnly] 方法,return 其他 2 种形式的部分视图。

话虽如此,用户只会使用一次 SignUp 表单,并且可能永远不会使用 ForgetPassword 表单,因此包含所有额外的 html 只会降低性能,并且最好有链接将它们重定向到 SignUpForgetPassword 的单独页面,或者如果您希望它们在同一页面中,然后使用 [=64= 按需为它们加载部分内容].

作为旁注,您不需要 return Json(...) 中的 JsonRequestBehavior.AllowGet 参数(它是 [HttpPost] 方法),并且您永远不应该设置 value使用 HtmlHelper 方法时的属性。