The following code has been stripped down a lot, but basically what I'm looking to achieve is as follows:
I'd like to able to edit Questions and their containing Answer Choices, while being able to dynamically add/remove Questions/Answer Choices from the page. Ideally, the HtmlFieldPrefix for my items would be non-sequential, but Html.EditorFor() uses a sequential index.
I have a Question ViewModel that contains an IEnumerable of Answer Choices:
public class QuestionViewModel
{
public int QuestionId { get; set; }
public IEnumerable<AnswerChoiceViewModel> AnswerChoices { get; set; }
}
In my Question partial view (Question.ascx), I have this:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.QuestionViewModel>" %>
<%=Html.HiddenFor(m => m.QuestionId)%>
<%=Html.EditorFor(m => m.AnswerChoices) %>
And the Answer Choice editor template (AnswerChoiceViewModel.ascx):
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.AnswerChoiceViewModel>" %>
<%=Html.HiddenFor(m => m.AnswerChoiceId)%>
<%=Html.TextBoxFor(m => m.Name)%>
When I render Question.ascx, the output will look as follows:
<input type="hidden" id="QuestionId" value="1" />
<input type="hidden" id="Question.AnswerChoices[0].AnswerChoiceId" value="1" />
<input type="hidden" id="Question.AnswerChoices[0].Name" value="Answer Choice 1" />
<input type="hidden" id="QuestionId" value="2" />
<input type="hidden" id="Question.AnswerChoices[1].AnswerChoiceId" value="2" />
<input type="hidden" id="Question.AnswerChoices[1].Name" value="Answer Choice 2" />
What I want to know is how I can provide EditorFor a custom GUID index so that the page would render like this:
<input type="hidden" id="QuestionId" value="1" />
<input type="hidden" id="Question.AnswerChoices[e1424d5e-5585-413c-a1b0-595f39747876].AnswerChoiceId" value="1" />
<input type="hidden" id="Question.AnswerChoices[e1424d5e-5585-413c-a1b0-595f39747876].Name" value="Answer Choice 1" />
<input type="hidden" id="QuestionId" value="2" />
<input type="hidden" id="Question.AnswerChoices[633db1c3-f1e6-470b-9c7f-c138f2d9fa71].AnswerChoiceId" value="2" />
<input type="hidden" id="Question.AnswerChoices[633db1c3-f1e6-470b-9c7f-c138f2d9fa71].Name" value="Answer Choice 2" />
I have already written a helper method that will get the prefix index of the current context and store it in a hidden ".Index" field so that non-sequential indices can be bound correctly. Just want to know how EditorFor is assigning the indexes so that I can override it (or any other working solution).
While ago I tackled with this problem and ran into a post from S. Sanderson(creator of Knockoutjs) where he described and solved similar problem. I used portions of his code and tried to modify it to suit my needs. I put the code below in some class (exapmle: Helpers.cs), add the namespace in web.config.
After you can have EditorTemplate or partial like this
And enumerate through your list rendering template(partial).
Steve Sanderson has provided a simple implementation that may do what you're looking for. I recently started using it myself; it is not perfect, but it does work. You have to do a little magic-stringing to use his
BeginCollectionItem
method, unfortunately; I'm trying to workaround that myself.Another option is to override id attribute like this:
@Html.TextBoxFor(m => m.Name, new { id = @guid })
It took me way longer than it should to figure this out. Everyone is working way too hard to do this. The secret sauce is these four lines of code:
Now, what is this doing? We get a new guid, this is our new index to replace the integer one that is automagically assigned. Next we get the get the the default field prefix and we strip off that int index we don't want. After acknowledging we've created some technical debt, we then update the viewdata so that all of the editorfor calls now use that as the new prefix. Finally, we add an input that gets posted back to the model binder specifying the index it should use to bind these fields together.
Where does this magic need to happen? Inside your editor template: /Views/Shared/EditorTemplates/Phone.cshtml
EditorTemplate? What?! How?! Just put it in the directory mentioned above using the object name for the filename. Let MVC convention work its magic. From your main view just add the editor for that IEnumerable property:
Now, back in your controller, make sure you update your method signature to accept that ienumerable (Bind include Phones):
How do you add and remove them on the page? Add some buttons, bind some JavaScript, add a method to the controller that will return a view for that model. Ajax back to grab it and insert it into the page. I'll let you work out those details, as it's just busy work at this point.
Html.EditorFor
is nothing else as a so called Html helper method, which rendersinput
with all apropriate attributes.The only solution which comes me to mind is to write the own one. It must be pretty simple - 5-10 lines ling. Take a look at this Creating Custom Html Helpers Mvc.