如何使用模型将列表从视图正确连接到控制器

How to properly connect a list from the view to the controller using a Model

我想要一个表单,允许用户填写一些字段,当控制器接收到这些字段时,这些字段应该被压缩成一个对象。我找不到解释这个的词,所以让我举个例子。

这个对象(AllocationInformation)基本上有六个字段,是模型的一部分:

AllocationInformation
{
    FundSource
    Function
    Location
    Program
    Subject
    Object
}

现在模型中有一个 List<AllocationInformation>。该视图将允许用户创建任意数量的分配对象。

所以表单中有六个字段,还有一个按钮,让用户可以根据需要多次添加相同的六个字段中的另一个。

视图还需要能够渲染这些对象(如果它们已经存在于模型中)。我可以完成所有这些工作,但它丑陋如罪,我希望找到更好的方法。

目前我的看法是这样的:

<button type="button" class="btn btn-primary" onclick="addAllocation()">Add Allocation</button>
<div class="row">
    <div id="allocationsDiv" class="container">
        <div class="row">
            <div class="col-sm-1" id="fundSourceCol">FundSource</div>
            <div class="col-sm-1" id="functionCol">Function</div>
            <div class="col-sm-1" id="locationCol">Location</div>
            <div class="col-sm-1" id="programCol">Program</div>
            <div class="col-sm-1" id="subjectCol">Subject</div>
            <div class="col-sm-1" id="objectCol">Object</div>
            <div class="col-sm-1" id="removeCol">Remove?</div>
        </div>



        @if (Model.Allocations != null)
        {
            for (int i = 0; i < Model.Allocations.Count; i++)
            {
                <div>
                    @Html.TextBoxFor(m => m.Allocations[i], new { @class = "form-control input-xsmall" })
                </div>
            }
        }
    </div>
</div>

这是 addAllocation() 的 javascript:

function addAllocation()
{
    var $row = $("<div class='row'></div>");

    $("#allocationsDiv").append($row);

    var fundSource = "<div class='col-sm-1'><input type='text' name='Allocation.FundSource' class='funding-source-item form-control' /></div>";

    $row.append(fundSource);

    var func = "<div class='col-sm-1'><input type='text' name='Allocation.Function' class='funding-source-item form-control col-sm-1' /></div>";

    $row.append(func);

    var location = "<div class='col-sm-1'><input type='text' name='Allocation.Location' class='funding-source-item form-control col-sm-1' /></div>";

    $row.append(location);

    var program = "<div class='col-sm-1'><input type='text' name='Allocation.Program' class='funding-source-item form-control col-sm-1' /></div>";

    $row.append(program);

    var subject = "<div class='col-sm-1'><input type='text' name='Allocation.Subject' class='funding-source-item form-control col-sm-1' /></div>";

    $row.append(subject);

    var object = "<div class='col-sm-1'><input type='text' name='Allocation.Object' class='funding-source-item form-control col-sm-1' /></div>";

    $row.append(object);

    var $remove = $("<div class='col-sm-1'><i class='glyphicon glyphicon-remove glyphicon-clickable col-sm-1 funding-source-item'></i></div>");

    $remove.on('click', function () {
        $row.remove()
    });

    $row.append($remove);



}

这就是问题所在。addAllocation() 函数需要知道已进行多少分配的当前索引,以便正确构建输入名称,因为它们应该采用 Allocation[i].FundSource,这意味着我必须有一个全局 javascript 变量或类似的东西来跟踪它,这似乎是一个糟糕的主意。

我只是认为必须有一种我不知道的更好的方法。

这是实际的 AllocationInformation class 以防有帮助:

public class AllocationInformation
{
    [Key]
    public int Id { get; set; }

    [ForeignKey("CheckRequestModel")]
    public int CheckRequestId { get; set; }

    public CheckRequestModel CheckRequestModel { get; set; }

    public string FundSource { get; set; }

    public string Function { get; set; }

    public string Location { get; set; }

    public string Program { get; set; }

    public string Subject { get; set; }

    public string Object { get; set; }

    public float Amount { get; set; }

    public int VendorNumber { get; set; }
}

您的 Razor 实现看起来不错。如果它是所有现有对象,那应该正确地回发一个 List<AllocationInformation> 对象。

