Удаленная проверка на вложенных моделях представления с __RequestVerificationToken - PullRequest
0 голосов
/ 31 октября 2018

У меня есть класс 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 Ответ

0 голосов
/ 31 октября 2018

Добавление префикса к свойствам, определенным в 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] методов, которые возвращают частичные представления других 2 форм.

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

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

...