下面的代码已被剥夺了很多,但基本上我要找实现如下:
我想能够编辑问题和他们的回答含选择,同时能够动态地从网页添加/删除问题/答案选择。 理想情况下,我的项目HtmlFieldPrefix是不连续的,但Html.EditorFor()使用顺序索引。
我有一个问题视图模型包含答案选项的IEnumerable:
public class QuestionViewModel
{
public int QuestionId { get; set; }
public IEnumerable<AnswerChoiceViewModel> AnswerChoices { get; set; }
}
在我的问题的局部视图(Question.ascx),我有这样的:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.QuestionViewModel>" %>
<%=Html.HiddenFor(m => m.QuestionId)%>
<%=Html.EditorFor(m => m.AnswerChoices) %>
而答案选择编辑模板(AnswerChoiceViewModel.ascx):
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.AnswerChoiceViewModel>" %>
<%=Html.HiddenFor(m => m.AnswerChoiceId)%>
<%=Html.TextBoxFor(m => m.Name)%>
当我渲染Question.ascx,输出将如下所示:
<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" />
我想知道的是我能提供给EditorFor自定义的GUID索引,使页面会使这样的:
<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" />
我已经写一个辅助方法,将得到当前上下文的前缀索引,并将其存储在一个隐藏的“的.index”字段,以便不连续的索引可以正确绑定。 只是想知道EditorFor是如何分配的指标,这样我可以重写它(或任何其他工作液)。
虽然以前我解决了这个问题,并跑进从他那里描述和解决类似的问题S.桑德森(Knockoutjs的创建者)后。 我用他的代码部分,并试图修改它以适合我的需要。 我把下面的代码中的某一类(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("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", 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<string> 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<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
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 + "]"; } <input type="hidden" name="@(prefix).Index" value="@index"/>
现在,这是什么做的? 我们得到一个新的GUID,这是我们替换是自动分配的整数一个新指标。 接下来,我们得到了得到了默认的字段前缀和我们去掉这个int指数,我们不想要的。 承认我们已经创建了一些技术上的债务后,我们再更新可视数据,使所有的editorfor调用现在使用它作为新的前缀。 最后,我们添加被调回模型绑定指定它应该使用这些字段绑在一起的指数的输入。
哪里这个魔术需要的情况发生? 里面你的编辑器模板:/Views/Shared/EditorTemplates/Phone.cshtml
@using TestMVC.Models @using System.Text.RegularExpressions @model Phone <div class="form-horizontal"> <hr /> @{ 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 + "]"; } <input type="hidden" name="@(prefix).Index" value="@index"/> <div class="form-group"> @Html.LabelFor(model => model.Number, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Number, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Number, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.IsEnabled, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> <div class="checkbox"> @Html.EditorFor(model => model.IsEnabled) @Html.ValidationMessageFor(model => model.IsEnabled, "", new { @class = "text-danger" }) </div> </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Details, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.TextAreaFor(model => model.Details, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Details, "", new { @class = "text-danger" }) </div> </div> </div>
EditorTemplate? 什么?! 怎么样?! 只要把它放在上面使用对象名称为文件名中提到的目录。 让MVC公约的工作它的魔力。 从主视图中只需添加为IEnumerable的属性编辑器:
<div class="form-group"> @Html.LabelFor(model => model.Phones, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Phones, new { htmlAttributes = new { @class = "form-control" } }) </div> </div>
现在,回到你的控制器,请确保您更新方法签名接受IEnumerable的(包括绑定手机):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ContactId,FirstName,LastName,Phones")] Contact contact)
{
if (ModelState.IsValid)
{
db.Contacts.Add(contact);
db.SaveChanges();
//TODO need to update this to save phone numbers
return RedirectToAction("Index");
}
return View(contact);
}
你如何添加和删除他们在网页上? 添加一些按钮,结合一些JavaScript,添加一个方法来控制,这将返回一个视图,而且模型。 阿贾克斯回抓住它,并把它插入到页面中。 我会让你的工作这些细节,因为它是在这一点上只忙着工作。
Html.EditorFor
是闲来无事为所谓的HTML辅助方法,这使得input
与所有apropriate属性。
其中谈到我心中唯一的解决办法是写自己一个人。 它必须很简单 - 5-10线灵。 看看这个创建自定义HTML辅助的mvc 。
史蒂夫·桑德森提供了一个简单的实现 ,可能你在找什么。 我最近开始使用它自己; 它并不完美,但它确实工作。 你必须做一个小魔术,拉丝用他BeginCollectionItem
方法,不幸的是, 我试图要解决的是自己。
另一种选择是覆盖id属性是这样的:
@Html.TextBoxFor(m => m.Name, new { id = @guid })
文章来源: How to produce non-sequential prefix collection indices with MVC HTML Editor templates?