Реализация Jquery Ajax в многошаговых формах регистров, основанных на разделенных моделях представления в ASP.NET MVC5
Я использовал подход Дарина-Димитрова для создания многошаговой регистрационной формы, которая объясняется здесь, и она отлично работает. Теперь я хочу обработать события отправки для кнопок "Предыдущий", "Следующий" и "Готово", используя jquery ajax вместо Html.Beginform().
Заметки:
- Я использую MVC 5 с.NET 4.5.2
- У меня есть свойства fileupload и datetime в моей второй ступени viewmodel.
Вот моя модель
[Serializable]
public class RegisterWizardViewModel
{
public int CurrentStepIndex { get; set; }
public IList<IStepViewModel> Steps { get; set; }
public void Initialize()
{
Steps = typeof(IStepViewModel)
.Assembly
.GetTypes()
.Where(t => !t.IsAbstract && typeof(IStepViewModel).IsAssignableFrom(t))
.Select(t => (IStepViewModel)Activator.CreateInstance(t))
.ToList();
}
public interface IStepViewModel
{
}
[Serializable]
public class RegisterStep1ViewModel : IStepViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
[Serializable]
public class RegisterStep2ViewModel : IStepViewModel
{
[Display(Name = "FirstName", ResourceType = typeof(Resources.Resources))]
public string FirstName { get; set; }
[Display(Name = "LastName", ResourceType = typeof(Resources.Resources))]
public string LastName { get; set; }
[NonSerialized]
private HttpPostedFileBase _file;
public HttpPostedFileBase File
{
get
{
return _file;
}
set
{
_file = value;
}
}
[Display(Name = "BirthDay", ResourceType = typeof(Resources.Resources))]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}", ApplyFormatInEditMode = true)]
public DateTime? BirthDay { get; set; }
[Display(Name = "NationalCode", ResourceType = typeof(Resources.Resources))]
public int NationalCode { get; set; }
[Display(Name = "Gender", ResourceType = typeof(Resources.Resources))]
public bool IsMale { get; set; }
[Display(Name = "Mobile", ResourceType = typeof(Resources.Resources))]
public string MobilePhone { get; set; }
[Display(Name = "Country", ResourceType = typeof(Resources.Resources))]
public string Country { get; set; }
[Display(Name = "Address", ResourceType = typeof(Resources.Resources))]
public string Address { get; set; }
[MustBeTrue]
public bool CaptchaValid { get; set; }
}
}
Вот мой контроллер
[AllowAnonymous]
public ActionResult Index()
{
var wizard = new RegisterWizardViewModel();
wizard.Initialize();
return View(wizard);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Index([Deserialize] RegisterWizardViewModel wizard, RegisterWizardViewModel.IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(Request["next"]))
{
wizard.CurrentStepIndex++;
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
else
{
var model1 = wizard.Steps[0] as RegisterWizardViewModel.RegisterStep1ViewModel;
var model2 = wizard.Steps[1] as RegisterWizardViewModel.RegisterStep2ViewModel;
var uploadedFile = (model2.File != null && model2.File.ContentLength > 0) ? new byte[model2.File.InputStream.Length] : null;
if (uploadedFile != null)
{
model2.File.InputStream.Read(uploadedFile, 0, uploadedFile.Length);
}
var user = new ApplicationUser { UserName = model1.Email, Email = model1.Email, FirstName = model2.FirstName, LastName = model2.LastName, Image = uploadedFile , BirthDay = model2.BirthDay, IsMale = model2.IsMale, NationalCode = model2.NationalCode, MobilePhone = model2.MobilePhone, Country = model2.Country, Address = model2.Address };
var result = UserManager.Create(user, model1.Password);
if (result.Succeeded)
{
SignInManager.SignIn(user, isPersistent: false, rememberBrowser: false);
return Json(new { response = "Redirect", url = Url.Action("Index", "Home") });
}
else
{
AddErrors(result);
}
}
}
else if (!string.IsNullOrEmpty(Request["prev"]))
{
wizard.CurrentStepIndex--;
}
return View(wizard);
}
И я использовал этот код jquery для отправки через вызов ajax в моем представлении indexwizard регистра.
@section scripts{
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script type="text/javascript">
$(function () {
$('form').on("submit", function (e) {
e.preventDefault;
if ($(this).valid()) {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
window.location = result.url;
}
});
}
return false;
});
});
</script>
}
Теперь проблема в том, что контроллер не распознает, какая кнопка была нажата, и Request["next"]
или же Request["prev"]
всегда возвращать ноль, и в случае, если состояние модели действительно для первого шага (электронная почта, пароль, подтверждение), контроллер напрямую переходит к созданию пользователя. Стоит отметить, что, поскольку я не смог перейти ко второму шагу с помощью ajax-вызова, я не знаю, отправлял ли контроллер загрузку файла и свойство datetime без каких-либо проблем или нет.
Обновить:
Вот индексный вид
@using (Html.BeginForm("Index", "RegisterWizard", FormMethod.Post, new { @class = "form-horizontal", role = "form", enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken()
@Html.Serialize("wizard", Model)
@Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())
@Html.EditorFor(x => currentStep, null, "")
if (Model.CurrentStepIndex > 0)
{
<div class="col-xs-9 col-sm-6 col-md-5 col-lg-5" style="padding-right:0px;">
<input type="submit" class="btn btn-default" value="Previous" name="prev" />
</div>
}
if (Model.CurrentStepIndex < Model.Steps.Count - 1)
{
<div class="col-xs-10 col-sm-8 col-md-6" style="">
<input type="submit" class="btn btn-default" value="Next" name="next" style="float:left;"/>
</div>
}
else
{
<div class="col-xs-3 col-sm-6 col-md-7 col-lg-7" style="padding-right:0px;">
<input type="submit" class="btn btn-default " value="Finish" name="finish" />
</div>
}
}
1 ответ
Ну, в случае, если кто-то сталкивается с этой проблемой и, вероятно, имеет аналогичную проблему, я объясняю мой обходной путь.
Основная проблема с распознаванием того, какой кнопке была отправлена форма:
Я добавил один скрытый вход с именем Button и динамически изменил его имя с помощью jquery on click для каждой кнопки и, наконец, изменил свой контроллер, чтобы получить это значение из запроса.
Следующее является частичным представлением, которое содержит всю форму и действие моего контроллера.
@using Microsoft.Web.Mvc
@model Models.RegisterWizardViewModel
@{
var currentStep = Model.Steps[Model.CurrentStepIndex];
}
@using (Html.BeginForm("Index", "RegisterWizard", FormMethod.Post, new { @class = "form-horizontal", role = "form", enctype = "multipart/form-data", @id = "mainRWF" }))
{
@Html.AntiForgeryToken()
@Html.Hidden("Button")
@Html.Serialize("wizard", Model)
@Html.Hidden("StepType", Model.Steps[Model.CurrentStepIndex].GetType())
@Html.EditorFor(x => currentStep, null, "")
if (Model.CurrentStepIndex > 0)
{
<div >
<input type="submit" value="Previous" name="prev" />
</div>
}
if (Model.CurrentStepIndex < Model.Steps.Count - 1)
{
<div >
<input type="submit" value="Next" name="next" />
</div>
}
else
{
<div >
<input type="submit" value="Submit" name="finish"/>
</div>
}
}
...
[ValidateAntiForgeryToken]
public async Task<ActionResult> Index([Deserialize]RegisterWizardViewModel wizard, RegisterWizardViewModel.IStepViewModel step)
{
wizard.Steps[wizard.CurrentStepIndex] = step;
if (ModelState.IsValid)
{
if (Request.Form.GetValues("Button").Contains("next"))
{
wizard.CurrentStepIndex++;
}
else if (Request.Form.GetValues("Button").Contains("prev"))
{
wizard.CurrentStepIndex--;
}
else
{
//Do stuff with received values
return Json(new { response = "Success" });
}
}
else if (Request.Form.GetValues("Button").Contains("prev"))
{
// Even if validation failed we allow the user to
// navigate to previous steps
wizard.CurrentStepIndex--;
}
else if (!ModelState.IsValid)
{
// If we got this far, something failed, redisplay form with errors by inserting this into html() of success func of ajax
return PartialView("_IndexPartial", wizard);
}
// return next step
return PartialView("_IndexPartial", wizard);
}
и, очевидно, у меня проблемы с загрузкой файлов! и выбрал html5 formData для обработки загрузки в качестве опции данных при вызове ajax для кнопки завершения.
Я хотел бы оценить комментарии по этому решению, чтобы сделать его лучше.