在 .NET MVC 应用程序中处理来自动态子表单的数据输入的最佳方法

Best approach in dealing with data inputs from dynamic sub forms in .NET MVC app

我对 .NET MVC 比较陌生,并且一直在努力使用我正在开发的 Web 应用程序。总而言之,这个应用程序由一个表单组成,主要部分是输入客户的姓名,然后是一个添加child 的按钮。单击此按钮时,将通过局部视图显示一个子表单。当点击子表单上的保存按钮时,我不确定如何处理数据(但子表单将折叠,HTML 侧的手风琴样式),但要点是客户可以添加一个或多个 child 子表单来输入他们的 child(ren) 的姓名和年龄。

处理这些表单上的输入的最佳方式是什么?我和一些同事谈过,他们似乎不确定。有人建议我首先将 'parent' 表单保存到数据库中,然后在每个 sub-form (child) 中,当单击保存按钮时,将它们也发送到数据库中,使用parent Id 来连接它们。其他人建议为此使用 Vue。在 model-binding 或将数据发送回控制器方面,您有什么建议?

首先,表单的视图模型如下所示:

namespace ModelBindingObjects.ViewModel
{
    public class ParentVM
    {
        public string Name { get; set; }

        public List<ChildVM> Children { get; set; }
    }

public class ChildVM
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }

}

对于数据库端,模型如下所示:

    public partial class Parent
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        public string Name { get; set; }
    }

 public partial class Child
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int Id { get; set; }

        public int ParentId { get; set; }

        [Required]
        [StringLength(50)]
        public string Name { get; set; }

        public int Age { get; set; }

视图看起来像这样

@model ModelBindingObjects.ViewModel.ParentVM
<h3>A Sample Form</h3>
@using (Html.BeginForm())
{
    <div class="container">
        <div class="row">
            <div class="form-row">
                <div class="form-group col-md-6">
                    @Html.LabelFor(x => x.Name, new { @class = "required" })
                    @Html.TextBoxFor(x => x.Name, new { @class = "form-control" })
                </div>
                <div class="col-md-6">

                </div>
            </div>
        </div>
        <div class="row">
            <div class="form-group col-md-6">
                <div class="addChildren">
                    <h5 class="topspacing-med btmspacing-med">Your Child(ren) Details</h5>
                    <div class="displayChildForm">
                        @*@Html.Partial("_AddChild", new ViewDataDictionary()
                            {
                                TemplateInfo = new TemplateInfo()
                                { HtmlFieldPrefix = "Test" }
                            })*@
                    </div>
                    <button type="button" class="btn btn-outline-secondary btn-sm btn-block btmspacing-med" id="addChild">Add New Child</button>
                </div>
            </div>
            <div class="form-group col-md-6"></div>
        </div>
    </div>
}


@section scripts {
    <script type="text/javascript">

       var hideButtons = function () {
            $("#addChild").hide();
        };

        $("#saveChild").click(function () {
            console.log("testing");
            $(".panel-body").hide();
        });

        $("#addChild").click(function () {
            $.ajax({
                url: '@Url.Action("RenderAddChildForm", "Home")',
                dataType: "html",
                type: 'GET',
                success: function (result) {
                    $('.displayChildForm').append(result);
                    hideButtons();
                },
                error: function (xhr, ajaxOptions, thrownError) {
                    console.log(xhr.status);
                    console.log(thrownError);
                },

            });
        });


    </script>

然后局部视图...

@model ModelBindingObjects.ViewModel.ChildVM

    <div class="panel panel-primary">
        <div class="panel-heading"><h5>Add A Child</h5></div>
        <div class="panel-body">
            <div class="form-group row">
                <div class="col-md-12">
                    <input type="hidden" name="products.Index" value="test" />
                    @*@Html.LabelFor(x => x.Name)
                    @Html.TextBoxFor(x => x.Name, new { @class = "form-control" })*@
                    <input type="text" name="[test].Name" value="Delaney" />
                </div>
            </div>
            <div class="form-group row">
                <div class="col-md-12">
                    <input type="text" name="[test].Age" value="12" />
                </div>
            </div>
            <div class="btn-group float-right mt-2" role="group">
                <button class="btn btn-primary btn-sm btn-block" type="submit" id="saveChild">Save</button>
            </div>
        </div>
    </div>

最后,老控制器:

namespace ModelBindingObjects.Controllers
{
    public class HomeController : Controller
    {
        private string PartialViewPath(string name)
        {
            return $"~/Views/Partials/{name}.cshtml";
        }

        // GET: Home
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult RenderAddChildForm()
        {
            return PartialView(PartialViewPath("_AddChild"));
        }
    }



}

这取决于页面流的复杂程度,您对 Vue 的熟悉程度,以及是否有单独的编辑页面或您希望在同一页面内支持 edit/delete。如果它所做的只是节省 children,那么您可以使用 AJAX 请求轻松做到这一点,而无需引入 Vue。

一种方法是将这两个操作分开,因此用户不能添加 child,除非他们已经添加并保存了 parent 的数据。在parent表单保存成功后,return保存了parentId,并禁用了按钮。对于每个子表单,当用户单击 "save child" 时,发送 {parentId, childVm}.

另一种方法是整个页面只有一个保存按钮,即每个 child 没有自己的保存按钮。 displayChildForm div 充当输入集合,带有单个 "add child" 按钮,可创建更多 children 部分视图并将它们附加到自身。当用户单击页面的保存按钮时,您发送 {parentVm, childVmCollection[]}。

您可以混合使用这两种方法,因此您最终会得到一个 "save parent" 按钮和 displayChildForm div 中的一个 "save/edit children" 按钮。单击保存 children 按钮时,您发送 {parentId,childVmCollection[]}。

您可以将模型绑定到模型上的 ICollection 属性

https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.2#collections

一个老歌但一个金曲:https://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/

您可能会发现我的一些库也对这类事情有帮助。

https://github.com/mcintyre321/FormFactory https://github.com/mcintyre321/AspNetCoreJTokenModelBinder