Удаленная проверка на вложенных моделях представления с __RequestVerificationToken

У меня есть класс viewmodel, который называется "LoginIndexViewModel" для использования на странице бритвы входа, содержащий формы входа в систему, входа в систему и забыть пароль. Он содержит несколько свойств, каждое из которых является моделью представления отдельно. Вот модель представления "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; }

    .
    .
    .
}

Я использовал атрибут [ValidateAntiForgeryToken] над методом действия "CheckUsername" и @Html.AntiForgeryToken() в представлении бритвы, например, следующим:

[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>
   .
   .
   .
 }

Проблема заключается в том, что вызов удаленной проверки вызывает исключение: "Обязательное поле формы защиты от подделки"__RequestVerificationToken"отсутствует".

Согласно инструменту отладчика Mozilla, я обнаружил, что CheckUsername вызывает параметр "SignUp.__RequestVerificationToken" вместо "__RequestVerificationToken", поэтому возникает исключение.

Кто-нибудь знает, что случилось, и почему __RequestVerificationToken не предоставляет?

1 ответ

Решение

Добавление префикса к свойствам, определенным в AdditionalFields это дизайн, и добавляется jquery.validate.unobtrusive.js, Соответствующий код добавляет префикс в adapters.add("remote", ["url", "type", "additionalfields"], function (options) { метод.

Есть множество вариантов решения вашей проблемы

Создайте свой собственный заказ (скажем) [ValidateAntiForgeryTokenWithPrefix] атрибут, основанный на существующем исходном коде, но изменяющий для удаления любого префикса из запроса (не рекомендуется)

Сделайте свой собственный вызов Ajax, а не с помощью [Remote] атрибут - т.е. обрабатывать .blur() Событие текстового поля для вызова метода сервера, передавая значение и токен, и обновляя заполнитель, сгенерированный @Html.ValidationMessageFor() в обратном вызове успеха, и обрабатывать .keyup() событие, чтобы очистить любое сообщение. Это имеет преимущество в производительности, потому что после первоначальной проверки remote Правило делает вызов Ajax на каждом keyup() событие, так что это может привести к большому количеству обращений к серверу и базе данных.

Однако самым простым решением было бы создать 3 партиала на основе LoginViewModel, SignUpViewModel а также ForgetPasswordViewModel и затем позвоните с главного экрана, используя @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

Затем вы можете опустить [Bind(Prefix = "SignUp.UserName")] от CheckUsername() метод.

В качестве альтернативы вы делаете основной вид на основе скажем LoginViewModel, а затем использовать @Html.Action() звонить [ChildActionOnly] методы, которые возвращают частичные представления двух других форм.

Сказав это, пользователь будет только когда-либо использовать SignUp сформировать один раз, и никогда не может использовать ForgetPassword форма, так что включение всего этого лишнего html просто снижает производительность, и было бы лучше иметь ссылки для перенаправления их на отдельные страницы для SignUp а также ForgetPasswordили, если вы хотите, чтобы они были на той же странице, то загрузите партиалы для них по требованию, используя ajax.

Как примечание стороны, вам не нужно JsonRequestBehavior.AllowGet аргумент в вашем return Json(...) (это [HttpPost] метод), и вы никогда не должны устанавливать value атрибут при использовании HtmlHelper методы.

Другие вопросы по тегам