質問

I have a ViewModel that contains two lists of Conditions - BuyConditions and SellConditions - plus a couple of attributes - Name etc. The lists are displayed using tables in the view. jQuery allows the tables to be manipulated - adding Conditions to the tables (from Buy Condition / Sell Condition DDL's) and removing Conditions from the tables.

My problem is that the Condition lists are not updated in the model when it is posted back to the controller.

Something that may point to the issue is that when the model is returned to the view (due to ModelStat.IsValid failing), the data in the Markets DDL still exists, but the data in the Buy and Sell Condition DDL's doesn't.

I can't find an example on the net to figure out where I'm going wrong. Anyone done this before?

ViewModel:

public class StrategyViewModel
{
    public string ID { get; set; }

    [Display(Name = "Strategy Name")]
    public string StrategyName { get; set; }

    [Display(Name = "Market")]
    public string SelectedMarketID { get; set; }

    [Display(Name = "Asset Type")]
    public string SelectedShareTypeID { get; set; }

    [Display(Name = "Share")]
    public string SelectedShareID { get; set; }

    public bool Active { get; set; }

    public IEnumerable<Market> Markets { get; set; }              // top level of three cascading DDL's - the other two populated via JQuery
    public IEnumerable<Condition> BuyConditionList { get; set; }  // used to populate DDL
    public IEnumerable<Condition> SellConditionList { get; set; } // used to populate DDL 
    public List<BuyCondition> BuyConditions { get; set; }         // list of Buy Conditions
    public List<SellCondition> SellConditions { get; set; }       // list of Sell Conditions

    AppRepository repository = new AppRepository();

    public StrategyViewModel()
    {
        // populate lists
        Markets = ListUtils.AddDefaultOptionToMarketList( repository.GetMarkets(), -1, "Select a Market" ).AsEnumerable();
        BuyConditionList = repository.GetConditions();
        SellConditionList = repository.GetConditions();
        BuyConditions = new List<BuyCondition>();
        SellConditions = new List<SellCondition>();
    }
}

Controller:

 public ActionResult Create()
    {
        StrategyViewModel model = new StrategyViewModel();

        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    [Authorize(Roles = "User, Admin")]
    public ActionResult Create(StrategyViewModel model)
    {

        // for debugging
        var errorList = ModelState.Values.SelectMany(v => v.Errors);

        if (ModelState.IsValid)
        {

            var user = repository.GetCurrentUser(User.Identity.GetUserId());
            var strategy = new Strategy();

            strategy.StrategyName = model.StrategyName;
            strategy.ShareID = Convert.ToInt32(model.SelectedShareID);
            strategy.BuyConditions = model.BuyConditions.ToList();
            strategy.SellConditions = model.SellConditions.ToList();
            strategy.Active = model.Active;

            int strategyID = repository.AddStrategy(user, strategy);

            return RedirectToAction("Index");

        }

        return View(model);
    }

View:

@model ShareTrigger.Models.StrategyViewModel

@{
ViewBag.Title = "Create";
}

@{Html.EnableUnobtrusiveJavaScript(true);}


<script type="text/javascript">
$(document).ready(function () {

    $(function () {
        $('#SelectedMarketID').change(function () {
            var selectedMarketID = $(this).val();
            $.getJSON('@Url.Action("ShareTypes")', { marketId: selectedMarketID }, function (shareTypes) {

                var shareTypesSelect = $('#SelectedShareTypeID');

                shareTypesSelect.empty();

                $.each(shareTypes, function (index, shareType) {
                    shareTypesSelect.append($('<option/>').attr('value', shareType.Value).text(shareType.Text));
                });

                $('#SelectedShareTypeID').trigger("change");

            });
        });

        $('#SelectedShareTypeID').change(function () {
            var selectedShareTypeId = $(this).val();
            var selectedMarketID = $('#SelectedMarketID').val();

            $.getJSON('@Url.Action("Shares")', { shareTypeId: selectedShareTypeId, marketID: selectedMarketID }, function (shares) {

                var sharesSelect = $('#SelectedShareID');

                sharesSelect.empty();

                $.each(shares, function (index, share) {
                    sharesSelect.append($('<option/>').attr('value', share.Value).text(share.Text));
                });
            });
        });
    });

    $('#AddBuyCondition').click(function () {

        var conditionID = +$("#BuyConditionList").val();
        if (conditionID != -2) {
            conditionID = +$("#BuyConditionList").val();
            var conditionText = $("#BuyConditionList").find('option:selected').text();

            $("tr:contains('Add')").remove();

            $('#BCDropDownRow').before('<tr class="bcrow"><td class="BuyConditionCell" data-conditionID="' + conditionID + '"><input data-val="true" data-val-number="The field ConditionID must be a number." data-val-required="The ConditionID field is required." id="item_ConditionID" name="item.ConditionID" type="hidden" value="' + conditionID + '">' + conditionText + '</td><td><button class="RemoveBuyCondition type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-minus"></span></button></td></tr>');
            $("#BuyConditionList").find('option:selected').remove();
            var rows = $('#BuyConditionList option').size();
            if (rows == 0) { $("#BuyConditionList").append('<option value="-2">No more conditions to select</option>'); }
        }
    });

    $('#BuyConditionsTable').on('click', '.RemoveBuyCondition', function () {
        var conditionID = +$(this).parent().parent().find('.BuyConditionCell').data('conditionid');
        var conditionText = $(this).parent().parent().find('.BuyConditionCell').text();

        $(this).closest('.bcrow').remove();

        $('#BuyConditionList option[value="-2"]').remove();

        $("#BuyConditionList").append('<option value="' + conditionID + '">' + conditionText + '</option>');

        var options = $('#BuyConditionList option');
        var arr = options.map(function (_, o) { return { t: $(o).text(), v: o.value }; }).get();
        arr.sort(function (o1, o2) { return o1.t > o2.t ? 1 : o1.t < o2.t ? -1 : 0; });
        options.each(function (i, o) {
            o.value = arr[i].v;
            $(o).text(arr[i].t);
        });
    });


    $('#AddSellCondition').click(function () {

        var conditionID = +$("#SellConditionList").val();
        if (conditionID != -2) {
            conditionID = +$("#SellConditionList").val();
            var conditionText = $("#SellConditionList").find('option:selected').text();

            $("tr:contains('Add')").remove();
            $('#SCDropDownRow').before('<tr class="scrow"><td class="SellConditionCell" data-conditionID="' + conditionID + '"><input id="item_ConditionID" name="item.ConditionID" type="hidden" value="' + conditionID + '">' + conditionText + '</td><td><button class="RemoveSellCondition type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-minus"></span></button></td></tr>');
            $("#SellConditionList").find('option:selected').remove();
            var rows = $('#SellConditionList option').size();
            if (rows == 0) { $("#SellConditionList").append('<option value="-2">No more conditions to select</option>'); }
        }
    });

    $('#SellConditionsTable').on('click', '.RemoveSellCondition', function () {
        var conditionID = +$(this).parent().parent().find('.SellConditionCell').data('conditionid');
        var conditionText = $(this).parent().parent().find('.SellConditionCell').text();

        $(this).closest('.scrow').remove();

        $('#SellConditionList option[value="-2"]').remove();

        $("#SellConditionList").append('<option value="' + conditionID + '">' + conditionText + '</option>');

        var options = $('#SellConditionList option');
        var arr = options.map(function (_, o) { return { t: $(o).text(), v: o.value }; }).get();
        arr.sort(function (o1, o2) { return o1.t > o2.t ? 1 : o1.t < o2.t ? -1 : 0; });
        options.each(function (i, o) {
            o.value = arr[i].v;
            $(o).text(arr[i].t);
        });
    });
});
</script>

<h2>Create Strategy</h2>

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()

<div class="form-horizontal">
    <hr />
    @Html.ValidationSummary(true)

    <div class="form-group">
        @Html.LabelFor(model => model.StrategyName, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.StrategyName)
            @Html.ValidationMessageFor(model => model.StrategyName)
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.SelectedMarketID, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(x => x.SelectedMarketID, new SelectList(Model.Markets, "MarketId", "MarketCode"))
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.SelectedShareTypeID, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(x => x.SelectedShareTypeID, Enumerable.Empty<SelectListItem>(), "Select an Asset Type")
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.SelectedMarketID, new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(x => x.SelectedShareID, Enumerable.Empty<SelectListItem>(), "Select a Share")
        </div>
    </div>

    <div>
        &nbsp;
    </div>

    <div class="col-md-10">
        <table class="table" id="BuyConditionsTable">
            <tr>
                <th>
                    @Html.DisplayName("Buy Conditions")
                </th>
                <th></th>
            </tr>

            @foreach (var item in Model.BuyConditions)
            {
                <tr>
                    <td>
                        @Html.HiddenFor(modelItem => item.ConditionID)
                        @Html.DisplayFor(modelItem => item.ConditionName)
                    </td>
                    <td></td>
                </tr>
            }
            <tr id="BCDropDownRow">
                <td>
                    <button id="AddBuyCondition" type="button" class="btn btn-default btn-xs">
                        <span class="glyphicon glyphicon-plus"></span>
                    </button>
                    &nbsp;
                    @Html.DropDownListFor(x => x.BuyConditionList, new SelectList(Model.BuyConditionList, "ConditionID", "ConditionName"))
                </td>
                <td></td>
            </tr>
        </table>
    </div>

    <div>
        &nbsp;
    </div>

    <div class="col-md-10">
        <table class="table" id="SellConditionsTable">
            <tr>
                <th>
                    @Html.DisplayName("Sell Conditions")
                </th>
                <th></th>
            </tr>

            @foreach (var item in Model.SellConditions)
            {
                <tr>
                    <td>
                        @Html.HiddenFor(modelItem => item.ConditionID)
                        @Html.DisplayFor(modelItem => item.ConditionName)
                    </td>
                    <td></td>
                </tr>
            }
            <tr id="SCDropDownRow">
                <td>
                    <button id="AddSellCondition" type="button" class="btn btn-default btn-xs">
                        <span class="glyphicon glyphicon-plus"></span>
                    </button> &nbsp;
                    @Html.DropDownListFor(x => x.SellConditionList, new SelectList(Model.SellConditionList, "ConditionID", "ConditionName"))
                </td>
                <td></td>
            </tr>
        </table>
    </div>

    <div class="form-group">
        <div class="col-md-offset-9 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
</div>
}

@section Scripts {
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
}
役に立ちましたか?

解決

The reason you're getting BuyConditions and SellConditions empty because of the following code

@Html.HiddenFor(modelItem => item.ConditionID)

This will render ALL as

<input type="hidden" id="item_ConditionID" name="item.ConditionID" />

It can't be resolved in the controller action Create due to the wrong naming.

Instead, it should have been

@Html.HiddenFor(x => x.SellConditions[index].ConditionID)

This will render the control as (if index is 0)

<input type="hidden" id="SellConditions_0__ConditionID" name="SellConditions[0].ConditionID" />

Therefore, the foreach block should be as follow

@foreach (int index = 0; index < Model.SellConditions.Count; index++)
   {
        <tr>
            <td>
                @Html.HiddenFor(x => x.SellConditions[index].ConditionID)
                @Html.DisplayFor(x => x.SellConditions[index].ConditionName)
            </td>
            <td></td>
        </tr>
   }

And the same rule for BuyConditions too.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top