0

This has been a thorn in my side for a while. If I use EditorFor on an array of objects and the editor Template has a form in it ex.

public class FooController:Controller {

    public ActionResult Action(Foo foo) {
        // ...
    }
}

Index.cshtml

@model IEnumerable<Foo>
@Html.EditorFor(m=> m)

EditorTemplate

@model Foo
@using (Html.BeginForm("action", "controller"))
{
    @Html.TextBoxFor(f=> f.A)
    @Html.CheckBoxFor(f=> f.B)
    @Html.LabelFor(f=> f.B)
}

So I'll hit a few problems.

The checkbox label's for doesn't bind correctly to the checkbox (This has to do with the label not receiving the proper name of the property ([0].A as opposed to A).

I'm aware I can get rid of the pre- text by doing a foreach on the model in Index but that screws up ids and naming as the framework doesnt realize there are multiples of the same item and give them the same names.

For the checkboxes I've just been doing it manually as such.

@Html.CheckBoxFor(m => m.A, new {id= Html.NameFor(m => m.A)})
<label for="@Html.NameFor(m => m.A)">A</label>

However I cant solve the inability of the controller to accept the item as a single model. I've even tried allowing an array of Foo's in the Action parameters but that only work when its the first item being edited ([0]...) if its any other item in the array (ex. [1].A) the controller doesn't know how to parse it. Any help would be appreciated.

Patrick
  • 1,717
  • 7
  • 21
  • 28
Alex Krupka
  • 710
  • 9
  • 20
  • How about making a complex model? – Marc Lyon Sep 07 '16 at 15:20
  • @MarcLyon do you mean wrapping the IEnumerable in a parent class? Because that defeats the whole purpose of wrapping each individual item in a form – Alex Krupka Sep 07 '16 at 15:30
  • I'm not entirely sure from your description, but it might be a duplicate of http://stackoverflow.com/q/25333332/11683 – GSerg Sep 07 '16 at 15:36
  • @GSerg I think that question is dealing with how to be able to use custom templates. I just want to be able to have each element use its default template with its form allowing it to pass it back without the mvc router getting confused and thinking its an array. – Alex Krupka Sep 07 '16 at 15:52
  • @AlexKrupka you can probably make a custom model binder that you write specially for that method, that cuts `[i].` before property names and binds it to the model you expect, but that makes payload that comes to your controller look invalid. – Red Sep 07 '16 at 21:26
  • The issue with the label is explained in [this answer](http://stackoverflow.com/questions/36150145/mvc-radiobuttonfor-razor-how-to-make-label-click-able/36150772#36150772). But why in the world are you generating a form for each item in the collection - you can only post back one at at time. Not only are you degrading performance, your confusing your users (a user might edit values in 2 forms, hit a submit button and assume everything is updated) - a sure way to ensure users will stop using your site. –  Sep 07 '16 at 23:21
  • Either use a single form (and remove the `
    ` element in the template so you can submit and update all changes to all items, or have a 'Edit' link that either redirects to an edit form, or displays a modal popup to edit the selected item.
    –  Sep 07 '16 at 23:27

2 Answers2

0

Make your model a class with the properties you need. create a class in your Models subfolder

public class MyModel { 
public IEnumerable<Foo> Foolist { get ; set;} 
public string Something { get;set;}
}

your EditorFor will have to have a foreach loop for Foolist... MVC will attempt to put your model together from the form and return it to your POST action in the controller.

Edit:

You could create an EditorTemplate for foo. In Views/Shared/EditorTemplates folder, create FooTemplate.cs

@model Foo
<div class="span6 float-left" style="margin-bottom: 6px">
    @Html.TextBoxFor(m => m.A, new { style = "width:190px" })
    @Html.CheckBoxFor(m => m.B, new { style = "width:40px" })
    @Html.ValidationMessage("foo", null, new { @class = "help-inline" })
</div>

then in your view

@foreach (var myFoo in Model)
    {
      @EditorFor(myFoo)
    }

This still suffers from the "model gets passed back as a whole" requiredment of yours. Not sure about why there is a need to process these individually.

Marc Lyon
  • 336
  • 2
  • 7
  • Thanks, but this is exactly what I'm trying to avoid I Don't want to have to send back all the data of every row in the IEnumerable. I want to send back a specific item. – Alex Krupka Sep 07 '16 at 15:31
  • Then you will need to utilize JavaScript and AJAX to send back atomic requests to your controller for update or whatever you are doing. If you use IEnumerable in your model, MVC will array out the values and when it returns the model it will give you the A.[0] indexes you are trying to avoid. What is the issue with processing the model as a whole? – Marc Lyon Sep 07 '16 at 15:35
  • Thanks I'm aware of the ajax and JS options but I'm trying to solve this particluar problem without them. In the given case if the Action was a edit for the model you don't want to record that every object was edited (if you have an auditDate on the model) just because you updated 1 item out of thousands – Alex Krupka Sep 07 '16 at 15:46
0

Hah finally solved this - Here's how I did it. As a bit of background HTML forms use the name attribute when submitting forms, but the label for element uses Id . so I only adapt the id tag to have the prefix and not the name tag.

--In the cshtml file

@{
  var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
  ViewData.TemplateInfo.HtmlFieldPrefix = "";
}

then I can specify the id for the properties by their prefix while letting the name remain the same like so

  @Html.CheckBoxFor(m => m.A, 
                    new {id = prefix+"."+ Html.NameFor(m => m.A)}) 
 <label for="@prefix.@Html.NameFor(m => m.A)">A!</label></div>
Alex Krupka
  • 710
  • 9
  • 20