我正在使用带有ASP.NET MVC 5的Entity Framework 7。
我有一些看似this的表单。点击其中一个“新”按钮会显示Bootstrap modal,看起来像this。提交模态表单会将新实体添加到数据库,然后将其名称和主键附加到选择列表。
这是有效的,但是如果用户改变主意,通过模态创建的项目(在这种情况下的位置)会永远保持不变。理想情况下,在主窗体完成之前,不会创建任何子项。虽然这个例子只有两个简单的字段,但其他数据模型有六个以上,可能包括它们自己的复杂字段(但这样做不会是一个可怕的限制)。
那么,最好的方法是什么,嵌套的div由JavaScript序列化?嵌套的div也可以让用户轻松允许reordering,这是最终目标。
ASP.NET有更好的方法来处理这个吗?
答案 0 :(得分:0)
这感觉很骇人,但它确实有效。
使用BeginCollectionItem,您可以让模态向DOM添加隐藏的输入元素。
我写了一个动作方法,它分别返回带有modelstate(有效/无效)的JSON和错误或部分HTML,用于无效和有效的提交。基于此,JavaScript会将错误添加到摘要中,或者将必要的标签和隐藏的输入添加到初始表单中。
然后让主窗体的viewmodel包含一个ICollection
数据模型,在下面的代码中称为Contacts
,ASP.NET处理数据绑定没有任何问题。
示例:
_CollectionItem.cshtml(有效提交后将部分HTML添加到主表单中)
@model Project.Models.ContactCreateViewModel
<li>
<div class="collection-item">
@using (Html.BeginCollectionItem("Contacts"))
{
<span class="item-name">@Model.LastName, @Model.FirstName</span> <span class="btn btn-danger delete-item">Delete</span>
@Html.HiddenFor(model => model.FirstName)
@Html.HiddenFor(model => model.LastName)
@Html.HiddenFor(model => model.PhoneNumber)
@Html.HiddenFor(model => model.PhoneExt)
@Html.HiddenFor(model => model.Email)
}
</div>
</li>
_CreateModal.cshtml(部分用于模态的主体)
@model Project.Models.ContactCreateViewModel
<div class="modal-header">
<button type="button" class="close btn-modal-close" data-dismiss="modal"><i class="fas fa-times"></i></button>
<h4 class="modal-title">New Contact</h4>
</div>
<div class="modal-body">
@using (Html.BeginForm("CreateModal", "Contacts", FormMethod.Post, new { id = "new-contact-form" }))
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
@Html.ValidationSummaryPlaceholder()
@* First Name *@
<div class="form-group">
@Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-4" })
<div class="col-md-8">
@Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger" })
</div>
</div>
@* Last Name *@
<div class="form-group">
@Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-4" })
<div class="col-md-8">
@Html.EditorFor(model => model.LastName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.LastName, "", new { @class = "text-danger" })
</div>
</div>
@* Phone Number *@
<div class="form-group">
@Html.LabelFor(model => model.PhoneNumber, htmlAttributes: new { @class = "control-label col-md-4" })
<div class="col-md-8">
@Html.EditorFor(model => model.PhoneNumber, new { htmlAttributes = new { @class = "form-control phone" } })
@Html.ValidationMessageFor(model => model.PhoneNumber, "", new { @class = "text-danger" })
</div>
</div>
@* Phone Ext *@
<div class="form-group">
@Html.LabelFor(model => model.PhoneExt, htmlAttributes: new { @class = "control-label col-md-4" })
<div class="col-md-8">
@Html.EditorFor(model => model.PhoneExt, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.PhoneExt, "", new { @class = "text-danger" })
</div>
</div>
@* Email *@
<div class="form-group">
@Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-4" })
<div class="col-md-8">
@Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
</div>
</div>
@* SUBMIT *@
<div class="form-group">
<div class="col-md-offset-4 col-md-8">
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
}
</div>
@Scripts.Render("~/Scripts/Custom/ajax-add-collection-item.js")
<script>
$(function () {
ajaxAddCollectionItem("new-contact-form", "contacts", function () {
alertify.success("Added new contact");
})
});
</script>
ajax-add-collection-item.js(捕获模态表单提交,将_CollectionItem.cshtml
添加到主表单)
// Posts form and adds collection item to ul
function ajaxAddCollectionItem(formId, listId, onSuccess = function () { }) {
let $form = $("#" + formId);
$form.submit(function (event) {
event.preventDefault();
$.ajax({
method: "POST",
url: $form.attr("action"),
data: $form.serialize(),
success: function (data) {
let successful = data["success"];
// If form is valid, close modal and append new entry to list
if (successful) {
$("#" + listId).append(data["html"]);
$(".delete-item").click(function (event) {
$(this).closest("li").remove();
});
$(".btn-modal-close").trigger("click");
onSuccess();
}
// If form is not valid, display error messages
else {
displayValidationErrors(data["errors"]);
}
},
error: function (error) {
alert("Dynamic content load failed.");
console.error("Ajax call failed for form: " + $form);
}
});
});
// Populate validation summary
function displayValidationErrors(errors) {
let $ul = $('div.validation-summary-valid.text-danger > ul');
$ul.empty();
$.each(errors, function (i, errorMessage) {
$ul.append('<li>' + errorMessage + '</li>');
});
}
}
ContactsController.cs
public class ContactsController
{
// GET: Contacts/CreateModal
public ActionResult CreateModal()
{
return PartialView("_CreateModal");
}
// POST: Contacts/CreateModal
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CreateModal(ContactCreateViewModel viewModel)
{
// If form is valid and email does not already exist, send HTML for collection item,
// otherwise send modelstate errors
if (ModelState.IsValid)
{
User user = await UserManager.FindByEmailAsync(viewModel.Email);
if (user == null)
// RenderPartialView returns partial HTML as a string,
// see https://weblog.west-wind.com/posts/2012/May/30/Rendering-ASPNET-MVC-Views-to-String
return Json(new { success = true, html = RenderPartialView("_CollectionItem", viewModel) });
else
ModelState.AddModelError("Email", "Email already exists.");
}
return Json(new { success = false, errors = GetModelStateErrors() });
}
// Actually in base controller class
protected string[] GetModelStateErrors()
{
return ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage).ToArray();
}
}