添加复杂数据模型列表以形成

时间:2017-12-06 07:05:00

标签: javascript html asp.net asp.net-mvc

我正在使用带有ASP.NET MVC 5的Entity Framework 7。

我有一些看似this的表单。点击其中一个“新”按钮会显示Bootstrap modal,看起来像this。提交模态表单会将新实体添加到数据库,然后将其名称和主键附加到选择列表。

这是有效的,但是如果用户改变主意,通过模态创建的项目(在这种情况下的位置)会永远保持不变。理想情况下,在主窗体完成之前,不会创建任何子项。虽然这个例子只有两个简单的字段,但其他数据模型有六个以上,可能包括它们自己的复杂字段(但这样做不会是一个可怕的限制)。

那么,最好的方法是什么,嵌套的div由JavaScript序列化?嵌套的div也可以让用户轻松允许reordering,这是最终目标。

ASP.NET有更好的方法来处理这个吗?

1 个答案:

答案 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();
    }
}