使用 ASP.NET MVC 和 Razor 添加相关实体?
Add related entities with ASP.NET MVC and Razor?
我有一个 Person
class 有导航 属性 ICollection<Address> Addresses
。 Address
实体有 属性 City
.
我想为用户提供 create/update a Person
及其地址的选项,包括添加和删除地址。
因此,为了显示地址,我只需调用 @Html.EditorFor(m => m.Addresses)
,razor 引擎使用驻留在 EditorTemplates 文件夹中的自定义模板来处理集合,生成foreach 字段匹配签名,如 id="Addresses_0__City" name="Addresses[0].City"
.
到目前为止一切顺利。
问题是添加新的Address
es。
我创建了一个按钮,单击该按钮时,jQuery 函数会调用一个操作(通过自定义 EditorTemplate 呈现),但其字段没有上述签名,但是仅 id="City" name="City"
,因此在 post 操作中未被识别为 Person
实体的一部分。
如何使用正确的签名生成那些 id
和 name
字段?
我已阅读 this 文章和许多其他文章,但发现 none 解决了 id
和 name
的问题。
根据评论中的建议,我最终使用了以下内容。
无论如何,让我感到困扰的是我必须将新项目包装为集合,并且隐藏字段只是附加在集合项目之后,而不是被注入(因为在删除时它会留在那里)。
所以我最终添加了以下扩展,用于 Razor cshtml 文件,以及在向集合中添加新项目时调用的操作:
这里是扩展(还有一些重载,请查看完整代码here):
private static string EditorForManyInternal<TModel, TValue>(HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, IEnumerable<TValue> collection, string templateName)
{
var sb = new StringBuilder();
var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
var htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
var items = collection ?? expression.Compile()(html.ViewData.Model);
foreach (var item in items)
{
var guid = Guid.NewGuid().ToString();
var dummy = new { Item = item };
var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);
var editor = html.EditorFor(singleItemExp, templateName, string.Format("{0}[{1}]", htmlFieldName, guid));
var hidden = String.Format(@"<input type='hidden' name='{0}.Index' value='{1}' />", htmlFieldName, guid);
var eNode = HtmlNode.CreateNode(editor.ToHtmlString().Trim());
if (eNode is HtmlTextNode)
throw new InvalidOperationException("Unsuported element.");
if (eNode.GetAttributeValue("id", "") == "")
eNode.SetAttributeValue("id", guid);
var hNode = HtmlNode.CreateNode(hidden);
eNode.AppendChild(hNode);
sb.Append(eNode.OuterHtml);
}
return sb.ToString();
}
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string templateName)
{
var value = EditorForManyInternal(html, expression, null, templateName);
return new MvcHtmlString(value);
}
视图中的用法:
<div>
<h4>@Resources.Person.Children</h4>
<ul id="patientChildren" class="list-group ajax-collection">
@Html.EditorForMany(m => m.Children)
</ul>
@Ajax.ActionLink("Create Child", "CreateChild", new { patientId = Model.Id, lastName = Model.LastName }, new AjaxOptions { UpdateTargetId = "patientChildren", InsertionMode = InsertionMode.InsertAfter, OnSuccess = CommonData.AjaxOnSuccessJsFuncName }, new { @class = "button btn-default" })
</div>
这是正在调用的 ajax 函数(将生成的项目分类为 ajax-collection-item
并将删除按钮分类为 btn remove
很重要):
//#region Ajax add and remove
var ajaxCollectionItemSelector = '.ajax-collection-item';
function attachAjaxRemoveHandlers(id) {
var context = $(id ? '#' + id : ajaxCollectionItemSelector);
var removeButton = context.find('.btn.remove');
removeButton.click(function () {
var button = $(this);
var collectionItem = button.closest(ajaxCollectionItemSelector);
collectionItem.remove();
});
};
function ajaxOnSuccess(ajaxContext) {
var collectionItem = $(ajaxContext);
var id = collectionItem.prop('id');
attachAjaxRemoveHandlers(id);
//TODO: following line doesn't work
collectionItem.find(':text:first-of-type').focus();
};
function runCommonScripts() {
attachAjaxRemoveHandlers();
};
//#endregion Ajax add and remove
新项目操作 (CreateChild
) 如下所示(EditorForSingle
扩展在 the same place:
public ContentResult CreateChild(int patientId, string lastName)
{
return this.EditorForSingle((Patient p) => p.Children,
new PatientChild
{
PatientId = patientId,
LastName = lastName
});
}
我有一个 Person
class 有导航 属性 ICollection<Address> Addresses
。 Address
实体有 属性 City
.
我想为用户提供 create/update a Person
及其地址的选项,包括添加和删除地址。
因此,为了显示地址,我只需调用 @Html.EditorFor(m => m.Addresses)
,razor 引擎使用驻留在 EditorTemplates 文件夹中的自定义模板来处理集合,生成foreach 字段匹配签名,如 id="Addresses_0__City" name="Addresses[0].City"
.
到目前为止一切顺利。
问题是添加新的Address
es。
我创建了一个按钮,单击该按钮时,jQuery 函数会调用一个操作(通过自定义 EditorTemplate 呈现),但其字段没有上述签名,但是仅 id="City" name="City"
,因此在 post 操作中未被识别为 Person
实体的一部分。
如何使用正确的签名生成那些 id
和 name
字段?
我已阅读 this 文章和许多其他文章,但发现 none 解决了 id
和 name
的问题。
根据评论中的建议,我最终使用了以下内容。
无论如何,让我感到困扰的是我必须将新项目包装为集合,并且隐藏字段只是附加在集合项目之后,而不是被注入(因为在删除时它会留在那里)。
所以我最终添加了以下扩展,用于 Razor cshtml 文件,以及在向集合中添加新项目时调用的操作:
这里是扩展(还有一些重载,请查看完整代码here):
private static string EditorForManyInternal<TModel, TValue>(HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, IEnumerable<TValue> collection, string templateName)
{
var sb = new StringBuilder();
var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
var htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
var items = collection ?? expression.Compile()(html.ViewData.Model);
foreach (var item in items)
{
var guid = Guid.NewGuid().ToString();
var dummy = new { Item = item };
var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);
var editor = html.EditorFor(singleItemExp, templateName, string.Format("{0}[{1}]", htmlFieldName, guid));
var hidden = String.Format(@"<input type='hidden' name='{0}.Index' value='{1}' />", htmlFieldName, guid);
var eNode = HtmlNode.CreateNode(editor.ToHtmlString().Trim());
if (eNode is HtmlTextNode)
throw new InvalidOperationException("Unsuported element.");
if (eNode.GetAttributeValue("id", "") == "")
eNode.SetAttributeValue("id", guid);
var hNode = HtmlNode.CreateNode(hidden);
eNode.AppendChild(hNode);
sb.Append(eNode.OuterHtml);
}
return sb.ToString();
}
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string templateName)
{
var value = EditorForManyInternal(html, expression, null, templateName);
return new MvcHtmlString(value);
}
视图中的用法:
<div>
<h4>@Resources.Person.Children</h4>
<ul id="patientChildren" class="list-group ajax-collection">
@Html.EditorForMany(m => m.Children)
</ul>
@Ajax.ActionLink("Create Child", "CreateChild", new { patientId = Model.Id, lastName = Model.LastName }, new AjaxOptions { UpdateTargetId = "patientChildren", InsertionMode = InsertionMode.InsertAfter, OnSuccess = CommonData.AjaxOnSuccessJsFuncName }, new { @class = "button btn-default" })
</div>
这是正在调用的 ajax 函数(将生成的项目分类为 ajax-collection-item
并将删除按钮分类为 btn remove
很重要):
//#region Ajax add and remove
var ajaxCollectionItemSelector = '.ajax-collection-item';
function attachAjaxRemoveHandlers(id) {
var context = $(id ? '#' + id : ajaxCollectionItemSelector);
var removeButton = context.find('.btn.remove');
removeButton.click(function () {
var button = $(this);
var collectionItem = button.closest(ajaxCollectionItemSelector);
collectionItem.remove();
});
};
function ajaxOnSuccess(ajaxContext) {
var collectionItem = $(ajaxContext);
var id = collectionItem.prop('id');
attachAjaxRemoveHandlers(id);
//TODO: following line doesn't work
collectionItem.find(':text:first-of-type').focus();
};
function runCommonScripts() {
attachAjaxRemoveHandlers();
};
//#endregion Ajax add and remove
新项目操作 (CreateChild
) 如下所示(EditorForSingle
扩展在 the same place:
public ContentResult CreateChild(int patientId, string lastName)
{
return this.EditorForSingle((Patient p) => p.Children,
new PatientChild
{
PatientId = patientId,
LastName = lastName
});
}