Reputation: 1167
In the ASP.Net MVC 5 application I'm currently writing, one Material
can have many names (MaterialName
s). 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
-input
s 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 name
s of the input
s 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 MaterialName
s 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
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:
Upvotes: 2
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
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