-1

I have a view model like:

public class OrganisationViewModel
{
    public string OrganisationName { get; set; }
    public List<BranchViewModel> Branches { get; set; }
    // more properties...
}

public class BranchViewModel
{
    public string BranchName { get; set; }
    // more properties...
}

This is how the Organisation page looks like:

enter image description here

What I want to achieve is to allow user to update a single BranchViewModel, so I have created a Modal for each Branch and when user click on the Edit branch link, the modal will open:

@for (int i = 0; i < Model.BranchViewModels.Count(); i++)
{
    var branchModalId = OrganisationHelper.GetBranchModalId(Model.BranchViewModels[i].BranchId);

    <div class="modal fade" id="@branchModalId" tabindex="-1" role="dialog" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <form action="/organisation/updateBranch" method="post" role="form">
                    <div class="modal-body">
                        @Html.AntiForgeryToken()
                        <div class="form">
                            <div class="form-group">
                                @Html.LabelFor(m => Model.BranchViewModels[i].BranchName, htmlAttributes: new { @class = "", @maxlength = GlobalConstants.MaxLengthForLongName })
                                @Html.TextBoxFor(m => Model.BranchViewModels[i].BranchName, new { @class = "form-control input-text" })
                                @Html.ValidationMessageFor(m => Model.BranchViewModels[i].BranchName, "", new { @class = "text-danger" })
                            </div>

                            @*more properties...*@
                        </div>

                    </div>
                    <div class="modal-footer">
                        <input type="button" value="Cancel" class="btn btn-secondary-grey" data-dismiss="modal" />
                        <input type="submit" class="btn btn-primary-action" value="Save" />
                    </div>
                </form>
            </div>
        </div>
    </div>
}

enter image description here

Now the problem is, since branches belong to an array the inputs on the page are generated as an array, so something like this:

<input class="form-control input-text" data-val="true" id="BranchViewModels_0__BranchName" name="BranchViewModels[0].BranchName" readonly="readonly" type="text" value="35671900246">

So when I submit the changes to the branch, the values are passed to the controller as List, so this controller will accept a List of one Branch:

public ActionResult UpdateBranch(List<BranchViewModel> branchViewModel)
{
}

What I want to achieve is to be able to pass the single branch to the controller, so I want the signature of the controller to be like:

public ActionResult UpdateBranch(BranchViewModel branchViewModel)
{
}

But I need the branch to be rendered as an array in the HTML, otherwise I will get duplicated input Ids... what is the best way to achieve this?

Hooman Bahreini
  • 14,480
  • 11
  • 70
  • 137

2 Answers2

0

Can you try instead of using the static @HTML.LabelFor and @HTML.TextBoxFor, you could write the html manually with <input> tags and specify the name of the input field. This way, it won't have an index.

<input name="BranchViewModel.BranchId" type="hidden" value="Model.BranchViewModels[i].BranchId" />
<input name="BranchViewModel.BranchName" max="@GlobalConstants.MaxLenghForName" value="Model.BranchViewModels[i].BranchName" />

Full modal code;

@for (int i = 0; i < Model.BranchViewModels.Count(); i++)
{
    var branchModalId = OrganisationHelper.GetBranchModalId(Model.BranchViewModels[i].BranchId);

    <div class="modal fade" id="@branchModalId" tabindex="-1" role="dialog" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <form action="/organisation/updateBranch" method="post" role="form">
                    <div class="modal-body">
                        @Html.AntiForgeryToken()
                        <div class="form">
                            <div class="form-group">
                                <label>Model.BranchViewModels[i].BranchName</label>
                                <input name="BranchViewModel.BranchId" type="hidden"value="Model.BranchViewModels[i].BranchId" />
                                <input name="BranchViewModel.BranchName" max="@GlobalConstants.MaxLenghForName" value="Model.BranchViewModels[i].BranchName" />
                            </div>

                            @*more properties...*@
                        </div>

                    </div>
                    <div class="modal-footer">
                        <input type="button" value="Cancel" class="btn btn-secondary-grey" data-dismiss="modal" />
                        <input type="submit" class="btn btn-primary-action" value="Save" />
                    </div>
                </form>
            </div>
        </div>
    </div>
}
Jerdine Sabio
  • 5,688
  • 2
  • 11
  • 23
  • Thanks, that's a good suggestion... The problem is I have quite a few properties with their validation defined in the `ViewModel` and using Html helpers is really useful, so I would prefer to keep them... – Hooman Bahreini Feb 21 '20 at 02:32
0

ASP.NET MVC serialization works on html element names. Indexers will have html element names with _i__ (where i is index of branch in that collection) when you create them via for loop. So, instead of using indexers (accessing Model.BranchViewModels[i] via for loop), you can try using foreach.

@{
    var idx = 0;
}
@foreach (var branch in Model.BranchViewModels)
{
    var branchModalId = OrganisationHelper.GetBranchModalId(branch.BranchId);

    <div class="modal fade" id="@branchModalId" tabindex="-1" role="dialog" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <form action="/organisation/updateBranch" method="post" role="form">
                    <div class="modal-body">
                        @Html.AntiForgeryToken()
                        <div class="form">
                            <div class="form-group">
                                @Html.LabelFor(m => branch.BranchName, htmlAttributes: new { @class = "", @maxlength = GlobalConstants.MaxLengthForLongName })
                                @Html.TextBoxFor(m => branch.BranchName, new { @class = "form-control input-text", @id= Model.BranchViewModels[idx].BranchName})
                                @Html.ValidationMessageFor(m => branch.BranchName, "", new { @class = "text-danger" })
                            </div>

                            @*more properties...*@
                        </div>

                    </div>
                    <div class="modal-footer">
                        <input type="button" value="Cancel" class="btn btn-secondary-grey" data-dismiss="modal" />
                        <input type="submit" class="btn btn-primary-action" value="Save" />
                    </div>
                </form>
            </div>
        </div>
    </div>
    idx++;
}
sam
  • 1,937
  • 1
  • 8
  • 14
  • Thanks for this, but this would generate invalid HTML, because I would have duplicate ids. – Hooman Bahreini Feb 21 '20 at 03:53
  • did you mean duplicate html element ids? if so, you can set html attribute id explicitly the way how you are setting class. – sam Feb 21 '20 at 03:54
  • Yes, the input elements ids, would be duplicated. I have stated this in the last paragraph of the question. – Hooman Bahreini Feb 21 '20 at 03:55
  • Sorry, I missed that part. But you can set the id like this: define a counter outside the for loop `@{ var idx = 0; }` and then in the foreach loop, use it like `@Html.TextBoxFor(m => branch.BranchName, new { @class = "form-control input-text", @id= Model.BranchViewModels[idx].BranchName})` . Also don't forget to increment idx in the foreach loop. – sam Feb 21 '20 at 03:59