问题出在您的 JavaScript 实施上。首先,您当前的 JavaScript 代码甚至没有考虑索引。所有的字段名称都是Allocation.Foo的形式。相反,它们需要采用 Allocations[N].Foo 的形式(还要注意复数形式)。

获取当前索引实际上是微不足道的。在您的 addAllocation JavaScript 方法中,只需执行:

var currentIndex = $('#allocationsDiv .row').length - 1;

然后,使用 currentIndex 变量代替字段名称中的 N

对于它的价值,您可能需要考虑将 Knockout JS 之类的东西集成到您的项目中。它使处理渲染集合之类的事情变得微不足道。您可以创建一个通用 "row" 模板,然后绑定该模板以针对某些可观察数组的每个实例进行渲染。然后,要添加或删除行,您只需在数组中添加或删除项目,Knockout 会相应地更新 HTML。

编辑

值得一提的是,我为您提供的查找当前索引的方法仅在项目未被带外删除时才有效。例如,如果您从三个项目(索引 0、1 和 2)开始,删除第二个(索引 1),然后添加一个新项目,您实际上将再次以索引 0、2 和 2 结束.显然,这是行不通的。您可以尝试在删除一个项目后重新索引所有现有项目,这将需要选择每一行,并使用基于 for 循环的新索引更新名称参数。然而,这确实非常笨重。这是您应该使用 Knockout JS 之类的东西的另一个原因。所有这些逻辑都是开箱即用的。

更新:淘汰赛速成班

这是我通常使用 Knockout 代码为我的视图设置代码的方式:

外部 JS

var Namespace = Namespace || {};

Namespace.Application = (function () {
    var _init = function (data) {
        var viewModel = Namespace.ViewModel(data);
        ko.applyBindings(viewModel);
        _wireEvents(viewModel);
        return viewModel;
    };

    var _wireEvents = function (viewModel) {
        $('#AddAllocation').on('click', viewModel.AddAllocation);
        $('#Allocations).on('click', '.remove', viewModel.RemoveAllocation);
        // other event handlers here
    };

    return {
        Init: _init
    };
})();

Namespace.ViewModel = function (data) {
    var self = {};

    self.Allocations = ko.observableArray(
        $.map(data.Allocations, Namespace.AllocationViewModel)
    );

    self.AddAllocation = function () {
        self.Allocations.push(new Namespace.AllocationViewModel(Namespace.Allocation));
    };

    self.RemoveAllocation = function () {
        var data = ko.dataFor(this);
        self.Allocations.remove(data);
    };

    return self;
};

Namespace.AllocationViewModel = function (data) {
    var self = {};

    self.Id = ko.observable(data.Id);
    self.CheckRequestId = ko.observable(data.CheckRequestId);
    // etc.

    return self;
};

页面 JS

<script>
    var Namespace = Namespace || {};

    Namespace.Allocation = @Html.Raw(Json.Encode(YourProject.Namespace.To.AllocationInformation));

    $(document).ready(function () {
        var data = @Html.Raw(Json.Encode(Model));
        Namespace.Application.Init(data);
    });
</script>

此处使用的 Namespace 变量是对您的应用程序使用命名空间的一般指示。污染 JavaScript 中的全局命名空间是一种不好的形式,因此您只需创建一些您的组织独有的变量,通常是公司名称之类的东西,然后将所有变量和函数放在该对象上。

Namespace.Application 对象是视图的核心功能。您可以随意命名;它不必被称为 Application.

Namespace.Allocation 只是您拥有的 AllocationInformation class 的 JavaScript 对象表示。存储它可以让您轻松添加基于此对象模板的新分配。

Namespace.ViewModel是主要的Knockout视图模型,Namespace.AllocationViewModel是单个分配特定的视图模型。主视图模型将以这些的可观察数组结束(通过 $.map)。

在此示例代码中,我手动创建了所有可观察对象。实际上有一个用于 Knockout 的映射插件,它允许您执行如下操作:

Namespace.ViewModel = function (data) {
    var model = ko.mapping.fromJS(data);

    return model;
}

它会自动为 data 对象的任何成员创建可观察对象。然后,您只需要向视图模型添加您需要的其他内容,而不必手动拼出每个 属性。