如何使用MVC HTML编辑器模板生成非顺序前缀集合索引?

以下代码已经被删除了很多,但基本上我想要实现的是如下:

我希望能够编辑问题及其包含的答案选择,同时能够从页面动态添加/删除问题/答案选择。 理想情况下,我的项目的HtmlFieldPrefix将是非顺序的,但Html.EditorFor()使用顺序索引。

我有一个问题ViewModel,其中包含IEnumerable的答案选择:

public class QuestionViewModel { public int QuestionId { get; set; } public IEnumerable AnswerChoices { get; set; } } 

在我的问题部分视图(Question.ascx)中,我有这个:

 <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>  m.QuestionId)%>  m.AnswerChoices) %> 

答案选择编辑器模板(AnswerChoiceViewModel.ascx):

 <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>  m.AnswerChoiceId)%>  m.Name)%> 

当我渲染Question.ascx时,输出将如下所示:

       

我想知道的是我如何提供EditorFor一个自定义GUID索引,以便页面呈现如下:

       

我已经编写了一个辅助方法,它将获取当前上下文的前缀索引并将其存储在隐藏的“.Index”字段中,以便可以正确绑定非顺序索引。 只是想知道EditorFor如何分配索引以便我可以覆盖它(或任何其他工作解决方案)。

前段时间我解决了这个问题,并从S. Sanderson(Knockoutjs的创建者)发了一篇post,他描述并解决了类似的问题。 我使用了部分代码并试图修改它以满足我的需要。 我将下面的代码放在某个类中(exapmle:Helpers.cs),在web.config中添加命名空间。

  #region CollectionItem helper private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_"; public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName) { var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName); string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString(); // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync. html.ViewContext.Writer.WriteLine(string.Format("", collectionName, itemIndex)); return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex)); } public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix) { return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix); } private static Queue GetIdsToReuse(HttpContextBase httpContext, string collectionName) { // We need to use the same sequence of IDs following a server-side validation failure, // otherwise the framework won't render the validation error messages next to each item. string key = idsToReuseKey + collectionName; var queue = (Queue)httpContext.Items[key]; if (queue == null) { httpContext.Items[key] = queue = new Queue(); var previouslyUsedIds = httpContext.Request[collectionName + ".index"]; if (!string.IsNullOrEmpty(previouslyUsedIds)) foreach (string previouslyUsedId in previouslyUsedIds.Split(',')) queue.Enqueue(previouslyUsedId); } return queue; } private class HtmlFieldPrefixScope : IDisposable { private readonly TemplateInfo templateInfo; private readonly string previousHtmlFieldPrefix; public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix) { this.templateInfo = templateInfo; previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix; templateInfo.HtmlFieldPrefix = htmlFieldPrefix; } public void Dispose() { templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix; } } #endregion 

你可以拥有EditorTemplate或者像这样的部分

 @using (Html.BeginCollectionItem("AnswerChoices")) { @Html.HiddenFor(m => m.AnswerChoiceId) @Html.TextBoxFor(m => m.Name) } 

并通过列表呈现模板(部分)进行枚举。

我花了很长时间来解决这个问题。 每个人都在努力工作。 秘诀就是这四行代码:

  @{ var index = Guid.NewGuid(); var prefix = Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, @"^(.+)\[\d+\]$").Groups[1].Captures[0].Value; //TODO add a ton of error checking and pull this out into a reusable class!!!! ViewData.TemplateInfo.HtmlFieldPrefix = prefix + "[" + index + "]"; }  

Html.EditorFor就像一个所谓的Html辅助方法,它使用所有适当的属性呈现input

我想到的唯一解决方案是编写自己的解决方案。 它必须非常简单 – 5-10行。 看看这个创建自定义Html助手Mvc 。

史蒂夫桑德森提供了一个简单的实现 ,可以做你想要的。 我最近开始自己使用它; 它并不完美,但确实有效。 不幸的是,你必须做一些神奇的穿线来使用他的BeginCollectionItem方法; 我正在努力解决这个问题。

另一种选择是覆盖id属性,如下所示:

@Html.TextBoxFor(m => m.Name, new { id = @guid })