Удаленная проверка на вложенных моделях представления с __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
методы.