Asp.net mvc 如何在ASP.NET MVC 4 with Razor中为具有列表属性的对象生成编辑表单
我有一个MVC应用程序的编辑页面,使用Razor 我有一个这样的模型:Asp.net mvc 如何在ASP.NET MVC 4 with Razor中为具有列表属性的对象生成编辑表单,asp.net-mvc,Asp.net Mvc,我有一个MVC应用程序的编辑页面,使用Razor 我有一个这样的模型: public class MyModelObject { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public List<MyOtherModelObject> OtherModelObjects { get; se
public class MyModelObject
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<MyOtherModelObject> OtherModelObjects { get; set; }
}
我正在为MyModelObject创建编辑页面。我需要一种方法在MyModelObject的编辑页面上为表单添加空间,以便用户创建/添加尽可能多的MyThermodeObject实例到其他ModelObject列表中
我认为用户可以点击一个按钮,对另一个操作执行ajax操作,该操作返回表单元素的部分视图(没有表单标记,因为这是我编辑页面上表单的一部分)。当用户添加了他们想要的所有MyThermodelObject并填写了数据后,他们应该能够保存对现有MyModelObject的编辑,这将HttpPost发布到编辑操作中,并且希望所有MyThermodelObject都在正确的列表中
我还需要用户能够重新订购的项目,一旦他们添加了他们
有人知道怎么做吗?是否已实施此解决方案的示例项目或在线示例演练?这包含一个分步指南,说明如何实现此目标
更新: 根据评论部分的要求,我正在逐步说明如何使上述文章适应您的场景 型号:
public class MyOtherModelObject
{
public string Name { get; set; }
public string Description { get; set; }
}
public class MyModelObject
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<MyOtherModelObject> OtherModelObjects { get; set; }
}
视图(~/Views/Home/Index.cshtml
):
脚本:
$('#addItem').click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) {
$('#editorRows').append(html);
}
});
return false;
});
$('a.deleteRow').live('click', function () {
$(this).parents('div.editorRow:first').remove();
return false;
});
备注:
BeginCollectionItem
custom helper取自我链接的同一篇文章,但为了完整起见,我在这里提供它:
public static class HtmlPrefixScopeExtensions
{
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, html.Encode(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;
}
}
}
公共静态类HtmlPrefixScopeExtensions
{
私有常量字符串idsToReuseKey=“\uHTMLPrefixScopeExtensions\uIDStoreUse”;
公共静态IDisposable BeginCollectionItem(此HtmlHelper html,字符串集合名称)
{
var idsToReuse=GetIdsToReuse(html.ViewContext.HttpContext,collectionName);
string itemIndex=idsToReuse.Count>0?idsToReuse.Dequeue():Guid.NewGuid().ToString();
//需要autocomplete=“off”来解决一个非常恼人的Chrome行为,即在用户单击“后退”后重用旧值,这会导致xyz.index和xyz[…]值不同步。
html.ViewContext.Writer.WriteLine(string.Format(“”,collectionName,html.Encode(itemIndex));
返回BeginHtmlFieldPrefixScope(html,string.Format(“{0}[{1}]”,collectionName,itemIndex));
}
公共静态IDisposable BeginHtmlFieldPrefixScope(此HtmlHelper html,字符串htmlFieldPrefix)
{
返回新的HtmlFieldPrefixScope(html.ViewData.TemplateInfo,htmlFieldPrefix);
}
私有静态队列GetIdsToReuse(HttpContextBase httpContext,string collectionName)
{
//在服务器端验证失败后,我们需要使用相同的ID序列,
//否则,框架将不会在每个项旁边呈现验证错误消息。
string key=idsToReuseKey+collectionName;
var queue=(queue)httpContext.Items[key];
if(队列==null)
{
httpContext.Items[key]=队列=新队列();
var previouslyUsedIds=httpContext.Request[collectionName+“.index”];
如果(!string.IsNullOrEmpty(以前使用的DIDS))
foreach(previouslyUsedIds.Split(',')中的字符串previouslyUsedId)
queue.Enqueue(以前使用的did);
}
返回队列;
}
私有类HtmlFieldPrefixScope:IDisposable
{
私有只读TemplateInfo TemplateInfo;
私有只读字符串previousHtmlFieldPrefix;
公共HtmlFieldPrefixScope(TemplateInfo TemplateInfo,字符串htmlFieldPrefix)
{
this.templateInfo=templateInfo;
previousHtmlFieldPrefix=templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix=HtmlFieldPrefix;
}
公共空间处置()
{
templateInfo.HtmlFieldPrefix=以前的HtmlFieldPrefix;
}
}
}
我能够从这篇博文中吸取教训,并将其应用到我的案例中,其中一个ModelObject具有多个属性,其中许多属性是列表属性
我对他的脚本进行了适当的修改,以便在一个模型中处理多个列表,并将尽快在博客上发布我的解决方案。它肯定要等到我现在的冲刺之后。完成后我会发布链接。您可能会第一眼看到(尽管这是Razor语法,也可能是MVC4语法是否能给出一个想法)。这看起来对列表有效,但对作为另一个对象的一部分的列表有效吗?它能绑定模型吗?不完全有效。它讨论了如何创建可变长度的对象列表,但没有讨论如何创建可变长度的对象列表,这些对象是另一个对象的一部分。@DaveH,但这很容易实现。您所要做的就是引入一个新的视图模型,该模型具有您试图编辑的列表的
IEnumerable
属性,就像您在问题中对MyModelObject
所做的那样。您说得简单吗?所有这些将如何绑定回父对象MyModelObject。答案还有很大一部分需要解决,这可能涉及重新编写MVC的默认ModelBind行为。这样做是否存在安全问题。我正在研究这个示例,并在其他几个论坛帖子上找到了它,用于解决单个对象的列表,但还没有看到我给出的示例有效地解决了问题。好的,显然我必须一步一步地为您编写代码。请稍等。请看我的最新答案。正如您所看到的,代码与本文中显示的代码非常接近。没有必要重写模型绑定器:-)您只需插入模型即可。下次请多做点努力。
@model MyModelObject
@using(Html.BeginForm())
{
@Html.HiddenFor(x => x.Id)
<div>
@Html.LabelFor(x => x.Name)
@Html.EditorFor(x => x.Name)
</div>
<div>
@Html.LabelFor(x => x.Description)
@Html.TextBoxFor(x => x.Description)
</div>
<hr/>
<div id="editorRows">
@foreach (var item in Model.OtherModelObjects)
{
@Html.Partial("EditorRow", item);
}
</div>
@Html.ActionLink("Add another...", "BlankEditorRow", null, new { id = "addItem" })
<input type="submit" value="Finished" />
}
@model MyOtherModelObject
<div class="editorRow">
@using (Html.BeginCollectionItem("OtherModelObjects"))
{
<div>
@Html.LabelFor(x => x.Name)
@Html.EditorFor(x => x.Name)
</div>
<div>
@Html.LabelFor(x => x.Description)
@Html.EditorFor(x => x.Description)
</div>
<a href="#" class="deleteRow">delete</a>
}
</div>
$('#addItem').click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) {
$('#editorRows').append(html);
}
});
return false;
});
$('a.deleteRow').live('click', function () {
$(this).parents('div.editorRow:first').remove();
return false;
});
public static class HtmlPrefixScopeExtensions
{
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, html.Encode(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;
}
}
}