3

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?

8
  • Refer the answers here and here for dynamically adding and deleting complex objects from collections Commented Apr 19, 2016 at 22:45
  • I know about the possibility of using indices. I was looking for a solution without them, because I think they produce much html-overhead without any need for that, given that I have only one string per index. Commented Apr 20, 2016 at 0:01
  • Since you editing a collection of complex objects, you must use indexers (there is no way to match up which Name belongs with which Id and MaterialId property otherwise, especially if you dynamically adding and deleting items even if you were to create a custom ModelBinder). You can only avoid the indexers if your property is public List<string> MaterialNames { get; set; } Commented Apr 20, 2016 at 0:05
  • In theory I do not need more information than I have. I would have to add the index just for the binder. My idea was to implement i.e. an overridden cast to enable the binder to convert strings to MaterialNames. That leads to the question: are the non-complex types the binder supports hard-coded? Does the binder use any kind of standard deserialization method so that I could add the conversion logic at that point? Commented Apr 20, 2016 at 0:26
  • It would not make sense to do any of that. Assume you have existing items M1, M2 and M3 and in the view you delete M2 and add M4 and change the name of M3 to M5. There is no way that you could then identify which items you need to add, update of delete in the database unless you have the associated ID property and the only way that can be done is by providing indexers. Commented Apr 20, 2016 at 0:35

3 Answers 3

2

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

Sign up to request clarification or add additional context in comments.

3 Comments

I did something similar to your solution just to make asp.net MVC model binding to work with the list of complex objects, since the posted values must have the index value in sequential order. In the end, I've dynamically added those hidden input fields before the form got posted.
@KMC The indexes actually don't have to be sequential. For example, in that code above, we could do index += 2 to deliberately skip values - it will still bind. They do all need to be both positive and unique values, though.
I remember having random indexes like account.name[0] and account.name[10]. I'll give it another try. thanks.
1

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.

2 Comments

I do not really like the idea because I need to duplicate data and structure/objects, just to fit the binder's needs. Additionally I need to add extra logic at all places where I want to present or let the user change those names. Possibly it's the best option though :/
I agree with you and that is why I am not a big fan of MVC(as a framework) or any framework that dictates how you build an application. I usually build services, or use websockets on the server side and plain html and javascript or typescript on the client side.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.