Georg Jung
Georg Jung

Reputation: 1167

MVC5 Custom Binding: List of Strings to List of Complex Type

In the ASP.Net MVC 5 application I'm currently writing, one Material can have many names (MaterialNames). I have build a web interface to add, edit, and delete those names for the material on the Edit and Create pages of the material. To achieve this I have multiple text-inputs on those pages which are named Material.MaterialNames. Those can be added and deleted client-side with javascript to change the number of names. I do not need indices in the names of the inputs because the data the user should see and edit is just a flat list of strings.

These are the relevant parts of my models:

public class MaterialVM
{
    public Material Material { get; set; }
    // ...
}
public class Material
{
    public int Id { get; set; }
    public List<MaterialName> MaterialNames { get; set; }
    // ...
}
public class MaterialName
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int MaterialId { get; set; }
}

Now all would work great if Material.MaterialNames would be of type List<string>. In my case the model binder can not create MaterialNames from the multiple string values passed as form data. I believe the default approach to fix this would be

Writing a custom model binder

Is it a good idea to do that like in this answer (= Override BindProperty and just change the behaviour for one PropertyType)? I would combine that with globally registering the binder for the type MaterialVM.

Do I miss a simpler option?

Isn't it possible to just offer the default binder a method to cast/convert/serialize/... a simple type to a complex type? Is my approach how to write a custom model binder correct? Are there any other options I'm missing which would make this more intuitive?

Upvotes: 3

Views: 1283

Answers (3)

Will Ray
Will Ray

Reputation: 10879

You can bind a complex type using the default model binder, but you need to add a .Index property to your repeating control's template.

Here is the simplest example I could think of for your case:

@model WebApplication25.Models.MaterialVM

@using (Html.BeginForm())
{
    @Html.HiddenFor(model => model.Material.Id)
    <table id="output">
        <tr>
            <th>Name</th>
            <th>MaterialId</th>
            <th>Id</th>
        </tr>

    </table>
    <button id="add-more" type="button">Add Another</button>
    <button type="submit">Submit</button>
}
@section scripts{
    <script>
        (function (window) {
            "use strict";
            var index = 0;
            $("#add-more").on("click", function () {
                $("#output").append(
                  "<tr><td>" +
                  "<input type='hidden' name='Material.MaterialNames.Index' value='" + index + "' />" + // add in the index
                  "<input type='text' name='Material.MaterialNames[" + index + "].Name' /></td>" +
                  "<td><input type='number' name='Material.MaterialNames[" + index + "].MaterialId' /></td>" +
                  "<td><input type='number' name='Material.MaterialNames[" + index + "].Id' />" +
                  "</td></tr>");
                index++;
            });

        }(window));
    </script>
}

This example should result in them appearing in your view model: Model bound data list

Upvotes: 2

Ionian316
Ionian316

Reputation: 2343

If you cache your MaterialVM in a session variable or Id and MaterialNames in a Dictionary<int, List<string>>, then you can post back to the action the id and a List<string> of the names. Using the id, you can look up the material, then update the model.

Alternatively, in JS, you can JSON.stringify your form data, then parse it on the server side.

Upvotes: 0

DonO
DonO

Reputation: 1070

The view model should be catering to the view so it should probably have a

List<string> materialNames

The MVC binding will bind to the view model.

Then in the controller or whatever layer you handle the mapping you can map the MaterialVM to Material.

Upvotes: 1

Related Questions